Skip to content

Commit e9817b0

Browse files
authored
Merge pull request swiftlang#22926 from xedin/diag-missing-clj-params
[Diagnostics] Diagnose missing arguments in closures via fixes
2 parents 969b90d + 9d4116d commit e9817b0

File tree

12 files changed

+361
-88
lines changed

12 files changed

+361
-88
lines changed

include/swift/AST/Types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2762,6 +2762,8 @@ class AnyFunctionType : public TypeBase {
27622762
bool operator!=(Param const &b) const { return !(*this == b); }
27632763

27642764
Param getWithoutLabel() const { return Param(Ty, Identifier(), Flags); }
2765+
2766+
Param withType(Type newType) const { return Param(newType, Label, Flags); }
27652767
};
27662768

27672769
class CanParam : public Param {

lib/Sema/CSDiag.cpp

Lines changed: 26 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5775,8 +5775,8 @@ bool FailureDiagnosis::diagnoseClosureExpr(
57755775
// If we have a contextual type available for this closure, apply it to the
57765776
// ParamDecls in our parameter list. This ensures that any uses of them get
57775777
// appropriate types.
5778-
if (contextualType && contextualType->is<AnyFunctionType>()) {
5779-
auto fnType = contextualType->getAs<AnyFunctionType>();
5778+
if (contextualType && contextualType->is<FunctionType>()) {
5779+
auto fnType = contextualType->getAs<FunctionType>();
57805780
auto *params = CE->getParameters();
57815781
auto inferredArgs = fnType->getParams();
57825782

@@ -5791,36 +5791,6 @@ bool FailureDiagnosis::diagnoseClosureExpr(
57915791
unsigned inferredArgCount = inferredArgs.size();
57925792

57935793
if (actualArgCount != inferredArgCount) {
5794-
// If the closure didn't specify any arguments and it is in a context that
5795-
// needs some, produce a fixit to turn "{...}" into "{ _,_ in ...}".
5796-
if (actualArgCount == 0 && CE->getInLoc().isInvalid()) {
5797-
auto diag =
5798-
diagnose(CE->getStartLoc(), diag::closure_argument_list_missing,
5799-
inferredArgCount);
5800-
std::string fixText; // Let's provide fixits for up to 10 args.
5801-
5802-
if (inferredArgCount <= 10) {
5803-
fixText += " _";
5804-
for (unsigned i = 0; i < inferredArgCount - 1; i ++) {
5805-
fixText += ",_";
5806-
}
5807-
fixText += " in ";
5808-
}
5809-
5810-
if (!fixText.empty()) {
5811-
// Determine if there is already a space after the { in the closure to
5812-
// make sure we introduce the right whitespace.
5813-
auto afterBrace = CE->getStartLoc().getAdvancedLoc(1);
5814-
auto text = CS.TC.Context.SourceMgr.extractText({afterBrace, 1});
5815-
if (text.size() == 1 && text == " ")
5816-
fixText = fixText.erase(fixText.size() - 1);
5817-
else
5818-
fixText = fixText.erase(0, 1);
5819-
diag.fixItInsertAfter(CE->getStartLoc(), fixText);
5820-
}
5821-
return true;
5822-
}
5823-
58245794
if (inferredArgCount == 1 && actualArgCount > 1) {
58255795
auto *argTupleTy = inferredArgs.front().getOldType()->getAs<TupleType>();
58265796
// Let's see if inferred argument is actually a tuple inside of Paren.
@@ -5956,49 +5926,33 @@ bool FailureDiagnosis::diagnoseClosureExpr(
59565926
}
59575927
}
59585928

5959-
bool onlyAnonymousParams =
5960-
std::all_of(params->begin(), params->end(), [](ParamDecl *param) {
5961-
return !param->hasName();
5962-
});
5963-
5964-
// Okay, the wrong number of arguments was used, complain about that.
5965-
// Before doing so, strip attributes off the function type so that they
5966-
// don't confuse the issue.
5967-
fnType = FunctionType::get(fnType->getParams(), fnType->getResult(),
5968-
fnType->getExtInfo());
5969-
auto diag = diagnose(
5970-
params->getStartLoc(), diag::closure_argument_list_tuple, fnType,
5971-
inferredArgCount, actualArgCount, (actualArgCount == 1));
5972-
5973-
// If closure expects no parameters but N was given,
5974-
// and all of them are anonymous let's suggest removing them.
5975-
if (inferredArgCount == 0 && onlyAnonymousParams) {
5976-
auto inLoc = CE->getInLoc();
5977-
auto &sourceMgr = CS.getASTContext().SourceMgr;
5978-
5979-
if (inLoc.isValid())
5980-
diag.fixItRemoveChars(params->getStartLoc(),
5981-
Lexer::getLocForEndOfToken(sourceMgr, inLoc));
5929+
// Extraneous arguments.
5930+
if (inferredArgCount < actualArgCount) {
5931+
auto diag = diagnose(
5932+
params->getStartLoc(), diag::closure_argument_list_tuple, fnType,
5933+
inferredArgCount, actualArgCount, (actualArgCount == 1));
5934+
5935+
bool onlyAnonymousParams =
5936+
std::all_of(params->begin(), params->end(),
5937+
[](ParamDecl *param) { return !param->hasName(); });
5938+
5939+
// If closure expects no parameters but N was given,
5940+
// and all of them are anonymous let's suggest removing them.
5941+
if (inferredArgCount == 0 && onlyAnonymousParams) {
5942+
auto inLoc = CE->getInLoc();
5943+
auto &sourceMgr = CS.getASTContext().SourceMgr;
5944+
5945+
if (inLoc.isValid())
5946+
diag.fixItRemoveChars(params->getStartLoc(),
5947+
Lexer::getLocForEndOfToken(sourceMgr, inLoc));
5948+
}
59825949
return true;
59835950
}
59845951

5985-
// If the number of parameters is less than number of inferred
5986-
// and all of the parameters are anonymous, let's suggest a fix-it
5987-
// with the rest of the missing parameters.
5988-
if (actualArgCount < inferredArgCount) {
5989-
SmallString<32> fixIt;
5990-
llvm::raw_svector_ostream OS(fixIt);
5991-
5992-
OS << ",";
5993-
auto numMissing = inferredArgCount - actualArgCount;
5994-
for (unsigned i = 0; i != numMissing; ++i) {
5995-
OS << ((onlyAnonymousParams) ? "_" : "<#arg#>");
5996-
OS << ((i == numMissing - 1) ? " " : ",");
5997-
}
5998-
5999-
diag.fixItInsertAfter(params->getEndLoc(), OS.str());
6000-
}
6001-
return true;
5952+
MissingArgumentsFailure failure(
5953+
expr, CS, fnType, inferredArgCount - actualArgCount,
5954+
CS.getConstraintLocator(CE, ConstraintLocator::ContextualType));
5955+
return failure.diagnoseAsError();
60025956
}
60035957

60045958
// Coerce parameter types here only if there are no unresolved

lib/Sema/CSDiagnostics.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,3 +1931,81 @@ bool ImplicitInitOnNonConstMetatypeFailure::diagnoseAsError() {
19311931
.fixItInsert(loc, ".init");
19321932
return true;
19331933
}
1934+
1935+
bool MissingArgumentsFailure::diagnoseAsError() {
1936+
auto *locator = getLocator();
1937+
auto path = locator->getPath();
1938+
1939+
// TODO: Currently this is only intended to diagnose contextual failures.
1940+
if (!(path.back().getKind() == ConstraintLocator::ApplyArgToParam ||
1941+
path.back().getKind() == ConstraintLocator::ContextualType))
1942+
return false;
1943+
1944+
if (auto *closure = dyn_cast<ClosureExpr>(getAnchor()))
1945+
return diagnoseTrailingClosure(closure);
1946+
1947+
return false;
1948+
}
1949+
1950+
bool MissingArgumentsFailure::diagnoseTrailingClosure(ClosureExpr *closure) {
1951+
auto diff = Fn->getNumParams() - NumSynthesized;
1952+
1953+
// If the closure didn't specify any arguments and it is in a context that
1954+
// needs some, produce a fixit to turn "{...}" into "{ _,_ in ...}".
1955+
if (diff == 0) {
1956+
auto diag =
1957+
emitDiagnostic(closure->getStartLoc(),
1958+
diag::closure_argument_list_missing, NumSynthesized);
1959+
1960+
std::string fixText; // Let's provide fixits for up to 10 args.
1961+
if (Fn->getNumParams() <= 10) {
1962+
fixText += " ";
1963+
interleave(
1964+
Fn->getParams(),
1965+
[&fixText](const AnyFunctionType::Param &param) { fixText += '_'; },
1966+
[&fixText] { fixText += ','; });
1967+
fixText += " in ";
1968+
}
1969+
1970+
if (!fixText.empty()) {
1971+
// Determine if there is already a space after the { in the closure to
1972+
// make sure we introduce the right whitespace.
1973+
auto afterBrace = closure->getStartLoc().getAdvancedLoc(1);
1974+
auto text = getASTContext().SourceMgr.extractText({afterBrace, 1});
1975+
if (text.size() == 1 && text == " ")
1976+
fixText = fixText.erase(fixText.size() - 1);
1977+
else
1978+
fixText = fixText.erase(0, 1);
1979+
diag.fixItInsertAfter(closure->getStartLoc(), fixText);
1980+
}
1981+
1982+
return true;
1983+
}
1984+
1985+
auto params = closure->getParameters();
1986+
bool onlyAnonymousParams =
1987+
std::all_of(params->begin(), params->end(),
1988+
[](ParamDecl *param) { return !param->hasName(); });
1989+
1990+
auto diag =
1991+
emitDiagnostic(params->getStartLoc(), diag::closure_argument_list_tuple,
1992+
resolveType(Fn), Fn->getNumParams(), diff, diff == 1);
1993+
1994+
// If the number of parameters is less than number of inferred
1995+
// let's try to suggest a fix-it with the rest of the missing parameters.
1996+
if (!closure->hasExplicitResultType() &&
1997+
closure->getInLoc().isValid()) {
1998+
SmallString<32> fixIt;
1999+
llvm::raw_svector_ostream OS(fixIt);
2000+
2001+
OS << ",";
2002+
for (unsigned i = 0; i != NumSynthesized; ++i) {
2003+
OS << ((onlyAnonymousParams) ? "_" : "<#arg#>");
2004+
OS << ((i == NumSynthesized - 1) ? " " : ",");
2005+
}
2006+
2007+
diag.fixItInsertAfter(params->getEndLoc(), OS.str());
2008+
}
2009+
2010+
return true;
2011+
}

lib/Sema/CSDiagnostics.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,28 @@ class ImplicitInitOnNonConstMetatypeFailure final
877877
bool diagnoseAsError() override;
878878
};
879879

880+
class MissingArgumentsFailure final : public FailureDiagnostic {
881+
using Param = AnyFunctionType::Param;
882+
883+
FunctionType *Fn;
884+
unsigned NumSynthesized;
885+
886+
public:
887+
MissingArgumentsFailure(Expr *root, ConstraintSystem &cs,
888+
FunctionType *funcType,
889+
unsigned numSynthesized,
890+
ConstraintLocator *locator)
891+
: FailureDiagnostic(root, cs, locator), Fn(funcType),
892+
NumSynthesized(numSynthesized) {}
893+
894+
bool diagnoseAsError() override;
895+
896+
private:
897+
/// If missing arguments come from trailing closure,
898+
/// let's produce tailored diagnostics.
899+
bool diagnoseTrailingClosure(ClosureExpr *closure);
900+
};
901+
880902
} // end namespace constraints
881903
} // end namespace swift
882904

lib/Sema/CSFix.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,18 @@ AllowInvalidInitRef::create(RefKind kind, ConstraintSystem &cs, Type baseTy,
342342
return new (cs.getAllocator()) AllowInvalidInitRef(
343343
cs, kind, baseTy, init, isStaticallyDerived, baseRange, locator);
344344
}
345+
346+
bool AddMissingArguments::diagnose(Expr *root, bool asNote) const {
347+
MissingArgumentsFailure failure(root, getConstraintSystem(), Fn,
348+
NumSynthesized, getLocator());
349+
return failure.diagnose(asNote);
350+
}
351+
352+
AddMissingArguments *
353+
AddMissingArguments::create(ConstraintSystem &cs, FunctionType *funcType,
354+
llvm::ArrayRef<Param> synthesizedArgs,
355+
ConstraintLocator *locator) {
356+
unsigned size = totalSizeToAlloc<Param>(synthesizedArgs.size());
357+
void *mem = cs.getAllocator().Allocate(size, alignof(AddMissingArguments));
358+
return new (mem) AddMissingArguments(cs, funcType, synthesizedArgs, locator);
359+
}

lib/Sema/CSFix.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ enum class FixKind : uint8_t {
122122
/// derived (rather than an arbitrary value of metatype type) or the
123123
/// referenced constructor must be required.
124124
AllowInvalidInitRef,
125+
126+
/// If there are fewer arguments than parameters, let's fix that up
127+
/// by adding new arguments to the list represented as type variables.
128+
AddMissingArguments,
125129
};
126130

127131
class ConstraintFix {
@@ -614,6 +618,45 @@ class AllowInvalidInitRef final : public ConstraintFix {
614618
ConstraintLocator *locator);
615619
};
616620

621+
class AddMissingArguments final
622+
: public ConstraintFix,
623+
private llvm::TrailingObjects<AddMissingArguments,
624+
AnyFunctionType::Param> {
625+
friend TrailingObjects;
626+
627+
using Param = AnyFunctionType::Param;
628+
629+
FunctionType *Fn;
630+
unsigned NumSynthesized;
631+
632+
AddMissingArguments(ConstraintSystem &cs, FunctionType *funcType,
633+
llvm::ArrayRef<AnyFunctionType::Param> synthesizedArgs,
634+
ConstraintLocator *locator)
635+
: ConstraintFix(cs, FixKind::AddMissingArguments, locator), Fn(funcType),
636+
NumSynthesized(synthesizedArgs.size()) {
637+
std::uninitialized_copy(synthesizedArgs.begin(), synthesizedArgs.end(),
638+
getSynthesizedArgumentsBuf().begin());
639+
}
640+
641+
public:
642+
std::string getName() const override { return "synthesize missing argument(s)"; }
643+
644+
ArrayRef<Param> getSynthesizedArguments() const {
645+
return {getTrailingObjects<Param>(), NumSynthesized};
646+
}
647+
648+
bool diagnose(Expr *root, bool asNote = false) const override;
649+
650+
static AddMissingArguments *create(ConstraintSystem &cs, FunctionType *fnType,
651+
llvm::ArrayRef<Param> synthesizedArgs,
652+
ConstraintLocator *locator);
653+
654+
private:
655+
MutableArrayRef<Param> getSynthesizedArgumentsBuf() {
656+
return {getTrailingObjects<Param>(), NumSynthesized};
657+
}
658+
};
659+
617660
} // end namespace constraints
618661
} // end namespace swift
619662

0 commit comments

Comments
 (0)