Skip to content

[Type checker] Improve diagnostics when an optional value is not unwrapped #17921

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 14, 2018

Conversation

DougGregor
Copy link
Member

@DougGregor DougGregor commented Jul 13, 2018

When we determine that an optional value needs to be unwrapped to make
an expression type check, use notes to provide several different
Fix-It options (with descriptions) rather than always pushing users
toward '!'. Specifically, the errors + Fix-Its now looks like this:

    error: value of optional type 'X?' must be unwrapped to a value of type 'X'
      f(x)
        ^
    note: coalesce using '??' to provide a default when the optional value contains 'nil'
      f(x)
        ^
          ?? <#default value#>
    note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
      f(x)
        ^
         !

In the case where we are referring to a member of an optional value, but wanted to refer to a member of the wrapped type, we provide a similar diagnostic with separate Fix-Its that suggestions a choice between optional chaining (with '?') and force-unwrap (with '!'):

error: value of optional type 'X?' must be unwrapped to refer to member 'f' of wrapped base type 'X'
  let _: Int = x.f()
               ^
note: chain the optional using '?' to access member 'f' only for non-'nil' base values
  let _: Int = x.f()
               ^
                ?
note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
  let _: Int = x.f()
               ^
                !

Fixes rdar://problem/42081852.

@xedin
Copy link
Contributor

xedin commented Jul 13, 2018

@DougGregor Maybe it would be useful to pull these changes in as well? https://github.com/apple/swift/pull/13839/files#diff-cf676abf2776cbcdb21380661ec41563R7718

Main idea of that PR was to remove re-solving with fixes enabled, that's why it didn't get merged.

@DougGregor
Copy link
Member Author

@xedin I do think we should bring those improvements into master, but I'm trying to keep my PR isolated to very-low-risk changes to diagnostics emission... I don't want anything to touch the solver in this PR because I want to pull it into 4.2.

@xedin
Copy link
Contributor

xedin commented Jul 13, 2018

Sounds good! I will create a separate PR to get them in then!

…apped.

When we determine that an optional value needs to be unwrapped to make
an expression type check, use notes to provide several different
Fix-It options (with descriptions) rather than always pushing users
toward '!'. Specifically, the errors + Fix-Its now looks like this:

    error: value of optional type 'X?' must be unwrapped to a value of
        type 'X'
      f(x)
        ^
    note: coalesce using '??' to provide a default when the optional
        value contains 'nil'
      f(x)
        ^
          ?? <#default value#>
    note: force-unwrap using '!' to abort execution if the optional
        value contains 'nil'
      f(x)
         ^
         !

Fixes rdar://problem/42081852.
@DougGregor DougGregor force-pushed the optional-not-unwrapped-diags branch from 3aade99 to 945c09b Compare July 13, 2018 18:02
@DougGregor
Copy link
Member Author

Now with updated test cases, but I have a few more places to eliminate the old diagnostic and provide a better diagnostic.

…ional base.

Improve diagnostics when referencing a member of an optional base, where the
Optional type does not have the member but the wrapped type does. Specifically,
suggest both the chaining ‘?’ and the force-unwrapping ‘!’ Fix-Its via explanatory
notes, e.g.:

  error: value of optional type '[Int]?' must be unwrapped to refer to member 'subscript' of wrapped base type '[Int]'
    return foo.array[0]
               ^
  note: chain the optional using '?' to access member 'subscript' only for non-'nil' base values
    return foo.array[0]
               ^
                    ?
  note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
    return foo.array[0]
               ^
                    !

More of rdar://problem/42081852.
@DougGregor
Copy link
Member Author

Second commit provides a diagnostic that suggests using the chaining '?' or a force-wrap '!' when doing member access onto an optional that needs to be unwrapped. It looks a little like this:

  error: value of optional type '[Int]?' must be unwrapped to refer to member 'subscript' of wrapped base type '[Int]'
    return foo.array[0]
               ^
  note: chain the optional using '?' to access member 'subscript' only for non-'nil' base values
    return foo.array[0]
               ^
                    ?
  note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
    return foo.array[0]
               ^
                    !

but isn't kicking in as often as I'd like due to other issues.

…mber access.

Introduce a new fix kind into the constraint solver to cover unwrapping the base
of a member access so we can refer to the a member of the unwrapped base.
Wire this fix kind to the just-added diagnostic that suggests either the
chaining ‘?’ or the force-unwrap ‘!’ via separate, descriptive Fix-Its.

Example:

error: value of optional type 'X?' must be unwrapped to refer to member 'f' of wrapped base type 'X'
  let _: Int = x.f()
               ^
note: chain the optional using '?' to access member 'f' only for non-'nil' base values
  let _: Int = x.f()
               ^
                ?
note: force-unwrap using '!' to abort execution if the optional value contains 'nil'
  let _: Int = x.f()
               ^
                !

Before this, we would sometimes get a Fix-It for just ‘?’ and sometimes get a Fix-It for the
coalescing ‘??’, neither of which is likely to be right.

More work on rdar://problem/42081852.
…wraps more consistently.

Replace the last (and most obscure) use of the poor “use ‘?’ or ‘!’” diagnostic with the
new, more explanatory version that provides separate notes with Fix-Its for coalescing or
force-unwrapping the value.

Finishes rdar://problem/42081852.
@DougGregor DougGregor changed the title [WIP] [Type checker] Improve diagnostics when an optional value is not unwrapped [Type checker] Improve diagnostics when an optional value is not unwrapped Jul 13, 2018
@DougGregor
Copy link
Member Author

@gregomni My change here to improve diagnostics around optional values that need to be unwrapped is going to conflict with #15321. I think this change makes it somewhat easier to accomplish what you want, though, because we have a better sense of when we're doing member access that could be done via a force or an optional chain, and it's eliminating some state we don't need in the constraint solver.

@DougGregor
Copy link
Member Author

@swift-ci please smoke test

1 similar comment
@DougGregor
Copy link
Member Author

@swift-ci please smoke test

Copy link
Contributor

@rudkx rudkx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome, thanks!

// expected-note@-1{{chain the optional using '?'}}{{18-18=?}}
// expected-note@-2{{force-unwrap using '!'}}{{18-18=!}}
let b: Int = q.s.utf8 // expected-error{{value of optional type 'String?' must be unwrapped to refer to member 'utf8' of wrapped base type 'String'}}
// expected-note@-1{{chain the optional using '?'}}{{17-17=?}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as an observation - it might not be best to suggest ? in case when contextual is not optional because we'd end up suggesting wrapping right-hand side in (q.s?.utf8)! anyway after that fix-it is applied...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That requires the solver try it as ? and see what happens. It’s good follow-up work that fits well with what @gregomni is doing, but I don’t want to make the solver do more in a 4.2-targeted change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, makes sense! I just wanted to point that out...

@DougGregor
Copy link
Member Author

An LLDB test was depending on this Fix-It; apple/swift-lldb#768 switches the test over to a different Fix-It.

@DougGregor
Copy link
Member Author

@swift-ci please smoke test macOS

@DougGregor DougGregor merged commit 44088fa into swiftlang:master Jul 14, 2018
@DougGregor DougGregor deleted the optional-not-unwrapped-diags branch July 14, 2018 06:43
if (recordFix(fixKind, locator))

// We're unwrapping the base to perform a member access.
if (recordFix(Fix::getUnwrapOptionalBase(*this, member), locator))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m confident the rest of this can be pulled out of the typing rule. Removing it altogether affects like 4 diagnostics in the test suite.

The only reason it’s here is because it is, by definition, operating on chains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants