Skip to content

Commit 72fe29f

Browse files
committed
[CS] Add custom diagnostic for missing result builder element
If we have a single missing argument for an empty `buildBlock` call, emit a custom diagnostic.
1 parent 5eb3062 commit 72fe29f

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,11 @@ NOTE(candidate_with_extraneous_args,none,
16051605
"candidate %0 has %1 parameter%s1, but context %2 has %3",
16061606
(Type, unsigned, Type, unsigned))
16071607

1608+
ERROR(result_builder_missing_element,none,
1609+
"expected expression in result builder %0", (DeclName))
1610+
ERROR(result_builder_missing_element_of_type,none,
1611+
"expected expression of type %0 in result builder %1", (Type, DeclName))
1612+
16081613
ERROR(no_accessible_initializers,none,
16091614
"%0 cannot be constructed because it has no accessible initializers",
16101615
(Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ FailureDiagnostic::getArgumentListFor(ConstraintLocator *locator) const {
177177
return S.getArgumentList(locator);
178178
}
179179

180+
StringRef FailureDiagnostic::getEditorPlaceholder(
181+
StringRef description, Type ty,
182+
llvm::SmallVectorImpl<char> &scratch) const {
183+
llvm::raw_svector_ostream OS(scratch);
184+
OS << "<#";
185+
if (!ty || ty->is<UnresolvedType>()) {
186+
OS << description;
187+
} else {
188+
OS << "T##";
189+
ty.print(OS);
190+
}
191+
OS << "#>";
192+
return StringRef(scratch.data(), scratch.size());
193+
}
194+
180195
Expr *FailureDiagnostic::getBaseExprFor(const Expr *anchor) const {
181196
if (!anchor)
182197
return nullptr;
@@ -5177,6 +5192,9 @@ bool MissingArgumentsFailure::diagnoseAsError() {
51775192
return true;
51785193
}
51795194

5195+
if (diagnoseMissingResultBuilderElement())
5196+
return true;
5197+
51805198
if (diagnoseInvalidTupleDestructuring())
51815199
return true;
51825200

@@ -5514,6 +5532,55 @@ bool MissingArgumentsFailure::diagnoseClosure(const ClosureExpr *closure) {
55145532
return true;
55155533
}
55165534

5535+
bool MissingArgumentsFailure::diagnoseMissingResultBuilderElement() const {
5536+
auto &ctx = getASTContext();
5537+
5538+
// Only handle a single missing argument in an empty builder for now. This
5539+
// should be the most common case though since most builders support N >= 1
5540+
// elements.
5541+
if (SynthesizedArgs.size() != 1)
5542+
return false;
5543+
5544+
auto *call = getAsExpr<CallExpr>(getRawAnchor());
5545+
if (!call || !call->isImplicit() || !call->getArgs()->empty())
5546+
return false;
5547+
5548+
auto *UDE = dyn_cast<UnresolvedDotExpr>(call->getFn());
5549+
if (!UDE || !isResultBuilderMethodReference(ctx, UDE))
5550+
return false;
5551+
5552+
auto overload = getCalleeOverloadChoiceIfAvailable(getLocator());
5553+
if (!overload)
5554+
return false;
5555+
5556+
auto *decl = overload->choice.getDeclOrNull();
5557+
if (!decl || decl->getBaseName() != ctx.Id_buildBlock)
5558+
return false;
5559+
5560+
auto resultBuilder =
5561+
getType(UDE->getBase())->getMetatypeInstanceType()->getAnyNominal();
5562+
if (!resultBuilder)
5563+
return false;
5564+
5565+
auto paramType = resolveType(SynthesizedArgs.front().param.getPlainType());
5566+
5567+
SmallString<64> scratch;
5568+
auto fixIt = getEditorPlaceholder("result", paramType, scratch);
5569+
auto fixItLoc = call->getStartLoc();
5570+
5571+
if (paramType->is<UnresolvedType>()) {
5572+
emitDiagnostic(diag::result_builder_missing_element,
5573+
resultBuilder->getName())
5574+
.fixItInsertAfter(fixItLoc, fixIt);
5575+
} else {
5576+
emitDiagnostic(diag::result_builder_missing_element_of_type, paramType,
5577+
resultBuilder->getName())
5578+
.fixItInsertAfter(fixItLoc, fixIt);
5579+
}
5580+
emitDiagnosticAt(decl, diag::decl_declared_here, decl);
5581+
return true;
5582+
}
5583+
55175584
bool MissingArgumentsFailure::diagnoseInvalidTupleDestructuring() const {
55185585
auto *locator = getLocator();
55195586
if (!locator->isLastElement<LocatorPathElt::ApplyArgument>())

lib/Sema/CSDiagnostics.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,11 @@ class FailureDiagnostic {
203203
[](GenericTypeParamType *, Type) {});
204204

205205
bool conformsToKnownProtocol(Type type, KnownProtocolKind protocol) const;
206+
207+
/// Retrieve an editor placeholder with a given description, or a given
208+
/// type if specified.
209+
StringRef getEditorPlaceholder(StringRef description, Type ty,
210+
llvm::SmallVectorImpl<char> &scratch) const;
206211
};
207212

208213
/// Base class for all of the diagnostics related to generic requirement
@@ -1505,6 +1510,9 @@ class MissingArgumentsFailure final : public FailureDiagnostic {
15051510
/// let's produce tailored diagnostics.
15061511
bool diagnoseClosure(const ClosureExpr *closure);
15071512

1513+
/// Diagnose a single missing argument to a buildBlock call.
1514+
bool diagnoseMissingResultBuilderElement() const;
1515+
15081516
/// Diagnose cases when instead of multiple distinct arguments
15091517
/// call got a single tuple argument with expected arity/types.
15101518
bool diagnoseInvalidTupleDestructuring() const;

test/Constraints/result_builder_diags.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,3 +1013,19 @@ func test_partially_resolved_closure_params() {
10131013
42
10141014
}
10151015
}
1016+
1017+
func testMissingElementInEmptyBuilder() {
1018+
@resultBuilder
1019+
struct SingleElementBuilder {
1020+
static func buildBlock<T>(_ x: T) -> T { x }
1021+
// expected-note@-1 2{{'buildBlock' declared here}}
1022+
}
1023+
1024+
func test1(@SingleElementBuilder fn: () -> Int) {}
1025+
test1 {}
1026+
// expected-error@-1 {{expected expression of type 'Int' in result builder 'SingleElementBuilder'}} {{10-10=<#T##Int#>}}
1027+
1028+
@SingleElementBuilder
1029+
func test2() -> Int {}
1030+
// expected-error@-1 {{expected expression of type 'Int' in result builder 'SingleElementBuilder'}} {{24-24=<#T##Int#>}}
1031+
}

validation-test/Sema/SwiftUI/rdar88256059.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ struct MyView: View {
99

1010
var body: some View {
1111
Table(self.data) {
12-
// expected-error@-1 {{missing argument for parameter #1 in call}}
12+
// expected-error@-1 {{expected expression in result builder 'TableColumnBuilder'}} {{23-23=<#result#>}}
1313
// expected-error@-2 {{cannot infer return type of empty closure}} {{23-23=<#result#>}}
1414
}
1515
}

0 commit comments

Comments
 (0)