Skip to content

Commit 81d8ee0

Browse files
committed
[Sema] Check that at least one buildBlock is callable given arg labels
When validating an @_functionBuilder type, reject types which only provide `buildBlock` declarations with argument labels (which don't have default values). These declarations will never be callable by the `buildBlock` calls produced by the function builder transformation, which does not insert any argument labels for `buildBlock` calls.
1 parent 992ffa6 commit 81d8ee0

File tree

5 files changed

+125
-43
lines changed

5 files changed

+125
-43
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5309,6 +5309,9 @@ NOTE(function_builder_non_static_buildblock, none,
53095309
NOTE(function_builder_buildblock_enum_case, none,
53105310
"enum case 'buildBlock' cannot be used to satisfy the function builder "
53115311
"requirement", ())
5312+
NOTE(function_builder_buildblock_arg_labels, none,
5313+
"potential match 'buildBlock' can never be called implicitly because it "
5314+
"has at least one labeled argument", ())
53125315
NOTE(function_builder_buildblock_not_static_method, none,
53135316
"potential match 'buildBlock' is not a static method", ())
53145317

lib/Sema/BuilderTransform.cpp

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,51 +1881,83 @@ std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
18811881

18821882
bool TypeChecker::typeSupportsBuilderOp(
18831883
Type builderType, DeclContext *dc, Identifier fnName,
1884-
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
1884+
ArrayRef<Identifier> argLabels, SmallVectorImpl<FuncDecl *> *matches,
1885+
SmallVectorImpl<BuilderOpMismatch> *nearMisses) {
1886+
1887+
auto recordNearMiss = [&](ValueDecl *vd, BuilderOpMismatchKind kind) {
1888+
if (nearMisses)
1889+
nearMisses->emplace_back(vd, kind);
1890+
};
1891+
18851892
bool foundMatch = false;
18861893
SmallVector<ValueDecl *, 4> foundDecls;
18871894
dc->lookupQualified(
18881895
builderType, DeclNameRef(fnName),
18891896
NL_QualifiedDefault | NL_ProtocolMembers, foundDecls);
18901897
for (auto decl : foundDecls) {
1891-
if (auto func = dyn_cast<FuncDecl>(decl)) {
1892-
// Function must be static.
1893-
if (!func->isStatic())
1894-
continue;
1898+
if (isa<EnumElementDecl>(decl)) {
1899+
recordNearMiss(decl, BuilderOpMismatchKind::EnumCase);
1900+
continue;
1901+
} else if (!isa<FuncDecl>(decl)) {
1902+
recordNearMiss(decl, BuilderOpMismatchKind::Other);
1903+
continue;
1904+
}
18951905

1896-
// Function must have the right argument labels, if provided.
1897-
if (!argLabels.empty()) {
1898-
auto funcLabels = func->getName().getArgumentNames();
1899-
if (argLabels.size() > funcLabels.size() ||
1900-
funcLabels.slice(0, argLabels.size()) != argLabels)
1901-
continue;
1906+
auto *func = cast<FuncDecl>(decl);
1907+
// Function must be static.
1908+
if (!func->isStatic()) {
1909+
recordNearMiss(func, BuilderOpMismatchKind::InstanceMethod);
1910+
continue;
1911+
}
1912+
1913+
// Function must have the right argument labels, if provided.
1914+
if (!argLabels.empty()) {
1915+
auto funcLabels = func->getName().getArgumentNames();
1916+
if (argLabels.size() > funcLabels.size() ||
1917+
funcLabels.slice(0, argLabels.size()) != argLabels) {
1918+
recordNearMiss(func, BuilderOpMismatchKind::ArgumentLabelMismatch);
1919+
continue;
19021920
}
1921+
}
19031922

1904-
foundMatch = true;
1905-
break;
1923+
// After the labels provided by the builder transform, any additional
1924+
// labeled parameters must have a default value (or else the function will
1925+
// be uncallable by the builder transformation).
1926+
auto params = func->getParameters()->getArray();
1927+
auto isLabeledUndefaulted = [&](ParamDecl *param) -> bool {
1928+
return !param->isDefaultArgument() && !param->getArgumentName().empty();
1929+
};
1930+
if (llvm::any_of(params.drop_front(argLabels.size()),
1931+
isLabeledUndefaulted)) {
1932+
recordNearMiss(func,
1933+
BuilderOpMismatchKind::ExtraneousUndefaultedArgument);
1934+
continue;
19061935
}
1907-
}
19081936

1909-
if (allResults)
1910-
allResults->append(foundDecls.begin(), foundDecls.end());
1937+
foundMatch = true;
1938+
1939+
// If we're not recording any candidates or matches, we can bail out as soon
1940+
// as we find the first match.
1941+
if (!nearMisses && !matches)
1942+
break;
1943+
1944+
if (matches)
1945+
matches->push_back(func);
1946+
}
19111947

19121948
return foundMatch;
19131949
}
19141950

19151951
Type swift::inferFunctionBuilderComponentType(NominalTypeDecl *builder) {
19161952
Type componentType;
19171953

1918-
SmallVector<ValueDecl *, 4> potentialMatches;
1954+
SmallVector<FuncDecl *, 4> matches;
19191955
ASTContext &ctx = builder->getASTContext();
19201956
bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp(
19211957
builder->getDeclaredInterfaceType(), builder, ctx.Id_buildBlock,
1922-
/*argLabels=*/{}, &potentialMatches);
1958+
/*argLabels=*/{}, &matches);
19231959
if (supportsBuildBlock) {
1924-
for (auto decl : potentialMatches) {
1925-
auto func = dyn_cast<FuncDecl>(decl);
1926-
if (!func || !func->isStatic())
1927-
continue;
1928-
1960+
for (auto func : matches) {
19291961
// If we haven't seen a component type before, gather it.
19301962
if (!componentType) {
19311963
componentType = func->getResultInterfaceType();

lib/Sema/TypeCheckAttr.cpp

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3022,10 +3022,10 @@ void AttributeChecker::visitPropertyWrapperAttr(PropertyWrapperAttr *attr) {
30223022

30233023
void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) {
30243024
auto *nominal = dyn_cast<NominalTypeDecl>(D);
3025-
SmallVector<ValueDecl *, 4> potentialMatches;
3025+
SmallVector<TypeChecker::BuilderOpMismatch, 4> nearMisses;
30263026
bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp(
30273027
nominal->getDeclaredType(), nominal, D->getASTContext().Id_buildBlock,
3028-
/*argLabels=*/{}, &potentialMatches);
3028+
/*argLabels=*/{}, nullptr, &nearMisses);
30293029

30303030
if (!supportsBuildBlock) {
30313031
{
@@ -3038,7 +3038,7 @@ void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) {
30383038
Type componentType;
30393039
std::tie(buildInsertionLoc, stubIndent, componentType) =
30403040
determineFunctionBuilderBuildFixItInfo(nominal);
3041-
if (buildInsertionLoc.isValid() && potentialMatches.empty()) {
3041+
if (buildInsertionLoc.isValid() && nearMisses.empty()) {
30423042
std::string fixItString;
30433043
{
30443044
llvm::raw_string_ostream out(fixItString);
@@ -3054,19 +3054,35 @@ void AttributeChecker::visitFunctionBuilderAttr(FunctionBuilderAttr *attr) {
30543054

30553055
// For any close matches, attempt to explain to the user why they aren't
30563056
// valid.
3057-
for (auto *member : potentialMatches) {
3058-
if (member->isStatic() && isa<FuncDecl>(member))
3059-
continue;
3057+
for (auto nearMiss : nearMisses) {
3058+
auto *member = nearMiss.first;
3059+
auto note = diag::function_builder_buildblock_not_static_method;
3060+
std::function<void(InFlightDiagnostic &)> fixIt;
3061+
3062+
switch (nearMiss.second) {
3063+
case TypeChecker::BuilderOpMismatchKind::InstanceMethod:
3064+
if (member->getDeclContext()->getSelfNominalTypeDecl() == nominal) {
3065+
note = diag::function_builder_non_static_buildblock;
3066+
fixIt = [&](InFlightDiagnostic &diag) {
3067+
diag.fixItInsert(member->getAttributeInsertionLoc(true), "static ");
3068+
};
3069+
}
3070+
break;
3071+
case TypeChecker::BuilderOpMismatchKind::EnumCase:
3072+
note = diag::function_builder_buildblock_enum_case;
3073+
break;
3074+
case TypeChecker::BuilderOpMismatchKind::ExtraneousUndefaultedArgument:
3075+
note = diag::function_builder_buildblock_arg_labels;
3076+
break;
3077+
case TypeChecker::BuilderOpMismatchKind::Other:
3078+
break;
3079+
case TypeChecker::BuilderOpMismatchKind::ArgumentLabelMismatch:
3080+
llvm_unreachable("buildBlock should not check argument labels");
3081+
}
30603082

3061-
if (isa<FuncDecl>(member) &&
3062-
member->getDeclContext()->getSelfNominalTypeDecl() == nominal)
3063-
diagnose(member->getLoc(), diag::function_builder_non_static_buildblock)
3064-
.fixItInsert(member->getAttributeInsertionLoc(true), "static ");
3065-
else if (isa<EnumElementDecl>(member))
3066-
diagnose(member->getLoc(), diag::function_builder_buildblock_enum_case);
3067-
else
3068-
diagnose(member->getLoc(),
3069-
diag::function_builder_buildblock_not_static_method);
3083+
auto diag = diagnose(member->getLoc(), note);
3084+
if (fixIt)
3085+
fixIt(diag);
30703086
}
30713087
}
30723088
}

lib/Sema/TypeChecker.h

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,12 +1222,37 @@ bool requireArrayLiteralIntrinsics(ASTContext &ctx, SourceLoc loc);
12221222
/// an \c UnresolvedMemberExpr, \c nullptr is returned.
12231223
UnresolvedMemberExpr *getUnresolvedMemberChainBase(Expr *expr);
12241224

1225+
enum class BuilderOpMismatchKind : unsigned {
1226+
/// The potential match was an instance method rather than a static method.
1227+
InstanceMethod,
1228+
1229+
/// The potential match was an enum case.
1230+
EnumCase,
1231+
1232+
/// The potential match did not have the correct argument labels.
1233+
ArgumentLabelMismatch,
1234+
1235+
/// The potential match had an argument that would not be provided by the
1236+
/// function builder transformation.
1237+
ExtraneousUndefaultedArgument,
1238+
1239+
/// Catchall for mismatches which could not be classified more specifically.
1240+
Other
1241+
};
1242+
1243+
using BuilderOpMismatch = std::pair<ValueDecl *, BuilderOpMismatchKind>;
1244+
12251245
/// Checks whether a function builder type has a well-formed function builder
1226-
/// method with the given name. If provided and non-empty, the argument labels
1227-
/// are verified against any candidates.
1228-
bool typeSupportsBuilderOp(Type builderType, DeclContext *dc, Identifier fnName,
1229-
ArrayRef<Identifier> argLabels = {},
1230-
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
1246+
/// method with the given name. If provided and non-empty, the \c argLabels
1247+
/// are verified against any candidates. If provided, \c nearMisses will be
1248+
/// populated with all members which have the correct base name, but were not
1249+
/// considered matches (as well as the reason they were not considered matches,
1250+
/// if known).
1251+
bool typeSupportsBuilderOp(
1252+
Type builderType, DeclContext *dc, Identifier fnName,
1253+
ArrayRef<Identifier> argLabels = {},
1254+
SmallVectorImpl<FuncDecl *> *matches = nullptr,
1255+
SmallVectorImpl<BuilderOpMismatch> *nearMisses = nullptr);
12311256
}; // namespace TypeChecker
12321257

12331258
/// Temporary on-stack storage and unescaping for encoded diagnostic

test/decl/var/function_builders.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ enum InvalidBuilder11 { // expected-error {{function builder must provide at lea
199199
case buildBlock(Any) // expected-note {{enum case 'buildBlock' cannot be used to satisfy the function builder requirement}}
200200
}
201201

202+
@_functionBuilder
203+
struct InvalidBuilder12 { // expected-error {{function builder must provide at least one static 'buildBlock' method}}
204+
static func buildBlock(exprs: Any...) -> Int { return exprs.count } // expected-note {{potential match 'buildBlock' can never be called implicitly because it has at least one labeled argument}}
205+
}
206+
202207
struct S {
203208
@ValidBuilder1 var v1: Int { 1 }
204209
@ValidBuilder2 var v2: Int { 1 }
@@ -216,4 +221,5 @@ struct S {
216221
@InvalidBuilder9 var i9: Int { 1 }
217222
@InvalidBuilder10 var i10: Int { 1 }
218223
@InvalidBuilder11 var i11: InvalidBuilder11 { 1 }
224+
@InvalidBuilder12 var i12: Int { 1 } // expected-error {{missing argument label 'exprs:' in call}}
219225
}

0 commit comments

Comments
 (0)