Skip to content

[ConstraintSystem] A few improvements to key path handling and diagnostics #69031

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 8 commits into from
Oct 10, 2023
34 changes: 0 additions & 34 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -393,10 +393,6 @@ enum class FixKind : uint8_t {
/// is marked as `inout`.
AllowConversionThroughInOut,

/// Ignore either capability (read/write) or type mismatch in conversion
/// between two key path types.
IgnoreKeyPathContextualMismatch,

/// Ignore a type mismatch between deduced element type and externally
/// imposed one.
IgnoreCollectionElementContextualMismatch,
Expand Down Expand Up @@ -1167,36 +1163,6 @@ class GenericArgumentsMismatch final
}
};

/// Detect situations where key path doesn't have capability required
/// by the context e.g. read-only vs. writable, or either root or value
/// types are incorrect e.g.
///
/// ```swift
/// struct S { let foo: Int }
/// let _: WritableKeyPath<S, Int> = \.foo
/// ```
///
/// Here context requires a writable key path but `foo` property is
/// read-only.
class KeyPathContextualMismatch final : public ContextualMismatch {
KeyPathContextualMismatch(ConstraintSystem &cs, Type lhs, Type rhs,
ConstraintLocator *locator)
: ContextualMismatch(cs, FixKind::IgnoreKeyPathContextualMismatch, lhs,
rhs, locator) {}

public:
std::string getName() const override {
return "fix key path contextual mismatch";
}

static KeyPathContextualMismatch *
create(ConstraintSystem &cs, Type lhs, Type rhs, ConstraintLocator *locator);

static bool classof(const ConstraintFix *fix) {
return fix->getKind() == FixKind::IgnoreKeyPathContextualMismatch;
}
};

/// Detect situations when argument of the @autoclosure parameter is itself
/// marked as @autoclosure and is not applied. Form a fix which suggests a
/// proper way to forward such arguments, e.g.:
Expand Down
11 changes: 9 additions & 2 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2060,8 +2060,15 @@ bool TypeVarBindingProducer::computeNext() {

for (auto supertype : enumerateDirectSupertypes(type)) {
// If we're not allowed to try this binding, skip it.
if (auto simplifiedSuper = checkTypeOfBinding(TypeVar, supertype))
addNewBinding(binding.withType(*simplifiedSuper));
if (auto simplifiedSuper = checkTypeOfBinding(TypeVar, supertype)) {
auto supertype = *simplifiedSuper;
// A key path type cannot be bound to type-erased key path variants.
if (TypeVar->getImpl().isKeyPathType() &&
(supertype->isPartialKeyPath() || supertype->isAnyKeyPath()))
continue;

addNewBinding(binding.withType(supertype));
}
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1493,10 +1493,10 @@ bool MemberAccessOnOptionalBaseFailure::diagnoseAsError() {
} else {
emitDiagnostic(diag::invalid_optional_inferred_keypath_root, baseType,
Member, unwrappedBaseType);
emitDiagnostic(diag::optional_key_path_root_base_chain, Member)
.fixItInsert(sourceRange.End, "?.");
emitDiagnostic(diag::optional_key_path_root_base_unwrap, Member)
.fixItInsert(sourceRange.End, "!.");
// Note that unwrapping fix-its cannot be suggested in this case
// because neither `.?` nor `.!` can be used to start a key path
// literal. Suggesting an explicit type here won't work either
// because contextual root is going to be optional still.
}
} else {
// Check whether or not the base of this optional unwrap is implicit self
Expand Down
7 changes: 0 additions & 7 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1296,13 +1296,6 @@ AllowInvalidRefInKeyPath::create(ConstraintSystem &cs, RefKind kind,
AllowInvalidRefInKeyPath(cs, kind, member, locator);
}

KeyPathContextualMismatch *
KeyPathContextualMismatch::create(ConstraintSystem &cs, Type lhs, Type rhs,
ConstraintLocator *locator) {
return new (cs.getAllocator())
KeyPathContextualMismatch(cs, lhs, rhs, locator);
}

bool RemoveAddressOf::diagnose(const Solution &solution, bool asNote) const {
InvalidUseOfAddressOf failure(solution, getFromType(), getToType(),
getLocator());
Expand Down
121 changes: 95 additions & 26 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5958,14 +5958,6 @@ bool ConstraintSystem::repairFailures(
if (repairViaBridgingCast(*this, lhs, rhs, conversionsOrFixes, locator))
break;

// If both types are key path, the only differences
// between them are mutability and/or root, value type mismatch.
if (isKnownKeyPathType(lhs) && isKnownKeyPathType(rhs)) {
auto *fix = KeyPathContextualMismatch::create(
*this, lhs, rhs, getConstraintLocator(locator));
conversionsOrFixes.push_back(fix);
}

if (lhs->is<FunctionType>() && !rhs->is<AnyFunctionType>() &&
isExpr<ClosureExpr>(anchor)) {
auto *fix = ContextualMismatch::create(*this, lhs, rhs,
Expand Down Expand Up @@ -6608,8 +6600,20 @@ bool ConstraintSystem::repairFailures(
if (hasFixFor(lastCompLoc, FixKind::AllowTypeOrInstanceMember))
return true;

auto *keyPathLoc = getConstraintLocator(anchor);

if (hasFixFor(keyPathLoc))
return true;

if (auto contextualInfo = getContextualTypeInfo(anchor)) {
if (hasFixFor(getConstraintLocator(
keyPathLoc,
LocatorPathElt::ContextualType(contextualInfo->purpose))))
return true;
}

conversionsOrFixes.push_back(IgnoreContextualType::create(
*this, lhs, rhs, getConstraintLocator(anchor)));
*this, lhs, rhs, keyPathLoc));
break;
}
default:
Expand Down Expand Up @@ -10841,6 +10845,12 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
if (result.ViableCandidates.empty() && result.UnviableCandidates.empty())
return fixMissingMember(origBaseTy, memberTy, locator);

bool baseIsKeyPathRootType = [&]() {
auto keyPathComponent =
locator->getLastElementAs<LocatorPathElt::KeyPathComponent>();
return keyPathComponent && keyPathComponent->getIndex() == 0;
}();

// The result of the member access can either be the expected member type
// (for '!' or optional members with '?'), or the original member type
// with one extra level of optionality ('?' with non-optional members).
Expand All @@ -10854,13 +10864,17 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
*this, ConstraintKind::Bind,
UnwrapOptionalBase::create(*this, member, baseObjTy, locator),
memberTy, innerTV, locator);
auto optionalResult = Constraint::createFixed(
*this, ConstraintKind::Bind,
UnwrapOptionalBase::createWithOptionalResult(*this, member,
baseObjTy, locator),
optTy, memberTy, locator);
optionalities.push_back(nonoptionalResult);
optionalities.push_back(optionalResult);

if (!baseIsKeyPathRootType) {
auto optionalResult = Constraint::createFixed(
*this, ConstraintKind::Bind,
UnwrapOptionalBase::createWithOptionalResult(*this, member,
baseObjTy, locator),
optTy, memberTy, locator);
optionalities.push_back(optionalResult);
}

addDisjunctionConstraint(optionalities, locator);

// Look through one level of optional.
Expand Down Expand Up @@ -11636,6 +11650,13 @@ bool ConstraintSystem::resolveKeyPath(TypeVariableType *typeVar,
contextualType = BoundGenericType::get(
keyPathTy->getDecl(), keyPathTy->getParent(), {root, value});
}
} else if (contextualType->isPlaceholder()) {
auto root = simplifyType(getKeyPathRootType(keyPath));
if (!(root->isTypeVariableOrMember() || root->isPlaceholder())) {
auto value = getKeyPathValueType(keyPath);
contextualType =
BoundGenericType::get(ctx.getKeyPathDecl(), Type(), {root, value});
}
}
}

Expand Down Expand Up @@ -12150,12 +12171,14 @@ ConstraintSystem::simplifyKeyPathConstraint(
}

if (boundRoot &&
matchTypes(boundRoot, rootTy, ConstraintKind::Bind, subflags, locator)
matchTypes(rootTy, boundRoot, ConstraintKind::Bind, subflags,
locator.withPathElement(ConstraintLocator::KeyPathRoot))
.isFailure())
return false;

if (boundValue &&
matchTypes(boundValue, valueTy, ConstraintKind::Bind, subflags, locator)
matchTypes(valueTy, boundValue, ConstraintKind::Bind, subflags,
locator.withPathElement(ConstraintLocator::KeyPathValue))
.isFailure())
return false;

Expand Down Expand Up @@ -12206,6 +12229,16 @@ ConstraintSystem::simplifyKeyPathConstraint(
tv->getImpl().canBindToHole();
})) {
(void)tryMatchRootAndValueFromType(keyPathTy);

// If the type of the key path is not yet resolved simplifying this
// constraint would disconnect it from root and value, let's bind it
// to a placeholder type to make sure this doesn't happen.
if (auto *typeVar = keyPathTy->getAs<TypeVariableType>()) {
return matchTypes(keyPathTy, PlaceholderType::get(Context, typeVar),
ConstraintKind::Bind, subflags,
locator.getBaseLocator());
}

return SolutionKind::Solved;
}
}
Expand Down Expand Up @@ -12364,21 +12397,58 @@ ConstraintSystem::simplifyKeyPathConstraint(
kpDecl = getASTContext().getWritableKeyPathDecl();
}

auto formUnsolved = [&]() {
addUnsolvedConstraint(Constraint::create(
*this, ConstraintKind::KeyPath, keyPathTy, rootTy, valueTy,
locator.getBaseLocator(), componentTypeVars));
};

auto loc = locator.getBaseLocator();
if (definitelyFunctionType) {
increaseScore(SK_FunctionConversion, locator);
return SolutionKind::Solved;
} else if (!anyComponentsUnresolved ||
(definitelyKeyPathType && capability == ReadOnly)) {
auto resolvedKPTy =
BoundGenericType::get(kpDecl, nullptr, {rootTy, valueTy});
return matchTypes(resolvedKPTy, keyPathTy, ConstraintKind::Bind, subflags,
loc);
// If key path is connected to a disjunction (i.e. through coercion
// or used as an argument to a function/subscipt call) it cannot be
// bound until the choice is selected because it's undetermined
// until then whether key path is implicitly converted to a function
// type or not.
//
// \code
// struct Data {
// var value: Int = 42
// }
//
// func test<S: Sequence>(_: S, _: (S.Element) -> Int) {}
// func test<C: Collection>(_: C, _: (C.Element) -> Int) {}
//
// func test(arr: [Data]) {
// test(arr, \Data.value)
// }
// \endcode
//
// In this example if we did allow to bind the key path before
// an overload of `test` is selected, we'd end up with no solutions
// because the type of the key path expression is actually: '(Data) -> Int'
// and not 'WritableKeyPath<Data, Int>`.
auto *typeVar = keyPathTy->getAs<TypeVariableType>();
if (typeVar && findConstraintThroughOptionals(
typeVar, OptionalWrappingDirection::Unwrap,
[&](Constraint *match, TypeVariableType *) {
return match->getKind() ==
ConstraintKind::ApplicableFunction ||
match->getKind() == ConstraintKind::Disjunction;
Comment on lines +12439 to +12441
Copy link
Member

Choose a reason for hiding this comment

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

If we're just looking for function/disjunction constraints, how does this handle the let fn = \User.email as (User) -> String?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Coercions form disjunctions

})) {
formUnsolved();
} else {
auto resolvedKPTy =
BoundGenericType::get(kpDecl, nullptr, {rootTy, valueTy});
return matchTypes(resolvedKPTy, keyPathTy, ConstraintKind::Bind,
subflags, loc);
Comment on lines +12445 to +12448
Copy link
Member

Choose a reason for hiding this comment

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

In the little digging I did on this issue, it struck me that it seems like in this path we're basically using the simplification step to infer a KeyPath-type binding for the literal (by simplifying to a Bind constraint). Do you know why we wouldn't want to use the usual PotentialBindings::infer machinery to do that? It seems like we could then use BindingSet::favoredOverDisjunction to ensure we don't attempt the binding until we've resolved disjunctions rather than searching for the constraints here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We cannot use binding inference for this because constraint gets re-activated when adjacent types are bound.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I put a branch into resolveKeyPath to handle contextual placeholder but here I think we actually want to have it bound to propagate “unknown type” information out.

}
} else {
addUnsolvedConstraint(Constraint::create(*this, ConstraintKind::KeyPath,
keyPathTy, rootTy, valueTy,
locator.getBaseLocator(),
componentTypeVars));
formUnsolved();
}
return SolutionKind::Solved;
}
Expand Down Expand Up @@ -14987,7 +15057,6 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::RemoveExtraneousArguments:
case FixKind::SpecifyTypeForPlaceholder:
case FixKind::AllowAutoClosurePointerConversion:
case FixKind::IgnoreKeyPathContextualMismatch:
case FixKind::NotCompileTimeConst:
case FixKind::RenameConflictingPatternVariables:
case FixKind::MustBeCopyable:
Expand Down
6 changes: 4 additions & 2 deletions test/Constraints/keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,11 @@ func rdar32101765() {
}

let _: KeyPath<R32101765, Float> = \.prop32101765
// expected-error@-1 {{key path value type 'Int' cannot be converted to contextual type 'Float'}}
// expected-error@-1 {{cannot assign value of type 'KeyPath<R32101765, Int>' to type 'KeyPath<R32101765, Float>'}}
// expected-note@-2 {{arguments to generic parameter 'Value' ('Int' and 'Float') are expected to be equal}}
let _: KeyPath<R32101765, Float> = \R32101765.prop32101765
// expected-error@-1 {{key path value type 'Int' cannot be converted to contextual type 'Float'}}
// expected-error@-1 {{cannot assign value of type 'KeyPath<R32101765, Int>' to type 'KeyPath<R32101765, Float>'}}
// expected-note@-2 {{arguments to generic parameter 'Value' ('Int' and 'Float') are expected to be equal}}
let _: KeyPath<R32101765, Float> = \.prop32101765.unknown
// expected-error@-1 {{type 'Int' has no member 'unknown'}}
let _: KeyPath<R32101765, Float> = \R32101765.prop32101765.unknown
Expand Down
42 changes: 32 additions & 10 deletions test/expr/unary/keypath/keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -794,9 +794,11 @@ func test_keypath_with_method_refs() {
}

let _: KeyPath<S, Int> = \.foo // expected-error {{key path cannot refer to instance method 'foo()'}}
// expected-error@-1 {{key path value type '() -> Int' cannot be converted to contextual type 'Int'}}
// expected-error@-1 {{cannot assign value of type 'KeyPath<S, () -> Int>' to type 'KeyPath<S, Int>'}}
// expected-note@-2 {{arguments to generic parameter 'Value' ('() -> Int' and 'Int') are expected to be equal}}
let _: KeyPath<S, Int> = \.bar // expected-error {{key path cannot refer to static method 'bar()'}}
// expected-error@-1 {{key path value type '() -> Int' cannot be converted to contextual type 'Int'}}
// expected-error@-1 {{cannot assign value of type 'KeyPath<S, () -> Int>' to type 'KeyPath<S, Int>'}}
// expected-note@-2 {{arguments to generic parameter 'Value' ('() -> Int' and 'Int') are expected to be equal}}
let _ = \S.Type.bar // expected-error {{key path cannot refer to static method 'bar()'}}

struct A {
Expand Down Expand Up @@ -1008,16 +1010,10 @@ func testMemberAccessOnOptionalKeyPathComponent() {
func kp(_: KeyPath<String?, Int>) {}

kp(\.count) // expected-error {{key path root inferred as optional type 'String?' must be unwrapped to refer to member 'count' of unwrapped type 'String'}}
// expected-note@-1 {{chain the optional using '?.' to access unwrapped type member 'count'}} {{8-8=?.}}
// expected-note@-2 {{unwrap the optional using '!.' to access unwrapped type member 'count'}} {{8-8=!.}}
let _ : KeyPath<String?, Int> = \.count // expected-error {{key path root inferred as optional type 'String?' must be unwrapped to refer to member 'count' of unwrapped type 'String'}}
// expected-note@-1 {{chain the optional using '?.' to access unwrapped type member 'count'}} {{37-37=?.}}
// expected-note@-2 {{unwrap the optional using '!.' to access unwrapped type member 'count'}} {{37-37=!.}}

let _ : KeyPath<String?, Int> = \.utf8.count
// expected-error@-1 {{key path root inferred as optional type 'String?' must be unwrapped to refer to member 'utf8' of unwrapped type 'String'}}
// expected-note@-2 {{chain the optional using '?.' to access unwrapped type member 'utf8'}} {{37-37=?.}}
// expected-note@-3 {{unwrap the optional using '!.' to access unwrapped type member 'utf8'}} {{37-37=!.}}
}

func testSyntaxErrors() {
Expand Down Expand Up @@ -1067,8 +1063,9 @@ func f_56996() {
// https://github.com/apple/swift/issues/55805
// Key-path missing optional crashes compiler: Inactive constraints left over?
func f_55805() {
let _: KeyPath<String?, Int?> = \.utf8.count // expected-error {{value of optional type 'String.UTF8View?' must be unwrapped to refer to member 'count' of wrapped base type 'String.UTF8View'}}
// expected-note@-1 {{chain the optional using '?' to access member 'count' only for non-'nil' base values}}
let _: KeyPath<String?, Int?> = \.utf8.count
// expected-error@-1 {{key path root inferred as optional type 'String?' must be unwrapped to refer to member 'utf8' of unwrapped type 'String'}}
// expected-error@-2 {{key path value type 'Int' cannot be converted to contextual type 'Int?'}}
}

// rdar://74711236 - crash due to incorrect member access in key path
Expand Down Expand Up @@ -1202,3 +1199,28 @@ func test_keypath_inference_from_existentials() {
test(\.other, 42) // Ok
test(\.member, "") // Ok
}

// rdar://116376651 - key path type is bound before context is fully resolved.
func keypath_to_func_conversion_as_arg_to_overloaded_func() {
struct Data {
var value: Int = 42
}

func test<S: Sequence>(_: S, _: (S.Element) -> Int) {}
func test<C: Collection>(_: C, _: (C.Element) -> Int) {}

func test(arr: [Data]) {
test(arr, \Data.value) // Ok
}
}

// https://github.com/apple/swift/issues/55436
func test_keypath_coercion_to_function() {
struct User {
let email: String
}

let users = [User]()
let fn = \User.email as (User) -> String // Ok
_ = users.map(fn) // Ok
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
struct P<T: K> { }

struct S {
init<B>(_ a: P<B>) { // expected-note {{in call to initializer}}
init<B>(_ a: P<B>) { // expected-note {{where 'B' = 'String'}}
fatalError()
}
}
Expand All @@ -27,9 +27,8 @@ struct A {
}

extension A: K {
static let j = S(\A.id + "id") // expected-error {{generic parameter 'B' could not be inferred}}
// expected-error@-1 {{binary operator '+' cannot be applied to operands of type 'KeyPath<A, String>' and 'String'}}
// expected-note@-2 {{overloads for '+' exist with these partially matching parameter lists: (String, String)}}
static let j = S(\A.id + "id")
// expected-error@-1 {{initializer 'init(_:)' requires that 'String' conform to 'K'}}
}

// https://github.com/apple/swift/issues/47610
Expand Down