Skip to content

Commit d8ebabb

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 684621d commit d8ebabb

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;
@@ -5178,6 +5193,9 @@ bool MissingArgumentsFailure::diagnoseAsError() {
51785193
return true;
51795194
}
51805195

5196+
if (diagnoseMissingResultBuilderElement())
5197+
return true;
5198+
51815199
if (diagnoseInvalidTupleDestructuring())
51825200
return true;
51835201

@@ -5515,6 +5533,55 @@ bool MissingArgumentsFailure::diagnoseClosure(const ClosureExpr *closure) {
55155533
return true;
55165534
}
55175535

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