Merge pull request #28630 from xedin/dynamic-member-diagnostics

[Diagnostics] Port/Improve diagnostics for `@dynamicCallable` and `callAsFunction`
This commit is contained in:
Pavel Yaskevich
2019-12-09 10:24:27 -08:00
committed by GitHub
10 changed files with 130 additions and 62 deletions

View File

@@ -993,7 +993,6 @@ The things in the queue yet to be ported are:
- Problems related to calls and operator applications e.g.
- ``@dynamicCallable`` related diagnostics
- Missing explicit ``Self.`` and ``self.``
- Logic related to overload candidate ranking (``CalleeCandidateInfo``)
- ``diagnoseParameterErrors``

View File

@@ -2017,49 +2017,6 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
if (!isUnresolvedOrTypeVarType(fnType) &&
!fnType->is<AnyFunctionType>() && !fnType->is<MetatypeType>()) {
auto arg = callExpr->getArg();
auto isDynamicCallable =
CS.DynamicCallableCache[fnType->getCanonicalType()].isValid();
auto hasCallAsFunctionMethods = fnType->isCallableNominalType(CS.DC);
// Diagnose specific @dynamicCallable errors.
if (isDynamicCallable) {
auto dynamicCallableMethods =
CS.DynamicCallableCache[fnType->getCanonicalType()];
// Diagnose dynamic calls with keywords on @dynamicCallable types that
// don't define the `withKeywordArguments` method.
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
bool hasArgLabel = llvm::any_of(
tuple->getElementNames(), [](Identifier i) { return !i.empty(); });
if (hasArgLabel &&
dynamicCallableMethods.keywordArgumentsMethods.empty()) {
diagnose(callExpr->getFn()->getStartLoc(),
diag::missing_dynamic_callable_kwargs_method, fnType);
return true;
}
}
}
auto isExistentialMetatypeType = fnType->is<ExistentialMetatypeType>();
if (isExistentialMetatypeType) {
auto diag = diagnose(arg->getStartLoc(),
diag::missing_init_on_metatype_initialization);
diag.highlight(fnExpr->getSourceRange());
} else if (!isDynamicCallable) {
auto diag = diagnose(arg->getStartLoc(),
diag::cannot_call_non_function_value, fnType);
diag.highlight(fnExpr->getSourceRange());
// If the argument is an empty tuple, then offer a
// fix-it to remove the empty tuple and use the value
// directly.
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
if (tuple->getNumElements() == 0) {
diag.fixItRemove(arg->getSourceRange());
}
}
}
// If the argument is a trailing ClosureExpr (i.e. {....}) and it is on
// the line after the callee, then it's likely the user forgot to
@@ -2075,14 +2032,30 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
SM.getLineNumber(closure->getStartLoc())) {
diagnose(closure->getStartLoc(), diag::brace_stmt_suggest_do)
.fixItInsert(closure->getStartLoc(), "do ");
return true;
}
}
}
// Use the existing machinery to provide more useful diagnostics for
// @dynamicCallable calls, rather than cannot_call_non_function_value.
if ((isExistentialMetatypeType || !isDynamicCallable) &&
!hasCallAsFunctionMethods) {
auto isExistentialMetatypeType = fnType->is<ExistentialMetatypeType>();
if (isExistentialMetatypeType) {
auto diag = diagnose(arg->getStartLoc(),
diag::missing_init_on_metatype_initialization);
diag.highlight(fnExpr->getSourceRange());
return true;
} else {
auto diag = diagnose(arg->getStartLoc(),
diag::cannot_call_non_function_value, fnType);
diag.highlight(fnExpr->getSourceRange());
// If the argument is an empty tuple, then offer a
// fix-it to remove the empty tuple and use the value
// directly.
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
if (tuple->getNumElements() == 0) {
diag.fixItRemove(arg->getSourceRange());
}
}
return true;
}
}

View File

@@ -121,6 +121,12 @@ Expr *FailureDiagnostic::getBaseExprFor(Expr *anchor) const {
return SE->getBase();
else if (auto *MRE = dyn_cast<MemberRefExpr>(anchor))
return MRE->getBase();
else if (auto *call = dyn_cast<CallExpr>(anchor)) {
auto fnType = getType(call->getFn());
if (fnType->isCallableNominalType(getDC())) {
return call->getFn();
}
}
return nullptr;
}
@@ -3205,6 +3211,9 @@ bool MissingMemberFailure::diagnoseAsError() {
if (!anchor || !baseExpr)
return false;
if (diagnoseForDynamicCallable())
return true;
auto baseType = resolveType(getBaseType())->getWithoutSpecifierType();
DeclNameLoc nameLoc(anchor->getStartLoc());
@@ -3376,6 +3385,26 @@ bool MissingMemberFailure::diagnoseAsError() {
return true;
}
bool MissingMemberFailure::diagnoseForDynamicCallable() const {
auto *locator = getLocator();
if (!locator->isLastElement<LocatorPathElt::DynamicCallable>())
return false;
auto memberName = getName();
auto arguments = memberName.getArgumentNames();
assert(arguments.size() == 1);
auto &ctx = getASTContext();
if (arguments.front() == ctx.Id_withKeywordArguments) {
auto anchor = getAnchor();
emitDiagnostic(anchor->getLoc(),
diag::missing_dynamic_callable_kwargs_method, getBaseType());
return true;
}
return false;
}
bool InvalidMemberRefOnExistential::diagnoseAsError() {
auto *anchor = getRawAnchor();

View File

@@ -1148,6 +1148,12 @@ public:
bool diagnoseAsError() override;
private:
/// Tailored diagnostics for missing special `@dynamicCallable` methods
/// e.g. if caller expects `dynamicallyCall(withKeywordArguments:)`
/// overload to be present, but a class marked as `@dynamicCallable`
/// defines only `dynamicallyCall(withArguments:)` variant.
bool diagnoseForDynamicCallable() const;
static DeclName findCorrectEnumCaseName(Type Ty,
TypoCorrectionResults &corrections,
DeclName memberName);

View File

@@ -3167,6 +3167,12 @@ bool ConstraintSystem::repairFailures(
// for this call, we can consider overload unrelated.
if (llvm::any_of(getFixes(), [&](const ConstraintFix *fix) {
auto *locator = fix->getLocator();
// Since arguments to @dynamicCallable form either an array
// or a dictionary and all have to match the same element type,
// let's allow multiple invalid arguments.
if (locator->findFirst<LocatorPathElt::DynamicCallable>())
return false;
return locator->findLast<LocatorPathElt::ApplyArgToParam>()
? locator->getAnchor() == anchor
: false;
@@ -7819,7 +7825,35 @@ ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
choices.push_back(
OverloadChoice(type2, candidate, FunctionRefKind::SingleApply));
}
if (choices.empty()) return SolutionKind::Error;
if (choices.empty()) {
if (!shouldAttemptFixes())
return SolutionKind::Error;
// TODO(diagnostics): This is not going to be necessary once
// `@dynamicCallable` uses existing `member` machinery.
auto memberName = DeclName(
ctx, ctx.Id_dynamicallyCall,
{useKwargsMethod ? ctx.Id_withKeywordArguments : ctx.Id_withArguments});
auto *fix = DefineMemberBasedOnUse::create(
*this, desugar2, memberName,
getConstraintLocator(loc, ConstraintLocator::DynamicCallable));
if (recordFix(fix))
return SolutionKind::Error;
recordPotentialHole(tv);
Type(func1).visit([&](Type type) {
if (auto *typeVar = type->getAs<TypeVariableType>())
recordPotentialHole(typeVar);
});
return SolutionKind::Solved;
}
addOverloadSet(tv, choices, DC, loc);
// Create a type variable for the argument to the `dynamicallyCall` method.
@@ -7852,14 +7886,20 @@ ConstraintSystem::simplifyDynamicCallableApplicableFnConstraint(
addConstraint(ConstraintKind::Defaultable, argumentType,
ctx.TheAnyType, locator);
auto *baseArgLoc = getConstraintLocator(
loc->getAnchor(),
{ConstraintLocator::DynamicCallable, ConstraintLocator::ApplyArgument},
/*summaryFlags=*/0);
// All dynamic call parameter types must be convertible to the argument type.
for (auto i : indices(func1->getParams())) {
auto param = func1->getParams()[i];
auto paramType = param.getPlainType();
auto locatorBuilder =
locator.withPathElement(LocatorPathElt::TupleElement(i));
addConstraint(ConstraintKind::ArgumentConversion, paramType,
argumentType, locatorBuilder);
addConstraint(
ConstraintKind::ArgumentConversion, paramType, argumentType,
getConstraintLocator(baseArgLoc, LocatorPathElt::ApplyArgToParam(
i, 0, param.getParameterFlags())));
}
return SolutionKind::Solved;

View File

@@ -113,6 +113,7 @@ unsigned LocatorPathElt::getNewSummaryFlags() const {
case ConstraintLocator::KeyPathValue:
case ConstraintLocator::KeyPathComponentResult:
case ConstraintLocator::Condition:
case ConstraintLocator::DynamicCallable:
return 0;
case ConstraintLocator::FunctionArgument:
@@ -453,6 +454,10 @@ void ConstraintLocator::dump(SourceManager *sm, raw_ostream &out) const {
case Condition:
out << "condition expression";
break;
case DynamicCallable:
out << "implicit call to @dynamicCallable method";
break;
}
}
out << ']';

View File

@@ -167,6 +167,8 @@ CUSTOM_LOCATOR_PATH_ELT(Witness)
/// The condition associated with 'if' expression or ternary operator.
SIMPLE_LOCATOR_PATH_ELT(Condition)
SIMPLE_LOCATOR_PATH_ELT(DynamicCallable)
#undef LOCATOR_PATH_ELT
#undef CUSTOM_LOCATOR_PATH_ELT
#undef SIMPLE_LOCATOR_PATH_ELT

View File

@@ -3066,6 +3066,11 @@ void constraints::simplifyLocator(Expr *&anchor,
break;
}
case ConstraintLocator::DynamicCallable: {
path = path.slice(1);
continue;
}
case ConstraintLocator::ApplyFunction:
// Extract application function.
if (auto applyExpr = dyn_cast<ApplyExpr>(anchor)) {

View File

@@ -102,9 +102,7 @@ struct Mutating {
}
}
func testMutating(_ x: Mutating, _ y: inout Mutating) {
// TODO(SR-11378): Improve this error to match the error using a direct `callAsFunction` member reference.
// expected-error @+2 {{cannot call value of non-function type 'Mutating'}}
// expected-error @+1 {{cannot invoke 'x' with no arguments}}
// expected-error @+1 {{cannot use mutating member on immutable value: 'x' is a 'let' constant}}
_ = x()
// expected-error @+1 {{cannot use mutating member on immutable value: 'x' is a 'let' constant}}
_ = x.callAsFunction()

View File

@@ -52,10 +52,21 @@ func testCallable(
func testCallableDiagnostics(
a: Callable, b: DiscardableResult, c: Throwing, d: KeywordArgumentCallable
) {
a("hello", "world") // expected-error {{cannot invoke 'a' with an argument list of type '(String, String)'}}
b("hello", "world") // expected-error {{cannot invoke 'b' with an argument list of type '(String, String)'}}
try? c(1, 2, 3, 4) // expected-error {{cannot invoke 'c' with an argument list of type '(Int, Int, Int, Int)'}}
d(x1: "hello", x2: "world") // expected-error {{cannot invoke 'd' with an argument list of type '(x1: String, x2: String)'}}
a("hello", "world")
// expected-error@-1:5 {{cannot convert value of type 'String' to expected argument type 'Int'}}
// expected-error@-2:14 {{cannot convert value of type 'String' to expected argument type 'Int'}}
b("hello", "world")
// expected-error@-1:5 {{cannot convert value of type 'String' to expected argument type 'Double'}}
// expected-error@-2:14 {{cannot convert value of type 'String' to expected argument type 'Double'}}
try? c(1, 2, 3, 4)
// expected-error@-1:10 {{cannot convert value of type 'Int' to expected argument type 'String'}}
// expected-error@-2:13 {{cannot convert value of type 'Int' to expected argument type 'String'}}
// expected-error@-3:16 {{cannot convert value of type 'Int' to expected argument type 'String'}}
// expected-error@-4:19 {{cannot convert value of type 'Int' to expected argument type 'String'}}
d(x1: "hello", x2: "world")
// expected-error@-1:9 {{cannot convert value of type 'String' to expected argument type 'Float'}}
// expected-error@-2:22 {{cannot convert value of type 'String' to expected argument type 'Float'}}
}
func testIUO(