Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,8 @@ ERROR(expr_keypath_generic_type,none,
"key path cannot refer to generic type %0", (Identifier))
ERROR(expr_keypath_noncopyable_type,none,
"key path cannot refer to noncopyable type %0", (Type))
ERROR(expr_keypath_nonescapable_type,none,
"key path cannot refer to nonescapable type %0", (Type))
ERROR(expr_keypath_not_property,none,
"%select{key path|dynamic key path member lookup}1 cannot refer to %kind0",
(const ValueDecl *, bool))
Expand Down
15 changes: 15 additions & 0 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -4186,6 +4186,21 @@ class ConstraintSystem {
bool resolveTapBody(TypeVariableType *typeVar, Type contextualType,
ConstraintLocatorBuilder locator);

/// Bind key path expression to the given contextual type and generate
/// constraints for its requirements.
///
/// \param typeVar The type variable representing the key path expression.
/// \param contextualType The contextual type this key path expression
/// would be bound to.
/// \param flags The flags associated with this assignment.
/// \param locator The locator associated with contextual type.
///
/// \returns `true` if it was possible to generate constraints for
/// the requirements and assign fixed type to the key path expression,
/// `false` otherwise.
bool resolveKeyPath(TypeVariableType *typeVar, Type contextualType,
TypeMatchOptions flags, ConstraintLocatorBuilder locator);

/// Assign a fixed type to the given type variable.
///
/// \param typeVar The type variable to bind.
Expand Down
14 changes: 14 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,20 @@ bool MissingConformanceFailure::diagnoseAsError() {
}
}

if (isExpr<KeyPathExpr>(anchor)) {
if (auto *P = dyn_cast<ProtocolDecl>(protocolType->getAnyNominal())) {
if (P->isSpecificProtocol(KnownProtocolKind::Copyable)) {
emitDiagnostic(diag::expr_keypath_noncopyable_type, nonConformingType);
return true;
}

if (P->isSpecificProtocol(KnownProtocolKind::Escapable)) {
emitDiagnostic(diag::expr_keypath_nonescapable_type, nonConformingType);
return true;
}
}
}

if (diagnoseAsAmbiguousOperatorRef())
return true;

Expand Down
30 changes: 30 additions & 0 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4675,6 +4675,12 @@ ConstraintSystem::matchTypesBindTypeVar(
: getTypeMatchFailure(locator);
}

if (typeVar->getImpl().isKeyPathType()) {
return resolveKeyPath(typeVar, type, flags, locator)
Copy link
Contributor

Choose a reason for hiding this comment

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

Doing this in matchTypesBindTypeVar() seems wrong. Why can't you generate these constraints when the type variable is created?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type is created by inference, it's not possible to generate constraints then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The key paths are special in a way that we need to infer capability from the members in the chain before we can form the type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To elaborate more, when type variable is created there is no type for it. The type we get here is purely synthetic, formed by inference based on capabilities inferred from members referenced in a key path, which means that the type wasn't previously "opened" like in other places and has to be opened before it's assigned. I agree that this is unfortunate and we'd need a better way to express this but I don't see how we can do that without significant changes to inference.

? getTypeMatchSuccess()
: getTypeMatchFailure(locator);
}

assignFixedType(typeVar, type, /*updateState=*/true,
/*notifyInference=*/!flags.contains(TMF_BindingTypeVariable));

Expand Down Expand Up @@ -12257,6 +12263,30 @@ bool ConstraintSystem::resolveTapBody(TypeVariableType *typeVar,
return !generateConstraints(tapExpr);
}

bool ConstraintSystem::resolveKeyPath(TypeVariableType *typeVar,
Type contextualType,
TypeMatchOptions flags,
ConstraintLocatorBuilder locator) {
// Key path types recently gained Copyable, Escapable requirements.
// The solver cannot account for that during inference because Root
// and Value types are required to be only resolved enough to infer
// a capability of a key path itself.
if (auto *BGT = contextualType->getAs<BoundGenericType>()) {
auto keyPathTy = openUnboundGenericType(
BGT->getDecl(), BGT->getParent(), locator, /*isTypeResolution=*/false);

assignFixedType(
typeVar, keyPathTy, /*updateState=*/true,
/*notifyInference=*/!flags.contains(TMF_BindingTypeVariable));
addConstraint(ConstraintKind::Equal, keyPathTy, contextualType, locator);
return true;
}

assignFixedType(typeVar, contextualType, /*updateState=*/true,
/*notifyInference=*/!flags.contains(TMF_BindingTypeVariable));
return true;
}

ConstraintSystem::SolutionKind
ConstraintSystem::simplifyDynamicTypeOfConstraint(
Type type1, Type type2,
Expand Down
21 changes: 21 additions & 0 deletions test/Constraints/keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,24 @@ extension Collection {
func keypathToFunctionWithOptional() {
_ = Array("").prefix(1...4, while: \.isNumber) // Ok
}

func test_new_key_path_type_requirements() {
struct V: ~Copyable {
}

struct S: ~Copyable {
var x: Int
var v: V
}

_ = \S.x // expected-error {{key path cannot refer to noncopyable type 'S'}}
_ = \S.v
// expected-error@-1 {{key path cannot refer to noncopyable type 'S'}}
// expected-error@-2 {{key path cannot refer to noncopyable type 'V'}}

func test<R>(_: KeyPath<R, Int>) {} // expected-note {{'where R: Copyable' is implicit here}}

test(\S.x)
// expected-error@-1 {{key path cannot refer to noncopyable type 'S'}}
// expected-error@-2 {{local function 'test' requires that 'S' conform to 'Copyable'}}
}
15 changes: 11 additions & 4 deletions test/Sema/keypaths_noncopyable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,18 @@ struct C {

// rdar://109287447
public func testKeypath<V: ~Copyable>(m: consuming M<V>) {
_ = m[keyPath: \.nc] // expected-error {{key path cannot refer to noncopyable type 'NC'}}
_ = m[keyPath: \.nc.data] // expected-error {{key path cannot refer to noncopyable type 'NC'}}
_ = m[keyPath: \.ncg] // expected-error {{key path cannot refer to noncopyable type 'V'}}
_ = m[keyPath: \.ncg.protocolProp] // expected-error {{key path cannot refer to noncopyable type 'V'}}
_ = m[keyPath: \.nc]
// expected-error@-1 {{key path cannot refer to noncopyable type 'M<V>'}}
// expected-error@-2 {{key path cannot refer to noncopyable type 'NC'}}
_ = m[keyPath: \.nc.data]
// expected-error@-1 {{key path cannot refer to noncopyable type 'M<V>'}}
_ = m[keyPath: \.ncg]
// expected-error@-1 {{key path cannot refer to noncopyable type 'M<V>'}}
// expected-error@-2 {{key path cannot refer to noncopyable type 'V'}}
_ = m[keyPath: \.ncg.protocolProp]
// expected-error@-1 {{key path cannot refer to noncopyable type 'M<V>'}}
_ = m[keyPath: \.string]
// expected-error@-1 {{key path cannot refer to noncopyable type 'M<V>'}}

let b = Box(NC())
_ = b.with(\.data)
Expand Down
15 changes: 15 additions & 0 deletions test/Sema/lifetime_attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,18 @@ func invalidTarget(_ result: inout NE, _ source: consuming NE) { // expected-err
func immortalConflict(_ immortal: Int) -> NE { // expected-error{{conflict between the parameter name and 'immortal' contextual keyword}}
NE()
}

do {
struct Test: ~Escapable {
var v1: Int
var v2: NE
}

_ = \Test.v1 // expected-error {{key path cannot refer to nonescapable type 'Test'}}
_ = \Test.v2 // expected-error {{key path cannot refer to nonescapable type 'Test'}} expected-error {{key path cannot refer to nonescapable type 'NE'}}

func use(t: Test) {
t[keyPath: \.v1] // expected-error {{key path cannot refer to nonescapable type 'Test'}}
t[keyPath: \.v2] // expected-error {{key path cannot refer to nonescapable type 'Test'}} expected-error {{key path cannot refer to nonescapable type 'NE'}}
}
}
2 changes: 2 additions & 0 deletions test/expr/unary/keypath/keypath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,9 @@ func testKeyPathHole() {

func f(_ i: Int) {}
f(\.x) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}} {{6-6=<#Root#>}}
// expected-error@-1 {{cannot convert value of type 'KeyPath<Root, Value>' to expected argument type 'Int'}}
f(\.x.y) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}} {{6-6=<#Root#>}}
// expected-error@-1 {{cannot convert value of type 'KeyPath<Root, Value>' to expected argument type 'Int'}}

func provideValueButNotRoot<T>(_ fn: (T) -> String) {} // expected-note 2 {{in call to function 'provideValueButNotRoot'}}
provideValueButNotRoot(\.x) // expected-error {{cannot infer key path type from context; consider explicitly specifying a root type}}
Expand Down