Skip to content

Commit 3d7306a

Browse files
authored
Merge pull request #28630 from xedin/dynamic-member-diagnostics
[Diagnostics] Port/Improve diagnostics for `@dynamicCallable` and `callAsFunction`
2 parents ee1e7ec + cb8177e commit 3d7306a

10 files changed

+124
-56
lines changed

docs/TypeChecker.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,6 @@ The things in the queue yet to be ported are:
993993

994994
- Problems related to calls and operator applications e.g.
995995

996-
- ``@dynamicCallable`` related diagnostics
997996
- Missing explicit ``Self.`` and ``self.``
998997
- Logic related to overload candidate ranking (``CalleeCandidateInfo``)
999998
- ``diagnoseParameterErrors``

lib/Sema/CSDiag.cpp

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2017,25 +2017,21 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
20172017
if (!isUnresolvedOrTypeVarType(fnType) &&
20182018
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
20192019
auto arg = callExpr->getArg();
2020-
auto isDynamicCallable =
2021-
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();
20222020

2023-
auto hasCallAsFunctionMethods = fnType->isCallableNominalType(CS.DC);
2024-
2025-
// Diagnose specific @dynamicCallable errors.
2026-
if (isDynamicCallable) {
2027-
auto dynamicCallableMethods =
2028-
CS.DynamicCallableCache[fnType->getCanonicalType()];
2029-
2030-
// Diagnose dynamic calls with keywords on @dynamicCallable types that
2031-
// don't define the `withKeywordArguments` method.
2032-
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
2033-
bool hasArgLabel = llvm::any_of(
2034-
tuple->getElementNames(), [](Identifier i) { return !i.empty(); });
2035-
if (hasArgLabel &&
2036-
dynamicCallableMethods.keywordArgumentsMethods.empty()) {
2037-
diagnose(callExpr->getFn()->getStartLoc(),
2038-
diag::missing_dynamic_callable_kwargs_method, fnType);
2021+
// If the argument is a trailing ClosureExpr (i.e. {....}) and it is on
2022+
// the line after the callee, then it's likely the user forgot to
2023+
// write "do" before their brace stmt.
2024+
// Note that line differences of more than 1 are diagnosed during parsing.
2025+
if (auto *PE = dyn_cast<ParenExpr>(arg)) {
2026+
if (PE->hasTrailingClosure() && isa<ClosureExpr>(PE->getSubExpr())) {
2027+
auto *closure = cast<ClosureExpr>(PE->getSubExpr());
2028+
auto &SM = CS.getASTContext().SourceMgr;
2029+
if (closure->hasAnonymousClosureVars() &&
2030+
closure->getParameters()->size() == 0 &&
2031+
1 + SM.getLineNumber(callExpr->getFn()->getEndLoc()) ==
2032+
SM.getLineNumber(closure->getStartLoc())) {
2033+
diagnose(closure->getStartLoc(), diag::brace_stmt_suggest_do)
2034+
.fixItInsert(closure->getStartLoc(), "do ");
20392035
return true;
20402036
}
20412037
}
@@ -2046,7 +2042,8 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
20462042
auto diag = diagnose(arg->getStartLoc(),
20472043
diag::missing_init_on_metatype_initialization);
20482044
diag.highlight(fnExpr->getSourceRange());
2049-
} else if (!isDynamicCallable) {
2045+
return true;
2046+
} else {
20502047
auto diag = diagnose(arg->getStartLoc(),
20512048
diag::cannot_call_non_function_value, fnType);
20522049
diag.highlight(fnExpr->getSourceRange());
@@ -2059,30 +2056,6 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
20592056
diag.fixItRemove(arg->getSourceRange());
20602057
}
20612058
}
2062-
}
2063-
2064-
// If the argument is a trailing ClosureExpr (i.e. {....}) and it is on
2065-
// the line after the callee, then it's likely the user forgot to
2066-
// write "do" before their brace stmt.
2067-
// Note that line differences of more than 1 are diagnosed during parsing.
2068-
if (auto *PE = dyn_cast<ParenExpr>(arg)) {
2069-
if (PE->hasTrailingClosure() && isa<ClosureExpr>(PE->getSubExpr())) {
2070-
auto *closure = cast<ClosureExpr>(PE->getSubExpr());
2071-
auto &SM = CS.getASTContext().SourceMgr;
2072-
if (closure->hasAnonymousClosureVars() &&
2073-
closure->getParameters()->size() == 0 &&
2074-
1 + SM.getLineNumber(callExpr->getFn()->getEndLoc()) ==
2075-
SM.getLineNumber(closure->getStartLoc())) {
2076-
diagnose(closure->getStartLoc(), diag::brace_stmt_suggest_do)
2077-
.fixItInsert(closure->getStartLoc(), "do ");
2078-
}
2079-
}
2080-
}
2081-
2082-
// Use the existing machinery to provide more useful diagnostics for
2083-
// @dynamicCallable calls, rather than cannot_call_non_function_value.
2084-
if ((isExistentialMetatypeType || !isDynamicCallable) &&
2085-
!hasCallAsFunctionMethods) {
20862059
return true;
20872060
}
20882061
}

lib/Sema/CSDiagnostics.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,12 @@ Expr *FailureDiagnostic::getBaseExprFor(Expr *anchor) const {
121121
return SE->getBase();
122122
else if (auto *MRE = dyn_cast<MemberRefExpr>(anchor))
123123
return MRE->getBase();
124+
else if (auto *call = dyn_cast<CallExpr>(anchor)) {
125+
auto fnType = getType(call->getFn());
126+
if (fnType->isCallableNominalType(getDC())) {
127+
return call->getFn();
128+
}
129+
}
124130

125131
return nullptr;
126132
}
@@ -3205,6 +3211,9 @@ bool MissingMemberFailure::diagnoseAsError() {
32053211
if (!anchor || !baseExpr)
32063212
return false;
32073213

3214+
if (diagnoseForDynamicCallable())
3215+
return true;
3216+
32083217
auto baseType = resolveType(getBaseType())->getWithoutSpecifierType();
32093218

32103219
DeclNameLoc nameLoc(anchor->getStartLoc());
@@ -3376,6 +3385,26 @@ bool MissingMemberFailure::diagnoseAsError() {
33763385
return true;
33773386
}
33783387

3388+
bool MissingMemberFailure::diagnoseForDynamicCallable() const {
3389+
auto *locator = getLocator();
3390+
if (!locator->isLastElement<LocatorPathElt::DynamicCallable>())
3391+
return false;
3392+
3393+
auto memberName = getName();
3394+
auto arguments = memberName.getArgumentNames();
3395+
assert(arguments.size() == 1);
3396+
3397+
auto &ctx = getASTContext();
3398+
if (arguments.front() == ctx.Id_withKeywordArguments) {
3399+
auto anchor = getAnchor();
3400+
emitDiagnostic(anchor->getLoc(),
3401+
diag::missing_dynamic_callable_kwargs_method, getBaseType());
3402+
return true;
3403+
}
3404+
3405+
return false;
3406+
}
3407+
33793408
bool InvalidMemberRefOnExistential::diagnoseAsError() {
33803409
auto *anchor = getRawAnchor();
33813410

lib/Sema/CSDiagnostics.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,12 @@ class MissingMemberFailure final : public InvalidMemberRefFailure {
11481148
bool diagnoseAsError() override;
11491149

11501150
private:
1151+
/// Tailored diagnostics for missing special `@dynamicCallable` methods
1152+
/// e.g. if caller expects `dynamicallyCall(withKeywordArguments:)`
1153+
/// overload to be present, but a class marked as `@dynamicCallable`
1154+
/// defines only `dynamicallyCall(withArguments:)` variant.
1155+
bool diagnoseForDynamicCallable() const;
1156+
11511157
static DeclName findCorrectEnumCaseName(Type Ty,
11521158
TypoCorrectionResults &corrections,
11531159
DeclName memberName);

lib/Sema/CSSimplify.cpp

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3167,6 +3167,12 @@ bool ConstraintSystem::repairFailures(
31673167
// for this call, we can consider overload unrelated.
31683168
if (llvm::any_of(getFixes(), [&](const ConstraintFix *fix) {
31693169
auto *locator = fix->getLocator();
3170+
// Since arguments to @dynamicCallable form either an array
3171+
// or a dictionary and all have to match the same element type,
3172+
// let's allow multiple invalid arguments.
3173+
if (locator->findFirst<LocatorPathElt::DynamicCallable>())
3174+
return false;
3175+
31703176
return locator->findLast<LocatorPathElt::ApplyArgToParam>()
31713177
? locator->getAnchor() == anchor
31723178
: false;
@@ -7819,7 +7825,35 @@ ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
78197825
choices.push_back(
78207826
OverloadChoice(type2, candidate, FunctionRefKind::SingleApply));
78217827
}
7822-
if (choices.empty()) return SolutionKind::Error;
7828+
7829+
if (choices.empty()) {
7830+
if (!shouldAttemptFixes())
7831+
return SolutionKind::Error;
7832+
7833+
// TODO(diagnostics): This is not going to be necessary once
7834+
// `@dynamicCallable` uses existing `member` machinery.
7835+
7836+
auto memberName = DeclName(
7837+
ctx, ctx.Id_dynamicallyCall,
7838+
{useKwargsMethod ? ctx.Id_withKeywordArguments : ctx.Id_withArguments});
7839+
7840+
auto *fix = DefineMemberBasedOnUse::create(
7841+
*this, desugar2, memberName,
7842+
getConstraintLocator(loc, ConstraintLocator::DynamicCallable));
7843+
7844+
if (recordFix(fix))
7845+
return SolutionKind::Error;
7846+
7847+
recordPotentialHole(tv);
7848+
7849+
Type(func1).visit([&](Type type) {
7850+
if (auto *typeVar = type->getAs<TypeVariableType>())
7851+
recordPotentialHole(typeVar);
7852+
});
7853+
7854+
return SolutionKind::Solved;
7855+
}
7856+
78237857
addOverloadSet(tv, choices, DC, loc);
78247858

78257859
// Create a type variable for the argument to the `dynamicallyCall` method.
@@ -7852,14 +7886,20 @@ ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
78527886
addConstraint(ConstraintKind::Defaultable, argumentType,
78537887
ctx.TheAnyType, locator);
78547888

7889+
auto *baseArgLoc = getConstraintLocator(
7890+
loc->getAnchor(),
7891+
{ConstraintLocator::DynamicCallable, ConstraintLocator::ApplyArgument},
7892+
/*summaryFlags=*/0);
7893+
78557894
// All dynamic call parameter types must be convertible to the argument type.
78567895
for (auto i : indices(func1->getParams())) {
78577896
auto param = func1->getParams()[i];
78587897
auto paramType = param.getPlainType();
7859-
auto locatorBuilder =
7860-
locator.withPathElement(LocatorPathElt::TupleElement(i));
7861-
addConstraint(ConstraintKind::ArgumentConversion, paramType,
7862-
argumentType, locatorBuilder);
7898+
7899+
addConstraint(
7900+
ConstraintKind::ArgumentConversion, paramType, argumentType,
7901+
getConstraintLocator(baseArgLoc, LocatorPathElt::ApplyArgToParam(
7902+
i, 0, param.getParameterFlags())));
78637903
}
78647904

78657905
return SolutionKind::Solved;

lib/Sema/ConstraintLocator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const {
113113
case ConstraintLocator::KeyPathValue:
114114
case ConstraintLocator::KeyPathComponentResult:
115115
case ConstraintLocator::Condition:
116+
case ConstraintLocator::DynamicCallable:
116117
return 0;
117118

118119
case ConstraintLocator::FunctionArgument:
@@ -453,6 +454,10 @@ void ConstraintLocator::dump(SourceManager *sm, raw_ostream &out) const {
453454
case Condition:
454455
out << "condition expression";
455456
break;
457+
458+
case DynamicCallable:
459+
out << "implicit call to @dynamicCallable method";
460+
break;
456461
}
457462
}
458463
out << ']';

lib/Sema/ConstraintLocatorPathElts.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ CUSTOM_LOCATOR_PATH_ELT(Witness)
167167
/// The condition associated with 'if' expression or ternary operator.
168168
SIMPLE_LOCATOR_PATH_ELT(Condition)
169169

170+
SIMPLE_LOCATOR_PATH_ELT(DynamicCallable)
171+
170172
#undef LOCATOR_PATH_ELT
171173
#undef CUSTOM_LOCATOR_PATH_ELT
172174
#undef SIMPLE_LOCATOR_PATH_ELT

lib/Sema/ConstraintSystem.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3066,6 +3066,11 @@ void constraints::simplifyLocator(Expr *&anchor,
30663066
break;
30673067
}
30683068

3069+
case ConstraintLocator::DynamicCallable: {
3070+
path = path.slice(1);
3071+
continue;
3072+
}
3073+
30693074
case ConstraintLocator::ApplyFunction:
30703075
// Extract application function.
30713076
if (auto applyExpr = dyn_cast<ApplyExpr>(anchor)) {

test/Sema/call_as_function_simple.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,7 @@ struct Mutating {
102102
}
103103
}
104104
func testMutating(_ x: Mutating, _ y: inout Mutating) {
105-
// TODO(SR-11378): Improve this error to match the error using a direct `callAsFunction` member reference.
106-
// expected-error @+2 {{cannot call value of non-function type 'Mutating'}}
107-
// expected-error @+1 {{cannot invoke 'x' with no arguments}}
105+
// expected-error @+1 {{cannot use mutating member on immutable value: 'x' is a 'let' constant}}
108106
_ = x()
109107
// expected-error @+1 {{cannot use mutating member on immutable value: 'x' is a 'let' constant}}
110108
_ = x.callAsFunction()

test/attr/attr_dynamic_callable.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,21 @@ func testCallable(
5252
func testCallableDiagnostics(
5353
a: Callable, b: DiscardableResult, c: Throwing, d: KeywordArgumentCallable
5454
) {
55-
a("hello", "world") // expected-error {{cannot invoke 'a' with an argument list of type '(String, String)'}}
56-
b("hello", "world") // expected-error {{cannot invoke 'b' with an argument list of type '(String, String)'}}
57-
try? c(1, 2, 3, 4) // expected-error {{cannot invoke 'c' with an argument list of type '(Int, Int, Int, Int)'}}
58-
d(x1: "hello", x2: "world") // expected-error {{cannot invoke 'd' with an argument list of type '(x1: String, x2: String)'}}
55+
a("hello", "world")
56+
// expected-error@-1:5 {{cannot convert value of type 'String' to expected argument type 'Int'}}
57+
// expected-error@-2:14 {{cannot convert value of type 'String' to expected argument type 'Int'}}
58+
b("hello", "world")
59+
// expected-error@-1:5 {{cannot convert value of type 'String' to expected argument type 'Double'}}
60+
// expected-error@-2:14 {{cannot convert value of type 'String' to expected argument type 'Double'}}
61+
try? c(1, 2, 3, 4)
62+
// expected-error@-1:10 {{cannot convert value of type 'Int' to expected argument type 'String'}}
63+
// expected-error@-2:13 {{cannot convert value of type 'Int' to expected argument type 'String'}}
64+
// expected-error@-3:16 {{cannot convert value of type 'Int' to expected argument type 'String'}}
65+
// expected-error@-4:19 {{cannot convert value of type 'Int' to expected argument type 'String'}}
66+
67+
d(x1: "hello", x2: "world")
68+
// expected-error@-1:9 {{cannot convert value of type 'String' to expected argument type 'Float'}}
69+
// expected-error@-2:22 {{cannot convert value of type 'String' to expected argument type 'Float'}}
5970
}
6071

6172
func testIUO(

0 commit comments

Comments
 (0)