Skip to content

Commit 6b0070c

Browse files
authored
Merge pull request #26823 from theblixguy/fix/SR_10557
[Typechecker] @dynamicMemberLookup can't find subscript without arg label
2 parents b7acc6d + bb85ecb commit 6b0070c

File tree

6 files changed

+130
-48
lines changed

6 files changed

+130
-48
lines changed

include/swift/AST/Decl.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5158,6 +5158,7 @@ class VarDecl : public AbstractStorageDecl {
51585158
/// A function parameter declaration.
51595159
class ParamDecl : public VarDecl {
51605160
Identifier ArgumentName;
5161+
SourceLoc ParameterNameLoc;
51615162
SourceLoc ArgumentNameLoc;
51625163
SourceLoc SpecifierLoc;
51635164

@@ -5208,7 +5209,9 @@ class ParamDecl : public VarDecl {
52085209
/// The resulting source location will be valid if the argument name
52095210
/// was specified separately from the parameter name.
52105211
SourceLoc getArgumentNameLoc() const { return ArgumentNameLoc; }
5211-
5212+
5213+
SourceLoc getParameterNameLoc() const { return ParameterNameLoc; }
5214+
52125215
SourceLoc getSpecifierLoc() const { return SpecifierLoc; }
52135216

52145217
bool isTypeLocImplicit() const { return Bits.ParamDecl.IsTypeLocImplicit; }

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1121,7 +1121,10 @@ ERROR(missing_dynamic_callable_kwargs_method,none,
11211121
ERROR(invalid_dynamic_member_lookup_type,none,
11221122
"@dynamicMemberLookup attribute requires %0 to have a "
11231123
"'subscript(dynamicMember:)' method that accepts either "
1124-
"'ExpressibleByStringLiteral' or a keypath", (Type))
1124+
"'ExpressibleByStringLiteral' or a key path", (Type))
1125+
NOTE(invalid_dynamic_member_subscript, none,
1126+
"add an explicit argument label to this subscript to satisfy "
1127+
"the @dynamicMemberLookup requirement", ())
11251128

11261129
ERROR(string_index_not_integer,none,
11271130
"String must not be indexed with %0, it has variable size elements",

lib/AST/Decl.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5696,19 +5696,19 @@ void VarDecl::emitLetToVarNoteIfSimple(DeclContext *UseDC) const {
56965696
}
56975697
}
56985698

5699-
ParamDecl::ParamDecl(Specifier specifier,
5700-
SourceLoc specifierLoc, SourceLoc argumentNameLoc,
5701-
Identifier argumentName, SourceLoc parameterNameLoc,
5702-
Identifier parameterName, DeclContext *dc)
5703-
: VarDecl(DeclKind::Param,
5704-
/*IsStatic*/false,
5705-
specifier == ParamDecl::Specifier::Default
5706-
? VarDecl::Introducer::Let
5707-
: VarDecl::Introducer::Var,
5708-
/*IsCaptureList*/false, parameterNameLoc, parameterName, dc,
5709-
StorageIsMutable_t(!isImmutableSpecifier(specifier))),
5710-
ArgumentName(argumentName), ArgumentNameLoc(argumentNameLoc),
5711-
SpecifierLoc(specifierLoc) {
5699+
ParamDecl::ParamDecl(Specifier specifier, SourceLoc specifierLoc,
5700+
SourceLoc argumentNameLoc, Identifier argumentName,
5701+
SourceLoc parameterNameLoc, Identifier parameterName,
5702+
DeclContext *dc)
5703+
: VarDecl(DeclKind::Param,
5704+
/*IsStatic*/ false,
5705+
specifier == ParamDecl::Specifier::Default
5706+
? VarDecl::Introducer::Let
5707+
: VarDecl::Introducer::Var,
5708+
/*IsCaptureList*/ false, parameterNameLoc, parameterName, dc,
5709+
StorageIsMutable_t(!isImmutableSpecifier(specifier))),
5710+
ArgumentName(argumentName), ParameterNameLoc(parameterNameLoc),
5711+
ArgumentNameLoc(argumentNameLoc), SpecifierLoc(specifierLoc) {
57125712

57135713
Bits.ParamDecl.Specifier = static_cast<unsigned>(specifier);
57145714
Bits.ParamDecl.IsTypeLocImplicit = false;

lib/Sema/TypeCheckAttr.cpp

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,8 @@ visitDynamicCallableAttr(DynamicCallableAttr *attr) {
11441144
}
11451145

11461146
static bool hasSingleNonVariadicParam(SubscriptDecl *decl,
1147-
Identifier expectedLabel) {
1147+
Identifier expectedLabel,
1148+
bool ignoreLabel = false) {
11481149
auto *indices = decl->getIndices();
11491150
if (decl->isInvalid() || indices->size() != 1)
11501151
return false;
@@ -1153,6 +1154,10 @@ static bool hasSingleNonVariadicParam(SubscriptDecl *decl,
11531154
if (index->isVariadic() || !index->hasValidSignature())
11541155
return false;
11551156

1157+
if (ignoreLabel) {
1158+
return true;
1159+
}
1160+
11561161
return index->getArgumentName() == expectedLabel;
11571162
}
11581163

@@ -1161,22 +1166,23 @@ static bool hasSingleNonVariadicParam(SubscriptDecl *decl,
11611166
/// The method is given to be defined as `subscript(dynamicMember:)`.
11621167
bool swift::isValidDynamicMemberLookupSubscript(SubscriptDecl *decl,
11631168
DeclContext *DC,
1164-
TypeChecker &TC) {
1169+
TypeChecker &TC,
1170+
bool ignoreLabel) {
11651171
// It could be
11661172
// - `subscript(dynamicMember: {Writable}KeyPath<...>)`; or
11671173
// - `subscript(dynamicMember: String*)`
1168-
return isValidKeyPathDynamicMemberLookup(decl, TC) ||
1169-
isValidStringDynamicMemberLookup(decl, DC, TC);
1170-
1174+
return isValidKeyPathDynamicMemberLookup(decl, TC, ignoreLabel) ||
1175+
isValidStringDynamicMemberLookup(decl, DC, TC, ignoreLabel);
11711176
}
11721177

11731178
bool swift::isValidStringDynamicMemberLookup(SubscriptDecl *decl,
1174-
DeclContext *DC,
1175-
TypeChecker &TC) {
1179+
DeclContext *DC, TypeChecker &TC,
1180+
bool ignoreLabel) {
11761181
// There are two requirements:
11771182
// - The subscript method has exactly one, non-variadic parameter.
11781183
// - The parameter type conforms to `ExpressibleByStringLiteral`.
1179-
if (!hasSingleNonVariadicParam(decl, TC.Context.Id_dynamicMember))
1184+
if (!hasSingleNonVariadicParam(decl, TC.Context.Id_dynamicMember,
1185+
ignoreLabel))
11801186
return false;
11811187

11821188
const auto *param = decl->getIndices()->get(0);
@@ -1191,8 +1197,10 @@ bool swift::isValidStringDynamicMemberLookup(SubscriptDecl *decl,
11911197
}
11921198

11931199
bool swift::isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl,
1194-
TypeChecker &TC) {
1195-
if (!hasSingleNonVariadicParam(decl, TC.Context.Id_dynamicMember))
1200+
TypeChecker &TC,
1201+
bool ignoreLabel) {
1202+
if (!hasSingleNonVariadicParam(decl, TC.Context.Id_dynamicMember,
1203+
ignoreLabel))
11961204
return false;
11971205

11981206
const auto *param = decl->getIndices()->get(0);
@@ -1217,33 +1225,76 @@ visitDynamicMemberLookupAttr(DynamicMemberLookupAttr *attr) {
12171225
// This attribute is only allowed on nominal types.
12181226
auto decl = cast<NominalTypeDecl>(D);
12191227
auto type = decl->getDeclaredType();
1220-
1228+
auto &ctx = decl->getASTContext();
1229+
1230+
auto emitInvalidTypeDiagnostic = [&](const SourceLoc loc) {
1231+
TC.diagnose(loc, diag::invalid_dynamic_member_lookup_type, type);
1232+
attr->setInvalid();
1233+
};
1234+
12211235
// Look up `subscript(dynamicMember:)` candidates.
1222-
auto subscriptName = DeclName(TC.Context, DeclBaseName::createSubscript(),
1223-
TC.Context.Id_dynamicMember);
1236+
auto subscriptName =
1237+
DeclName(ctx, DeclBaseName::createSubscript(), ctx.Id_dynamicMember);
12241238
auto candidates = TC.lookupMember(decl, type, subscriptName);
1225-
1226-
// If there are no candidates, then the attribute is invalid.
1227-
if (candidates.empty()) {
1228-
TC.diagnose(attr->getLocation(), diag::invalid_dynamic_member_lookup_type,
1229-
type);
1230-
attr->setInvalid();
1239+
1240+
if (!candidates.empty()) {
1241+
// If no candidates are valid, then reject one.
1242+
auto oneCandidate = candidates.front().getValueDecl();
1243+
candidates.filter([&](LookupResultEntry entry, bool isOuter) -> bool {
1244+
auto cand = cast<SubscriptDecl>(entry.getValueDecl());
1245+
TC.validateDeclForNameLookup(cand);
1246+
return isValidDynamicMemberLookupSubscript(cand, decl, TC);
1247+
});
1248+
1249+
if (candidates.empty()) {
1250+
emitInvalidTypeDiagnostic(oneCandidate->getLoc());
1251+
}
1252+
12311253
return;
12321254
}
12331255

1234-
// If no candidates are valid, then reject one.
1235-
auto oneCandidate = candidates.front();
1236-
candidates.filter([&](LookupResultEntry entry, bool isOuter) -> bool {
1256+
// If we couldn't find any candidates, it's likely because:
1257+
//
1258+
// 1. We don't have a subscript with `dynamicMember` label.
1259+
// 2. We have a subscript with `dynamicMember` label, but no argument label.
1260+
//
1261+
// Let's do another lookup using just the base name.
1262+
auto newCandidates =
1263+
TC.lookupMember(decl, type, DeclBaseName::createSubscript());
1264+
1265+
// Validate the candidates while ignoring the label.
1266+
newCandidates.filter([&](const LookupResultEntry entry, bool isOuter) {
12371267
auto cand = cast<SubscriptDecl>(entry.getValueDecl());
12381268
TC.validateDeclForNameLookup(cand);
1239-
return isValidDynamicMemberLookupSubscript(cand, decl, TC);
1269+
return isValidDynamicMemberLookupSubscript(cand, decl, TC,
1270+
/*ignoreLabel*/ true);
12401271
});
12411272

1242-
if (candidates.empty()) {
1243-
TC.diagnose(oneCandidate.getValueDecl()->getLoc(),
1244-
diag::invalid_dynamic_member_lookup_type, type);
1245-
attr->setInvalid();
1273+
// If there were no potentially valid candidates, then throw an error.
1274+
if (newCandidates.empty()) {
1275+
emitInvalidTypeDiagnostic(attr->getLocation());
1276+
return;
1277+
}
1278+
1279+
// For each candidate, emit a diagnostic. If we don't have an explicit
1280+
// argument label, then emit a fix-it to suggest the user to add one.
1281+
for (auto cand : newCandidates) {
1282+
auto SD = cast<SubscriptDecl>(cand.getValueDecl());
1283+
auto index = SD->getIndices()->get(0);
1284+
TC.diagnose(SD, diag::invalid_dynamic_member_lookup_type, type);
1285+
1286+
// If we have something like `subscript(foo:)` then we want to insert
1287+
// `dynamicMember` before `foo`.
1288+
if (index->getParameterNameLoc().isValid() &&
1289+
index->getArgumentNameLoc().isInvalid()) {
1290+
TC.diagnose(SD, diag::invalid_dynamic_member_subscript)
1291+
.highlight(index->getSourceRange())
1292+
.fixItInsert(index->getParameterNameLoc(), "dynamicMember ");
1293+
}
12461294
}
1295+
1296+
attr->setInvalid();
1297+
return;
12471298
}
12481299

12491300
/// Get the innermost enclosing declaration for a declaration.

lib/Sema/TypeChecker.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,22 +2022,25 @@ bool isValidDynamicCallableMethod(FuncDecl *decl, DeclContext *DC,
20222022
/// the `subscript(dynamicMember:)` requirement for @dynamicMemberLookup.
20232023
/// The method is given to be defined as `subscript(dynamicMember:)`.
20242024
bool isValidDynamicMemberLookupSubscript(SubscriptDecl *decl, DeclContext *DC,
2025-
TypeChecker &TC);
2025+
TypeChecker &TC,
2026+
bool ignoreLabel = false);
20262027

20272028
/// Returns true if the given subscript method is an valid implementation of
20282029
/// the `subscript(dynamicMember:)` requirement for @dynamicMemberLookup.
20292030
/// The method is given to be defined as `subscript(dynamicMember:)` which
20302031
/// takes a single non-variadic parameter that conforms to
20312032
/// `ExpressibleByStringLiteral` protocol.
20322033
bool isValidStringDynamicMemberLookup(SubscriptDecl *decl, DeclContext *DC,
2033-
TypeChecker &TC);
2034+
TypeChecker &TC,
2035+
bool ignoreLabel = false);
20342036

20352037
/// Returns true if the given subscript method is an valid implementation of
20362038
/// the `subscript(dynamicMember: {Writable}KeyPath<...>)` requirement for
20372039
/// @dynamicMemberLookup.
20382040
/// The method is given to be defined as `subscript(dynamicMember:)` which
20392041
/// takes a single non-variadic parameter of `{Writable}KeyPath<T, U>` type.
2040-
bool isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl, TypeChecker &TC);
2042+
bool isValidKeyPathDynamicMemberLookup(SubscriptDecl *decl, TypeChecker &TC,
2043+
bool ignoreLabel = false);
20412044

20422045
/// Compute the wrapped value type for the given property that has attached
20432046
/// property wrappers, when the backing storage is known to have the given type.

test/attr/attr_dynamic_member_lookup.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func testIUOResult(x: IUOResult) {
140140
// Subscript index must be ExpressibleByStringLiteral.
141141
@dynamicMemberLookup
142142
struct Invalid1 {
143-
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid1' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a keypath}}
143+
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid1' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
144144
subscript(dynamicMember member: Int) -> Int {
145145
return 42
146146
}
@@ -149,7 +149,7 @@ struct Invalid1 {
149149
// Subscript may not be variadic.
150150
@dynamicMemberLookup
151151
struct Invalid2 {
152-
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid2' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a keypath}}
152+
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'Invalid2' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
153153
subscript(dynamicMember member: String...) -> Int {
154154
return 42
155155
}
@@ -188,7 +188,7 @@ func NotAllowedOnFunc() {}
188188
// @dynamicMemberLookup cannot be declared on a base class and fulfilled with a
189189
// derived class.
190190

191-
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'InvalidBase' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a keypath}}
191+
// expected-error @+1 {{@dynamicMemberLookup attribute requires 'InvalidBase' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
192192
@dynamicMemberLookup
193193
class InvalidBase {}
194194

@@ -722,3 +722,25 @@ struct SR10597_1_W<T> {
722722

723723
_ = SR10597_1_W<SR10597_1>(SR10597_1()).wooo // expected-error {{value of type 'SR10597_1_W<SR10597_1>' has no dynamic member 'wooo' using key path from root type 'SR10597_1'; did you mean 'woo'?}}
724724
_ = SR10597_1_W<SR10597_1>(SR10597_1()).bla // expected-error {{value of type 'SR10597_1_W<SR10597_1>' has no dynamic member 'bla' using key path from root type 'SR10597_1'}}
725+
726+
// SR-10557
727+
728+
@dynamicMemberLookup
729+
struct SR_10557_S {
730+
subscript(dynamicMember: String) -> String { // expected-error {{@dynamicMemberLookup attribute requires 'SR_10557_S' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
731+
// expected-note@-1 {{add an explicit argument label to this subscript to satisfy the @dynamicMemberLookup requirement}}{{13-13=dynamicMember }}
732+
fatalError()
733+
}
734+
}
735+
736+
@dynamicMemberLookup
737+
struct SR_10557_S1 {
738+
subscript(foo bar: String) -> String { // expected-error {{@dynamicMemberLookup attribute requires 'SR_10557_S1' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
739+
fatalError()
740+
}
741+
742+
subscript(foo: String) -> String { // expected-error {{@dynamicMemberLookup attribute requires 'SR_10557_S1' to have a 'subscript(dynamicMember:)' method that accepts either 'ExpressibleByStringLiteral' or a key path}}
743+
// expected-note@-1 {{add an explicit argument label to this subscript to satisfy the @dynamicMemberLookup requirement}} {{13-13=dynamicMember }}
744+
fatalError()
745+
}
746+
}

0 commit comments

Comments
 (0)