Skip to content

Commit 7139152

Browse files
AnthonyLatsisxedin
authored andcommitted
[Diagnostics|SR-5789] Fixits for applying subscripts by keyword (#16431)
* [Diagnostics|SR-5789] Added fixits for referring to subscripts by keyword If there is a subscript member, the error message changes to [type -bash has no member property or method named 'subscript'] The fix-it replaces parentheses with brackets, removes '.subscript' If the apply expression is incomplete, e.g. subscript(..., the fix-it adds a bracket at the end. * tests updated & logic for compatible arg types (except generics) * ignore generic types & switch to returning * avoid explicitly using sting literals * handle implicit conversion of tuples & encapsulate it * isolate subscript misusage * return bool instead of void * move function to FailureDiagnosis, diagnose independently & update error message * Update CSDiag.cpp
1 parent 9d6ff6d commit 7139152

File tree

4 files changed

+215
-12
lines changed

4 files changed

+215
-12
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ ERROR(could_not_find_type_member_corrected,none,
7575
"type %0 has no member %1; did you mean %2?",
7676
(Type, DeclName, DeclName))
7777

78+
ERROR(could_not_find_subscript_member_did_you_mean,none,
79+
"value of type %0 has no property or method named 'subscript'; "
80+
"did you mean to use the subscript operator?",
81+
(Type))
82+
NOTE(subscript_declared_here,none,
83+
"declared here", ())
84+
7885
ERROR(could_not_find_enum_case,none,
7986
"enum type %0 has no case %1; did you mean %2", (Type, DeclName, DeclName))
8087

lib/Sema/CSDiag.cpp

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,10 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
10021002

10031003
bool diagnoseSubscriptErrors(SubscriptExpr *SE, bool performingSet);
10041004

1005+
/// Diagnose the usage of 'subscript' instead of the operator when calling
1006+
/// a subscript and offer a fixit if the inputs are compatible.
1007+
bool diagnoseSubscriptMisuse(ApplyExpr *callExpr);
1008+
10051009
bool visitExpr(Expr *E);
10061010
bool visitIdentityExpr(IdentityExpr *E);
10071011
bool visitTryExpr(TryExpr *E);
@@ -5434,6 +5438,141 @@ bool FailureDiagnosis::diagnoseCallContextualConversionErrors(
54345438
return false;
54355439
}
54365440

5441+
bool FailureDiagnosis::diagnoseSubscriptMisuse(ApplyExpr *callExpr) {
5442+
auto UDE = dyn_cast<UnresolvedDotExpr>(callExpr->getFn());
5443+
if (!UDE)
5444+
return false;
5445+
5446+
auto baseExpr = UDE->getBase();
5447+
if (!baseExpr || UDE->getName().getBaseName() != getTokenText(tok::kw_subscript))
5448+
return false;
5449+
5450+
auto baseType = CS.getType(baseExpr);
5451+
// Look up subscript declarations.
5452+
auto lookup = CS.lookupMember(baseType->getRValueType(),
5453+
DeclName(DeclBaseName::createSubscript()));
5454+
auto nonSubscrLookup = CS.lookupMember(baseType->getRValueType(),
5455+
UDE->getName());
5456+
// Make sure we only found subscript declarations. If not, the problem
5457+
// is different - return.
5458+
if (lookup.empty() || !nonSubscrLookup.empty())
5459+
return false;
5460+
// Try to resolve a type of the argument expression.
5461+
auto argExpr = typeCheckChildIndependently(callExpr->getArg(),
5462+
Type(), CTP_CallArgument);
5463+
if (!argExpr)
5464+
return CS.TC.Diags.hadAnyError();
5465+
5466+
SmallVector<Identifier, 2> scratch;
5467+
ArrayRef<Identifier> argLabels = callExpr->getArgumentLabels(scratch);
5468+
SmallVector<OverloadChoice, 2> choices;
5469+
5470+
for (auto candidate : lookup)
5471+
choices.push_back(OverloadChoice(baseType, candidate.getValueDecl(),
5472+
UDE->getFunctionRefKind()));
5473+
CalleeCandidateInfo candidateInfo(baseType, choices,
5474+
callArgHasTrailingClosure(argExpr),
5475+
CS, true);
5476+
struct TypeConvertibleChecker {
5477+
ConstraintSystem &CS;
5478+
5479+
TypeConvertibleChecker(ConstraintSystem &CS) : CS(CS) {}
5480+
5481+
// Checks whether type1 is implicitly convertible to type2
5482+
bool isImplicitlyConvertible(Type type1, Type type2) {
5483+
if (!type1 || !type2)
5484+
return false;
5485+
5486+
if (auto tup1 = type1->getAs<TupleType>()) {
5487+
if (auto tup2 = type2->getAs<TupleType>())
5488+
return isImplicitlyConvertibleTuple(tup1, tup2);
5489+
else
5490+
return false;
5491+
}
5492+
if (type1->isEqual(type2) || type2->is<GenericTypeParamType>() ||
5493+
CS.TC.isSubtypeOf(type1, type2, CS.DC))
5494+
return true;
5495+
return false;
5496+
}
5497+
5498+
bool isImplicitlyConvertibleTuple(TupleType *tup1, TupleType *tup2) {
5499+
if (tup1->getNumElements() != tup2->getNumElements())
5500+
return false;
5501+
5502+
for (unsigned i = 0, e = tup1->getNumElements(); i < e; i ++) {
5503+
auto element1 = tup1->getElement(i);
5504+
auto element2 = tup2->getElement(i);
5505+
if (element1.hasName()) {
5506+
if (!element2.hasName() || element1.getName() !=
5507+
element2.getName())
5508+
return false;
5509+
}
5510+
if (!isImplicitlyConvertible(element1.getType(),
5511+
element2.getType()))
5512+
return false;
5513+
}
5514+
return true;
5515+
}
5516+
} checker(CS);
5517+
auto params = swift::decomposeArgType(CS.getType(argExpr), argLabels);
5518+
using ClosenessPair = CalleeCandidateInfo::ClosenessResultTy;
5519+
5520+
candidateInfo.filterList([&](UncurriedCandidate cand) -> ClosenessPair {
5521+
auto candFuncType = cand.getUncurriedFunctionType();
5522+
if (!candFuncType)
5523+
return {CC_GeneralMismatch, {}};
5524+
5525+
auto candParams = candFuncType->getParams();
5526+
if (params.size() != candParams.size())
5527+
return {CC_GeneralMismatch, {}};
5528+
5529+
for (unsigned i = 0, e = params.size(); i < e; i ++) {
5530+
if (checker.isImplicitlyConvertible(params[i].getType(),
5531+
candParams[i].getType()))
5532+
continue;
5533+
return {CC_GeneralMismatch, {}};
5534+
}
5535+
return {CC_ExactMatch, {}};
5536+
});
5537+
5538+
auto *locator = CS.getConstraintLocator(UDE, ConstraintLocator::Member);
5539+
auto memberRange = baseExpr->getSourceRange();
5540+
if (locator)
5541+
locator = simplifyLocator(CS, locator, memberRange);
5542+
auto nameLoc = DeclNameLoc(memberRange.Start);
5543+
5544+
auto diag = diagnose(baseExpr->getLoc(),
5545+
diag::could_not_find_subscript_member_did_you_mean,
5546+
baseType);
5547+
diag.highlight(memberRange).highlight(nameLoc.getSourceRange());
5548+
5549+
if (candidateInfo.closeness != CC_ExactMatch)
5550+
return true;
5551+
5552+
auto toCharSourceRange = Lexer::getCharSourceRangeFromSourceRange;
5553+
auto lastArgSymbol = toCharSourceRange(CS.TC.Context.SourceMgr,
5554+
argExpr->getEndLoc());
5555+
5556+
diag.fixItReplace(SourceRange(argExpr->getStartLoc()),
5557+
getTokenText(tok::l_square));
5558+
diag.fixItRemove(nameLoc.getSourceRange());
5559+
diag.fixItRemove(SourceRange(UDE->getDotLoc()));
5560+
if (CS.TC.Context.SourceMgr.extractText(lastArgSymbol) ==
5561+
getTokenText(tok::r_paren))
5562+
diag.fixItReplace(SourceRange(argExpr->getEndLoc()),
5563+
getTokenText(tok::r_square));
5564+
else
5565+
diag.fixItInsertAfter(argExpr->getEndLoc(),
5566+
getTokenText(tok::r_square));
5567+
diag.flush();
5568+
5569+
if (candidateInfo.size() == 1)
5570+
diagnose(candidateInfo.candidates.front().getDecl(),
5571+
diag::subscript_declared_here);
5572+
5573+
return true;
5574+
}
5575+
54375576
// Check if there is a structural problem in the function expression
54385577
// by performing type checking with the option to allow unresolved
54395578
// type variables. If that is going to produce a function type with
@@ -5480,11 +5619,18 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
54805619
auto originalFnType = CS.getType(callExpr->getFn());
54815620

54825621
if (shouldTypeCheckFunctionExpr(CS.TC, CS.DC, fnExpr)) {
5622+
5623+
// If we are misusing a subscript, diagnose that and provide a fixit.
5624+
// We diagnose this here to have enough context to offer an appropriate fixit.
5625+
if (diagnoseSubscriptMisuse(callExpr)) {
5626+
return CS.TC.Diags.hadAnyError();
5627+
}
54835628
// Type check the function subexpression to resolve a type for it if
54845629
// possible.
54855630
fnExpr = typeCheckChildIndependently(callExpr->getFn());
5486-
if (!fnExpr)
5487-
return true;
5631+
if (!fnExpr) {
5632+
return CS.TC.Diags.hadAnyError();
5633+
}
54885634
}
54895635

54905636
SWIFT_DEFER {

lib/Sema/CalleeCandidateInfo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ CalleeCandidateInfo::CalleeCandidateInfo(Type baseType,
835835
// the uncurry level is 1 if self has already been applied.
836836
unsigned uncurryLevel = 0;
837837
if (decl->getDeclContext()->isTypeContext() &&
838-
selfAlreadyApplied)
838+
selfAlreadyApplied && !isa<SubscriptDecl>(decl))
839839
uncurryLevel = 1;
840840

841841
candidates.push_back({ decl, uncurryLevel });

test/decl/subscript/subscripting.swift

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -246,33 +246,82 @@ struct MutableSubscriptInGetter {
246246
}
247247
}
248248

249+
protocol Protocol {}
250+
protocol RefinedProtocol: Protocol {}
251+
class SuperClass {}
252+
class SubClass: SuperClass {}
253+
class SubSubClass: SubClass {}
254+
class ClassConformingToProtocol: Protocol {}
255+
class ClassConformingToRefinedProtocol: RefinedProtocol {}
256+
257+
struct GenSubscriptFixitTest {
258+
subscript<T>(_ arg: T) -> Bool { return true } // expected-note {{declared here}}
259+
}
260+
261+
func testGenSubscriptFixit(_ s0: GenSubscriptFixitTest) {
262+
263+
_ = s0.subscript("hello")
264+
// expected-error@-1 {{value of type 'GenSubscriptFixitTest' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{27-28=]}}
265+
}
266+
249267
struct SubscriptTest1 {
250268
subscript(keyword:String) -> Bool { return true } // expected-note 2 {{found this candidate}}
251269
subscript(keyword:String) -> String? {return nil } // expected-note 2 {{found this candidate}}
270+
271+
subscript(arg: SubClass) -> Bool { return true } // expected-note {{declared here}}
272+
subscript(arg: Protocol) -> Bool { return true } // expected-note 2 {{declared here}}
273+
274+
subscript(arg: (foo: Bool, bar: (Int, baz: SubClass)), arg2: String) -> Bool { return true }
275+
// expected-note@-1 2 {{declared here}}
252276
}
253277

254278
func testSubscript1(_ s1 : SubscriptTest1) {
255279
let _ : Int = s1["hello"] // expected-error {{ambiguous subscript with base type 'SubscriptTest1' and index type 'String'}}
256-
280+
257281
if s1["hello"] {}
258-
259-
260-
let _ = s1["hello"] // expected-error {{ambiguous use of 'subscript'}}
282+
283+
_ = s1.subscript((true, (5, SubClass())), "hello")
284+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{52-53=]}}
285+
_ = s1.subscript((true, (5, baz: SubSubClass())), "hello")
286+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{60-61=]}}
287+
_ = s1.subscript((fo: true, (5, baz: SubClass())), "hello")
288+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}}
289+
_ = s1.subscript(SubSubClass())
290+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{33-34=]}}
291+
_ = s1.subscript(ClassConformingToProtocol())
292+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{47-48=]}}
293+
_ = s1.subscript(ClassConformingToRefinedProtocol())
294+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{54-55=]}}
295+
_ = s1.subscript(true)
296+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}}
297+
_ = s1.subscript(SuperClass())
298+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}}
299+
_ = s1.subscript("hello")
300+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{27-28=]}}
301+
_ = s1.subscript("hello"
302+
// expected-error@-1 {{value of type 'SubscriptTest1' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{27-27=]}}
303+
// expected-note@-2 {{to match this opening '('}}
304+
305+
let _ = s1["hello"]
306+
// expected-error@-1 {{ambiguous use of 'subscript'}}
307+
// expected-error@-2 {{expected ')' in expression list}}
261308
}
262309

263310
struct SubscriptTest2 {
264311
subscript(a : String, b : Int) -> Int { return 0 }
312+
// expected-note@-1 {{declared here}}
265313
subscript(a : String, b : String) -> Int { return 0 }
266314
}
267315

268316
func testSubscript1(_ s2 : SubscriptTest2) {
269317
_ = s2["foo"] // expected-error {{cannot subscript a value of type 'SubscriptTest2' with an index of type 'String'}}
270318
// expected-note @-1 {{overloads for 'subscript' exist with these partially matching parameter lists: (String, Int), (String, String)}}
271-
319+
272320
let a = s2["foo", 1.0] // expected-error {{cannot subscript a value of type 'SubscriptTest2' with an index of type '(String, Double)'}}
273321
// expected-note @-1 {{overloads for 'subscript' exist with these partially matching parameter lists: (String, Int), (String, String)}}
274-
275-
322+
323+
_ = s2.subscript("hello", 6)
324+
// expected-error@-1 {{value of type 'SubscriptTest2' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{30-31=]}}
276325
let b = s2[1, "foo"] // expected-error {{cannot convert value of type 'Int' to expected argument type 'String'}}
277326

278327
// rdar://problem/27449208
@@ -313,9 +362,10 @@ struct S4 {
313362

314363
// SR-2575
315364
struct SR2575 {
316-
subscript() -> Int {
365+
subscript() -> Int { // expected-note {{declared here}}
317366
return 1
318367
}
319368
}
320369

321-
SR2575().subscript() // expected-error{{type 'SR2575' has no member 'subscript'}}
370+
SR2575().subscript()
371+
// expected-error@-1 {{value of type 'SR2575' has no property or method named 'subscript'; did you mean to use the subscript operator?}} {{9-10=}} {{10-19=}} {{19-20=[}} {{20-21=]}}

0 commit comments

Comments
 (0)