Skip to content

Commit bd4ee46

Browse files
authored
Merge pull request #70007 from xedin/keypath-application-improvements
[ConstraintSystem] Modernization of key path application handling
2 parents 126022f + cceef44 commit bd4ee46

14 files changed

+169
-12
lines changed

include/swift/AST/DiagnosticsSema.def

+4
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ ERROR(expr_swift_keypath_anyobject_root,none,
704704
"the root type of a Swift key path cannot be 'AnyObject'", ())
705705
ERROR(expr_keypath_multiparam_func_conversion, none,
706706
"cannot convert key path into a multi-argument function type %0", (Type))
707+
ERROR(cannot_convert_type_to_keypath_subscript_index,none,
708+
"cannot use value of type %0 as a key path subscript index; argument must be"
709+
" a key path",
710+
(Type))
707711
WARNING(expr_deprecated_writable_keypath,Deprecation,
708712
"forming a writable keypath to property %0 that is read-only in this context "
709713
"is deprecated and will be removed in a future release",

include/swift/Sema/CSFix.h

+28
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,10 @@ enum class FixKind : uint8_t {
463463
/// Ignore situations when provided number of generic arguments didn't match
464464
/// expected number of parameters.
465465
IgnoreGenericSpecializationArityMismatch,
466+
467+
/// Ignore situations when key path subscript index gets passed an invalid
468+
/// type as an argument (something that is not a key path).
469+
IgnoreKeyPathSubscriptIndexMismatch,
466470
};
467471

468472
class ConstraintFix {
@@ -3708,6 +3712,30 @@ class IgnoreGenericSpecializationArityMismatch final : public ConstraintFix {
37083712
}
37093713
};
37103714

3715+
class IgnoreKeyPathSubscriptIndexMismatch final : public ConstraintFix {
3716+
Type ArgType;
3717+
3718+
IgnoreKeyPathSubscriptIndexMismatch(ConstraintSystem &cs, Type argTy,
3719+
ConstraintLocator *locator)
3720+
: ConstraintFix(cs, FixKind::IgnoreKeyPathSubscriptIndexMismatch,
3721+
locator),
3722+
ArgType(argTy) {}
3723+
3724+
public:
3725+
std::string getName() const override {
3726+
return "ignore invalid key path subscript index";
3727+
}
3728+
3729+
bool diagnose(const Solution &solution, bool asNote = false) const override;
3730+
3731+
static IgnoreKeyPathSubscriptIndexMismatch *
3732+
create(ConstraintSystem &cs, Type argType, ConstraintLocator *locator);
3733+
3734+
static bool classof(const ConstraintFix *fix) {
3735+
return fix->getKind() == FixKind::IgnoreKeyPathSubscriptIndexMismatch;
3736+
}
3737+
};
3738+
37113739
} // end namespace constraints
37123740
} // end namespace swift
37133741

include/swift/Sema/ConstraintLocatorPathElts.def

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ SIMPLE_LOCATOR_PATH_ELT(KeyPathType)
131131
/// The value of a key path.
132132
SIMPLE_LOCATOR_PATH_ELT(KeyPathValue)
133133

134+
/// The index parameter of a special `subscript(keyPath: KeyPath<...>)`.
135+
SIMPLE_LOCATOR_PATH_ELT(KeyPathSubscriptIndex)
136+
134137
/// An implicit @lvalue-to-inout conversion; only valid for operator
135138
/// arguments.
136139
SIMPLE_LOCATOR_PATH_ELT(LValueConversion)

include/swift/Sema/ConstraintSystem.h

+4
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ class TypeVariableType::Implementation {
499499
/// expression.
500500
bool isKeyPathValue() const;
501501

502+
/// Determine whether this type variable represents an index parameter of
503+
/// a special `subscript(keyPath:)` subscript.
504+
bool isKeyPathSubscriptIndex() const;
505+
502506
/// Determine whether this type variable represents a subscript result type.
503507
bool isSubscriptResultType() const;
504508

lib/Sema/CSBindings.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,27 @@ PotentialBindings::inferFromRelational(Constraint *constraint) {
14271427
return llvm::None;
14281428
}
14291429

1430+
if (TypeVar->getImpl().isKeyPathSubscriptIndex()) {
1431+
// Key path subscript index can only be a r-value non-optional
1432+
// type that is a subtype of a known KeyPath type.
1433+
type = type->getRValueType()->lookThroughAllOptionalTypes();
1434+
1435+
// If argument to a key path subscript is an existential,
1436+
// we can erase it to superclass (if any) here and solver
1437+
// will perform the opening if supertype turns out to be
1438+
// a valid key path type of its subtype.
1439+
if (kind == AllowedBindingKind::Supertypes) {
1440+
if (type->isExistentialType()) {
1441+
auto layout = type->getExistentialLayout();
1442+
if (auto superclass = layout.explicitSuperclass) {
1443+
type = superclass;
1444+
} else if (!CS.shouldAttemptFixes()) {
1445+
return llvm::None;
1446+
}
1447+
}
1448+
}
1449+
}
1450+
14301451
if (auto *locator = TypeVar->getImpl().getLocator()) {
14311452
// Don't allow a protocol type to get propagated from the base to the result
14321453
// type of a chain, Result should always be a concrete type which conforms

lib/Sema/CSDiagnostics.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -9302,3 +9302,8 @@ bool InvalidTypeSpecializationArity::diagnoseAsError() {
93029302
/*generic=*/nullptr);
93039303
return true;
93049304
}
9305+
9306+
bool InvalidTypeAsKeyPathSubscriptIndex::diagnoseAsError() {
9307+
emitDiagnostic(diag::cannot_convert_type_to_keypath_subscript_index, ArgType);
9308+
return true;
9309+
}

lib/Sema/CSDiagnostics.h

+18
Original file line numberDiff line numberDiff line change
@@ -3128,6 +3128,24 @@ class InvalidTypeSpecializationArity final : public FailureDiagnostic {
31283128
bool diagnoseAsError() override;
31293129
};
31303130

3131+
/// Diagnose attempts to pass non-keypath as an argument to key path subscript:
3132+
///
3133+
/// \code
3134+
/// func test(data: Int) {
3135+
/// data[keyPath: 42] // error `42` is not a key path
3136+
/// }
3137+
/// \endcode
3138+
class InvalidTypeAsKeyPathSubscriptIndex final : public FailureDiagnostic {
3139+
Type ArgType;
3140+
3141+
public:
3142+
InvalidTypeAsKeyPathSubscriptIndex(const Solution &solution, Type argType,
3143+
ConstraintLocator *locator)
3144+
: FailureDiagnostic(solution, locator), ArgType(resolveType(argType)) {}
3145+
3146+
bool diagnoseAsError() override;
3147+
};
3148+
31313149
} // end namespace constraints
31323150
} // end namespace swift
31333151

lib/Sema/CSFix.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -2627,3 +2627,16 @@ IgnoreGenericSpecializationArityMismatch::create(ConstraintSystem &cs,
26272627
return new (cs.getAllocator()) IgnoreGenericSpecializationArityMismatch(
26282628
cs, decl, numParams, numArgs, hasParameterPack, locator);
26292629
}
2630+
2631+
bool IgnoreKeyPathSubscriptIndexMismatch::diagnose(const Solution &solution,
2632+
bool asNote) const {
2633+
InvalidTypeAsKeyPathSubscriptIndex failure(solution, ArgType, getLocator());
2634+
return failure.diagnose(asNote);
2635+
}
2636+
2637+
IgnoreKeyPathSubscriptIndexMismatch *
2638+
IgnoreKeyPathSubscriptIndexMismatch::create(ConstraintSystem &cs, Type argType,
2639+
ConstraintLocator *locator) {
2640+
return new (cs.getAllocator())
2641+
IgnoreKeyPathSubscriptIndexMismatch(cs, argType, locator);
2642+
}

lib/Sema/CSSimplify.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -12414,9 +12414,18 @@ ConstraintSystem::simplifyKeyPathApplicationConstraint(
1241412414
// Otherwise, we don't have a key path type at all.
1241512415
return SolutionKind::Error;
1241612416
}
12417-
if (!keyPathTy->isTypeVariableOrMember())
12417+
12418+
if (!keyPathTy->isTypeVariableOrMember()) {
12419+
if (shouldAttemptFixes()) {
12420+
auto *fix = IgnoreKeyPathSubscriptIndexMismatch::create(
12421+
*this, keyPathTy, getConstraintLocator(locator));
12422+
recordAnyTypeVarAsPotentialHole(valueTy);
12423+
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
12424+
}
12425+
1241812426
return SolutionKind::Error;
12419-
12427+
}
12428+
1242012429
return unsolved();
1242112430
}
1242212431

@@ -14886,7 +14895,8 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1488614895
case FixKind::AllowAssociatedValueMismatch:
1488714896
case FixKind::GenericArgumentsMismatch:
1488814897
case FixKind::AllowConcreteTypeSpecialization:
14889-
case FixKind::IgnoreGenericSpecializationArityMismatch: {
14898+
case FixKind::IgnoreGenericSpecializationArityMismatch:
14899+
case FixKind::IgnoreKeyPathSubscriptIndexMismatch: {
1489014900
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
1489114901
}
1489214902
case FixKind::IgnoreThrownErrorMismatch: {

lib/Sema/ConstraintLocator.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const {
111111
case ConstraintLocator::PackExpansionType:
112112
case ConstraintLocator::ThrownErrorType:
113113
case ConstraintLocator::FallbackType:
114+
case ConstraintLocator::KeyPathSubscriptIndex:
114115
return 0;
115116

116117
case ConstraintLocator::FunctionArgument:
@@ -528,6 +529,10 @@ void LocatorPathElt::dump(raw_ostream &out) const {
528529
case ConstraintLocator::FallbackType: {
529530
out << "fallback type";
530531
break;
532+
533+
case ConstraintLocator::KeyPathSubscriptIndex:
534+
out << "key path subscript index parameter";
535+
break;
531536
}
532537
}
533538
}

lib/Sema/ConstraintSystem.cpp

+7-9
Original file line numberDiff line numberDiff line change
@@ -3693,16 +3693,13 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
36933693
// Key path application looks like a subscript(keyPath: KeyPath<Base, T>).
36943694
// The element type is T or @lvalue T based on the key path subtype and
36953695
// the mutability of the base.
3696-
auto keyPathIndexTy = createTypeVariable(
3697-
getConstraintLocator(locator, ConstraintLocator::FunctionArgument),
3698-
TVO_CanBindToInOut);
3696+
auto *keyPathIndexLoc =
3697+
getConstraintLocator(locator, ConstraintLocator::KeyPathSubscriptIndex);
3698+
auto keyPathIndexTy = createTypeVariable(keyPathIndexLoc,
3699+
/*options=*/0);
36993700
auto elementTy = createTypeVariable(
3700-
getConstraintLocator(locator, ConstraintLocator::FunctionArgument),
3701-
TVO_CanBindToLValue | TVO_CanBindToNoEscape);
3702-
auto elementObjTy = createTypeVariable(
3703-
getConstraintLocator(locator, ConstraintLocator::FunctionArgument),
3704-
TVO_CanBindToNoEscape);
3705-
addConstraint(ConstraintKind::Equal, elementTy, elementObjTy, locator);
3701+
getConstraintLocator(keyPathIndexLoc, ConstraintLocator::KeyPathValue),
3702+
TVO_CanBindToLValue | TVO_CanBindToNoEscape);
37063703

37073704
// The element result is an lvalue or rvalue based on the key path class.
37083705
addKeyPathApplicationConstraint(
@@ -6104,6 +6101,7 @@ void constraints::simplifyLocator(ASTNode &anchor,
61046101
case ConstraintLocator::OptionalPayload:
61056102
case ConstraintLocator::ImplicitlyUnwrappedDisjunctionChoice:
61066103
case ConstraintLocator::FallbackType:
6104+
case ConstraintLocator::KeyPathSubscriptIndex:
61076105
break;
61086106
}
61096107

lib/Sema/TypeCheckConstraints.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ bool TypeVariableType::Implementation::isKeyPathValue() const {
152152
return locator && locator->isKeyPathValue();
153153
}
154154

155+
bool TypeVariableType::Implementation::isKeyPathSubscriptIndex() const {
156+
return locator &&
157+
locator->isLastElement<LocatorPathElt::KeyPathSubscriptIndex>();
158+
}
159+
155160
bool TypeVariableType::Implementation::isSubscriptResultType() const {
156161
if (!(locator && locator->getAnchor()))
157162
return false;

test/Constraints/keypath.swift

+39
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,42 @@ func rdar32101765() {
265265
let _: KeyPath<R32101765, Float> = \R32101765.prop32101765.unknown
266266
// expected-error@-1 {{type 'Int' has no member 'unknown'}}
267267
}
268+
269+
// https://github.com/apple/swift/issues/69795
270+
func test_invalid_argument_to_keypath_subscript() {
271+
func test(x: Int) {
272+
x[keyPath: 5]
273+
// expected-error@-1 {{cannot use value of type 'Int' as a key path subscript index; argument must be a key path}}
274+
}
275+
276+
let _: (Int) -> Void = {
277+
let y = $0
278+
y[keyPath: 5]
279+
// expected-error@-1 {{cannot use value of type 'Int' as a key path subscript index; argument must be a key path}}
280+
}
281+
282+
func ambiguous(_: (String) -> Void) {}
283+
func ambiguous(_: (Int) -> Void) {}
284+
285+
// FIXME(diagnostic): This is not properly diagnosed in a general case and key path application is even more
286+
// complicated because overloads anchored on 'SubscriptExpr -> subscript member' do not point to declarations.
287+
// The diagnostic should point out that `ambiguous` is indeed ambiguous and that `5` is not a valid argument
288+
// for a key path subscript.
289+
ambiguous {
290+
// expected-error@-1 {{type of expression is ambiguous without a type annotation}}
291+
$0[keyPath: 5]
292+
}
293+
294+
class A {
295+
}
296+
297+
func test_invalid_existential_protocol(base: String, v: any BinaryInteger) {
298+
base[keyPath: v]
299+
// expected-error@-1 {{cannot use value of type 'any BinaryInteger' as a key path subscript index; argument must be a key path}}
300+
}
301+
302+
func test_invalid_existential_composition(base: String, v: any A & BinaryInteger) {
303+
base[keyPath: v]
304+
// expected-error@-1 {{cannot use value of type 'A' as a key path subscript index; argument must be a key path}}
305+
}
306+
}

test/expr/unary/keypath/keypath.swift

+4
Original file line numberDiff line numberDiff line change
@@ -1233,3 +1233,7 @@ func test_keypath_coercion_to_function() {
12331233
let fn = \User.email as (User) -> String // Ok
12341234
_ = users.map(fn) // Ok
12351235
}
1236+
1237+
func test_keypath_application_with_composition(v: String, kp: any KeyPath<String, Int> & PP) {
1238+
_ = v[keyPath: kp] // Ok
1239+
}

0 commit comments

Comments
 (0)