Skip to content

[SR-5688] [Sema] Handle key path component base type on MemberAccessOnOptionalBaseFailure #32376

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
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6bba692
[CSDiagnostics] Adjusting MemberAccessOnOptionalBaseFailure to be abl…
LucianoPAlmeida Jun 14, 2020
e7b1058
[tests] Adding regression tests for SR-5688
LucianoPAlmeida Jun 14, 2020
d44d809
[CSDiagnostics] Adjusting source range to diagnose/insert the fixes i…
LucianoPAlmeida Jun 14, 2020
69409c1
[tests] Adjusting regression tests to handle the fixits
LucianoPAlmeida Jun 14, 2020
96267b0
[AST] Creating an helper getSourceRange function for KeyPathExpr::Com…
LucianoPAlmeida Jun 14, 2020
93c49fc
[ConstraintSystem] Store member base type when recording UnwrapOption…
LucianoPAlmeida Jun 15, 2020
ae6b971
[AST] Creating a new diagnostic note for removing optional from writt…
LucianoPAlmeida Jun 15, 2020
a05d597
[CSDiagnostics] Adjusting logic around MemberAccessOnOptionalBaseFail…
LucianoPAlmeida Jun 15, 2020
6ebfd83
[tests] Adjusting regression tests to add subscript and key path root…
LucianoPAlmeida Jun 15, 2020
2f31e85
[Diagnostics] Adjusting message to mention base type
LucianoPAlmeida Jun 15, 2020
4df6287
[CSDiagnostics] Better naming for method/variable that represents bas…
LucianoPAlmeida Jun 15, 2020
ca6eab3
[CSDiagnostics] Adjusting to use the stored base member only when mem…
LucianoPAlmeida Jun 15, 2020
a5028f0
[Diagnostics] Adjusting minor typos and code
LucianoPAlmeida Jun 15, 2020
9caaf21
[AST] Adjusting keypath root diagnostic note message for use unwrappe…
LucianoPAlmeida Jun 20, 2020
7b34558
[CSDiagnostics] Adjusments in MemberAccessOnOptionalBaseFailure diagn…
LucianoPAlmeida Jun 20, 2020
0694bc4
[tests] Adding more test cases for SR-5688
LucianoPAlmeida Jun 20, 2020
b3fcf7a
[CSDiagnostics] Adjusting fixits for key path root and range for diag…
LucianoPAlmeida Jun 24, 2020
7f57de4
[CSSimplify] Attempt to diagnose InsertExplicitCall for optional func…
LucianoPAlmeida Jun 24, 2020
720e737
[tests] Adding TODO to improve the diagnostics refering to key path r…
LucianoPAlmeida Jun 25, 2020
a43e7f8
[CSDiagnostics] Adjusting comments
LucianoPAlmeida Jun 25, 2020
3a961c8
[CSSimplify] Adjusting logic on simplifyOptionalObjectConstraint to a…
LucianoPAlmeida Jun 25, 2020
1e7ebcf
[CSDiagnostics] Adjust the logic to use resolveType on MemberAccessOn…
LucianoPAlmeida Jun 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,9 @@ ERROR(optional_base_not_unwrapped,none,
NOTE(optional_base_chain,none,
"chain the optional using '?' to access member %0 only for non-'nil' "
"base values", (DeclNameRef))
NOTE(optional_base_remove_optional_for_keypath_root, none,
"use unwrapped type %0 as key path root", (Type))

ERROR(missing_unwrap_optional_try,none,
"value of optional type %0 not unwrapped; did you mean to use 'try!' "
"or chain with '?'?",
Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -5380,6 +5380,13 @@ class KeyPathExpr : public Expr {
return Loc;
}

SourceRange getSourceRange() const {
if (auto *indexExpr = getIndexExpr()) {
return indexExpr->getSourceRange();
}
return Loc;
}

Kind getKind() const {
return KindValue;
}
Expand Down
71 changes: 53 additions & 18 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -984,37 +984,72 @@ bool MissingExplicitConversionFailure::diagnoseAsError() {
return true;
}

SourceRange MemberAccessOnOptionalBaseFailure::getSourceRange() const {
if (auto componentPathElt =
getLocator()->getLastElementAs<LocatorPathElt::KeyPathComponent>()) {
auto anchor = getAnchor();
auto keyPathExpr = castToExpr<KeyPathExpr>(anchor);
if (componentPathElt->getIndex() == 0) {
if (auto rootType = keyPathExpr->getRootType()) {
return rootType->getSourceRange();
}
} else {
auto componentIdx = componentPathElt->getIndex() - 1;
auto component = keyPathExpr->getComponents()[componentIdx];
return component.getSourceRange();
}
}
return FailureDiagnostic::getSourceRange();
}

bool MemberAccessOnOptionalBaseFailure::diagnoseAsError() {
auto anchor = getAnchor();
auto baseType = getType(anchor);
auto baseType = getMemberBaseType();
auto locator = getLocator();

bool resultIsOptional = ResultTypeIsOptional;

// If we've resolved the member overload to one that returns an optional
// type, then the result of the expression is optional (and we want to offer
// only a '?' fixit) even though the constraint system didn't need to add any
// additional optionality.
auto overload = getOverloadChoiceIfAvailable(getLocator());
auto overload = getOverloadChoiceIfAvailable(locator);
if (overload && overload->openedType->getOptionalObjectType())
resultIsOptional = true;

auto unwrappedBaseType = baseType->getOptionalObjectType();
if (!unwrappedBaseType)
return false;

emitDiagnostic(diag::optional_base_not_unwrapped, baseType, Member,
unwrappedBaseType);

// FIXME: It would be nice to immediately offer "base?.member ?? defaultValue"
// for non-optional results where that would be appropriate. For the moment
// always offering "?" means that if the user chooses chaining, we'll end up
// in MissingOptionalUnwrapFailure:diagnose() to offer a default value during
// the next compile.
emitDiagnostic(diag::optional_base_chain, Member)
.fixItInsertAfter(getSourceRange().End, "?");

if (!resultIsOptional) {
emitDiagnostic(diag::unwrap_with_force_value)
.fixItInsertAfter(getSourceRange().End, "!");

auto sourceRange = getSourceRange();

emitDiagnostic(diag::optional_base_not_unwrapped,
baseType, Member, unwrappedBaseType);

auto componentPathElt =
locator->getLastElementAs<LocatorPathElt::KeyPathComponent>();
if (componentPathElt && componentPathElt->getIndex() == 0) {
// For members where the base type is an optional key path root
// let's emit a tailored note suggesting to use its unwrapped type.
auto *keyPathExpr = castToExpr<KeyPathExpr>(getAnchor());
if (auto rootType = keyPathExpr->getRootType()) {
emitDiagnostic(diag::optional_base_remove_optional_for_keypath_root,
unwrappedBaseType)
.fixItReplace(rootType->getSourceRange(),
unwrappedBaseType.getString());
}
} else {
// FIXME: It would be nice to immediately offer "base?.member ?? defaultValue"
// for non-optional results where that would be appropriate. For the moment
// always offering "?" means that if the user chooses chaining, we'll end up
// in MissingOptionalUnwrapFailure:diagnose() to offer a default value during
// the next compile.
emitDiagnostic(diag::optional_base_chain, Member)
.fixItInsertAfter(sourceRange.End, "?");

if (!resultIsOptional) {
emitDiagnostic(diag::unwrap_with_force_value)
.fixItInsertAfter(sourceRange.End, "!");
}
}

return true;
Expand Down
18 changes: 17 additions & 1 deletion lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -479,16 +479,32 @@ class LabelingFailure final : public FailureDiagnostic {
/// type without optional chaining or force-unwrapping it first.
class MemberAccessOnOptionalBaseFailure final : public FailureDiagnostic {
DeclNameRef Member;
Type MemberBaseType;
bool ResultTypeIsOptional;

public:
MemberAccessOnOptionalBaseFailure(const Solution &solution,
ConstraintLocator *locator,
DeclNameRef memberName, bool resultOptional)
DeclNameRef memberName,
Type memberBaseType,
bool resultOptional)
: FailureDiagnostic(solution, locator), Member(memberName),
MemberBaseType(resolveType(memberBaseType)),
ResultTypeIsOptional(resultOptional) {}

bool diagnoseAsError() override;

Type getMemberBaseType() const {
return MemberBaseType;
}

SourceLoc getLoc() const override {
// The end location points to the dot in the member access.
return getSourceRange().End;
}

SourceRange getSourceRange() const override;

};

/// Diagnose errors associated with rvalues in positions
Expand Down
15 changes: 9 additions & 6 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,24 @@ bool UnwrapOptionalBase::diagnose(const Solution &solution, bool asNote) const {
bool resultIsOptional =
getKind() == FixKind::UnwrapOptionalBaseWithOptionalResult;
MemberAccessOnOptionalBaseFailure failure(solution, getLocator(), MemberName,
resultIsOptional);
MemberBaseType, resultIsOptional);
return failure.diagnose(asNote);
}

UnwrapOptionalBase *UnwrapOptionalBase::create(ConstraintSystem &cs,
DeclNameRef member,
Type memberBaseType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
UnwrapOptionalBase(cs, FixKind::UnwrapOptionalBase, member, locator);
return new (cs.getAllocator()) UnwrapOptionalBase(
cs, FixKind::UnwrapOptionalBase, member, memberBaseType, locator);
}

UnwrapOptionalBase *UnwrapOptionalBase::createWithOptionalResult(
ConstraintSystem &cs, DeclNameRef member, ConstraintLocator *locator) {
return new (cs.getAllocator()) UnwrapOptionalBase(
cs, FixKind::UnwrapOptionalBaseWithOptionalResult, member, locator);
ConstraintSystem &cs, DeclNameRef member, Type memberBaseType,
ConstraintLocator *locator) {
return new (cs.getAllocator())
UnwrapOptionalBase(cs, FixKind::UnwrapOptionalBaseWithOptionalResult,
member, memberBaseType, locator);
}

bool AddAddressOf::diagnose(const Solution &solution, bool asNote) const {
Expand Down
9 changes: 6 additions & 3 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,12 @@ class ConstraintFix {
/// Unwrap an optional base when we have a member access.
class UnwrapOptionalBase final : public ConstraintFix {
DeclNameRef MemberName;
Type MemberBaseType;

UnwrapOptionalBase(ConstraintSystem &cs, FixKind kind, DeclNameRef member,
ConstraintLocator *locator)
: ConstraintFix(cs, kind, locator), MemberName(member) {
Type memberBaseType, ConstraintLocator *locator)
: ConstraintFix(cs, kind, locator), MemberName(member),
MemberBaseType(memberBaseType) {
assert(kind == FixKind::UnwrapOptionalBase ||
kind == FixKind::UnwrapOptionalBaseWithOptionalResult);
}
Expand All @@ -352,11 +354,12 @@ class UnwrapOptionalBase final : public ConstraintFix {
bool diagnose(const Solution &solution, bool asNote = false) const override;

static UnwrapOptionalBase *create(ConstraintSystem &cs, DeclNameRef member,
Type memberBaseType,
ConstraintLocator *locator);

static UnwrapOptionalBase *
createWithOptionalResult(ConstraintSystem &cs, DeclNameRef member,
ConstraintLocator *locator);
Type memberBaseType, ConstraintLocator *locator);
};

// Treat rvalue as if it was an lvalue
Expand Down
52 changes: 34 additions & 18 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5697,26 +5697,41 @@ ConstraintSystem::simplifyOptionalObjectConstraint(
// If the base type is not optional, let's attempt a fix (if possible)
// and assume that `!` is just not there.
if (!objectTy) {
if (!shouldAttemptFixes())
return SolutionKind::Error;

// Let's see if we can apply a specific fix here.
if (shouldAttemptFixes()) {
if (optTy->isHole())
return SolutionKind::Solved;

auto *fix =
RemoveUnwrap::create(*this, optTy, getConstraintLocator(locator));
if (optTy->isHole())
return SolutionKind::Solved;

auto fnType = optTy->getAs<FunctionType>();
if (fnType && fnType->getNumParams() == 0) {
// For function types with no parameters, let's try to
// offer a "make it a call" fix if possible.
auto optionalResultType = fnType->getResult()->getOptionalObjectType();
if (optionalResultType) {
if (matchTypes(optionalResultType, second, ConstraintKind::Bind,
flags | TMF_ApplyingFix, locator)
.isSuccess()) {
auto *fix =
InsertExplicitCall::create(*this, getConstraintLocator(locator));

return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
}
}
}

if (recordFix(fix))
return SolutionKind::Error;
auto *fix =
RemoveUnwrap::create(*this, optTy, getConstraintLocator(locator));

// If the fix was successful let's record
// "fixed" object type and continue.
objectTy = optTy;
} else {
// If fixes are not allowed, no choice but to fail.
if (recordFix(fix))
return SolutionKind::Error;
}

// If the fix was successful let's record
// "fixed" object type and continue.
objectTy = optTy;
}

// The object type is an lvalue if the optional was.
if (optLValueTy->is<LValueType>())
objectTy = LValueType::get(objectTy);
Expand Down Expand Up @@ -6950,11 +6965,12 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
SmallVector<Constraint *, 2> optionalities;
auto nonoptionalResult = Constraint::createFixed(
*this, ConstraintKind::Bind,
UnwrapOptionalBase::create(*this, member, locator), memberTy, innerTV,
locator);
UnwrapOptionalBase::create(*this, member, baseObjTy, locator),
memberTy, innerTV, locator);
auto optionalResult = Constraint::createFixed(
*this, ConstraintKind::Bind,
UnwrapOptionalBase::createWithOptionalResult(*this, member, locator),
UnwrapOptionalBase::createWithOptionalResult(*this, member,
baseObjTy, locator),
optTy, memberTy, locator);
optionalities.push_back(nonoptionalResult);
optionalities.push_back(optionalResult);
Expand Down
7 changes: 7 additions & 0 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1342,3 +1342,10 @@ func rdar62989214() {
arr[flag].isTrue // expected-error {{cannot convert value of type 'Flag' to expected argument type 'Int'}}
}
}

// SR-5688
func SR5688_1() -> String? { "" }
SR5688_1!.count // expected-error {{function 'SR5688_1' was used as a property; add () to call it}} {{9-9=()}}

func SR5688_2() -> Int? { 0 }
let _: Int = SR5688_2! // expected-error {{function 'SR5688_2' was used as a property; add () to call it}} {{22-22=()}}
3 changes: 1 addition & 2 deletions test/Constraints/fixes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ class T {
func m1() {
// <rdar://problem/17741575>
let l = self.m2!.prop1
// expected-error@-1 {{cannot force unwrap value of non-optional type '() -> U?'}} {{22-23=}}
// expected-error@-2 {{method 'm2' was used as a property; add () to call it}} {{22-22=()}}
// expected-error@-1 {{method 'm2' was used as a property; add () to call it}} {{22-22=()}}
}

func m2() -> U! {
Expand Down
79 changes: 79 additions & 0 deletions test/expr/unary/keypath/keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,85 @@ func testMissingMember() {
_ = \String.x.y // expected-error {{value of type 'String' has no member 'x'}}
}

// SR-5688
struct SR5688_A {
var b: SR5688_B?
}

struct SR5688_AA {
var b: SR5688_B
}

struct SR5688_B {
var m: Int
var c: SR5688_C?
}

struct SR5688_C {
var d: Int
}

struct SR5688_S {
subscript(_ x: Int) -> String? { "" }
}

struct SR5688_O {
struct Nested {
var foo = ""
}
}

func SR5688_KP(_ kp: KeyPath<String?, Int>) {}

func testMemberAccessOnOptionalKeyPathComponent() {

_ = \SR5688_A.b.m
// expected-error@-1 {{value of optional type 'SR5688_B?' must be unwrapped to refer to member 'm' of wrapped base type 'SR5688_B'}}
// expected-note@-2 {{chain the optional using '?' to access member 'm' only for non-'nil' base values}} {{18-18=?}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{18-18=!}}

_ = \SR5688_A.b.c.d
// expected-error@-1 {{value of optional type 'SR5688_B?' must be unwrapped to refer to member 'c' of wrapped base type 'SR5688_B'}}
// expected-note@-2 {{chain the optional using '?' to access member 'c' only for non-'nil' base values}} {{18-18=?}}
// expected-error@-3 {{value of optional type 'SR5688_C?' must be unwrapped to refer to member 'd' of wrapped base type 'SR5688_C'}}
// expected-note@-4 {{chain the optional using '?' to access member 'd' only for non-'nil' base values}} {{20-20=?}}
// expected-note@-5 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{20-20=!}}
_ = \SR5688_A.b?.c.d
// expected-error@-1 {{value of optional type 'SR5688_C?' must be unwrapped to refer to member 'd' of wrapped base type 'SR5688_C'}}
// expected-note@-2 {{chain the optional using '?' to access member 'd' only for non-'nil' base values}} {{21-21=?}}

_ = \SR5688_AA.b.c.d
// expected-error@-1 {{value of optional type 'SR5688_C?' must be unwrapped to refer to member 'd' of wrapped base type 'SR5688_C'}}
// expected-note@-2 {{chain the optional using '?' to access member 'd' only for non-'nil' base values}} {{21-21=?}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{21-21=!}}

\String?.count
// expected-error@-1 {{value of optional type 'String?' must be unwrapped to refer to member 'count' of wrapped base type 'String'}}
// expected-note@-2 {{use unwrapped type 'String' as key path root}} {{4-11=String}}

\Optional<String>.count
// expected-error@-1 {{value of optional type 'Optional<String>' must be unwrapped to refer to member 'count' of wrapped base type 'String'}}
// expected-note@-2 {{use unwrapped type 'String' as key path root}} {{4-20=String}}

\SR5688_S.[5].count
// expected-error@-1 {{value of optional type 'String?' must be unwrapped to refer to member 'count' of wrapped base type 'String'}}
// expected-note@-2 {{chain the optional using '?' to access member 'count' only for non-'nil' base values}}{{16-16=?}}
// expected-note@-3 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}}{{16-16=!}}


\SR5688_O.Nested?.foo.count
// expected-error@-1 {{value of optional type 'SR5688_O.Nested?' must be unwrapped to refer to member 'foo' of wrapped base type 'SR5688_O.Nested'}}
// expected-note@-2 {{use unwrapped type 'SR5688_O.Nested' as key path root}}{{4-20=SR5688_O.Nested}}

\(Int, Int)?.0
// expected-error@-1 {{value of optional type '(Int, Int)?' must be unwrapped to refer to member '0' of wrapped base type '(Int, Int)'}}
// expected-note@-2 {{use unwrapped type '(Int, Int)' as key path root}}{{4-15=(Int, Int)}}

// TODO(diagnostics) Improve diagnostics refering to key path root not able to be infered as an optional type.
SR5688_KP(\.count)
// expected-error@-1 {{value of optional type 'String?' must be unwrapped to refer to member 'count' of wrapped base type 'String'}}
}

func testSyntaxErrors() { // expected-note{{}}
_ = \. ; // expected-error{{expected member name following '.'}}
_ = \.a ;
Expand Down