Skip to content

[ResultBuilders] Improve diagnostics of unsupported variable declarations #38878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5882,6 +5882,12 @@ NOTE(result_builder_missing_build_array, none,
NOTE(result_builder_missing_build_limited_availability, none,
"add 'buildLimitedAvailability(_:)' to the result "
"builder %0 to erase type information for less-available types", (Type))
ERROR(result_builder_requires_explicit_var_initialization,none,
"local variable%select{| '%1'}0 requires explicit initializer to be used with "
"result builder %2", (bool, StringRef, DeclName))
ERROR(cannot_declare_computed_var_in_result_builder,none,
"cannot declare local %select{lazy|wrapped|computed|observed}0 variable "
"in result builder", (unsigned))

//------------------------------------------------------------------------------
// MARK: Tuple Shuffle Diagnostics
Expand Down
76 changes: 76 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5870,8 +5870,84 @@ static bool hasMissingElseInChain(IfStmt *ifStmt) {
return false;
}

bool SkipUnhandledConstructInResultBuilderFailure::diagnosePatternBinding(
PatternBindingDecl *PB) const {
bool diagnosed = false;

for (unsigned i : range(PB->getNumPatternEntries())) {
auto *pattern = PB->getPattern(i);

// Each variable bound by the pattern must be stored and cannot have
// observers.
{
SmallVector<VarDecl *, 8> variables;
pattern->collectVariables(variables);

bool diagnosedStorage = false;
for (auto *var : variables)
diagnosedStorage |= diagnoseStorage(var);

// if storage has been diagnosed, let's move to the next entry.
if (diagnosedStorage) {
diagnosed = true;
continue;
}
}

// Diagnose all of the patterns without explicit initializers.
if (PB->isExplicitlyInitialized(i))
continue;

StringRef name;

if (auto *TP = dyn_cast<TypedPattern>(pattern)) {
if (auto *NP = dyn_cast<NamedPattern>(TP->getSubPattern()))
name = NP->getNameStr();
}

emitDiagnosticAt(pattern->getLoc(),
diag::result_builder_requires_explicit_var_initialization,
!name.empty(), name, builder->getName())
.fixItInsertAfter(pattern->getEndLoc(), " = <#value#>");

diagnosed = true;
}

return diagnosed;
}

bool SkipUnhandledConstructInResultBuilderFailure::diagnoseStorage(
VarDecl *var) const {
enum class PropertyKind : unsigned { lazy, wrapped, computed, observed };

if (var->getImplInfo().isSimpleStored())
return false;

PropertyKind kind;
if (var->getAttrs().hasAttribute<LazyAttr>()) {
kind = PropertyKind::lazy;
} else if (var->hasAttachedPropertyWrapper()) {
kind = PropertyKind::wrapped;
} else if (var->hasObservers()) {
kind = PropertyKind::observed;
} else {
kind = PropertyKind::computed;
}

emitDiagnosticAt(var, diag::cannot_declare_computed_var_in_result_builder,
static_cast<unsigned>(kind));
return true;
}

void SkipUnhandledConstructInResultBuilderFailure::diagnosePrimary(
bool asNote) {

if (auto *decl = unhandled.dyn_cast<Decl *>()) {
auto *PB = dyn_cast<PatternBindingDecl>(decl);
if (PB && diagnosePatternBinding(PB))
return;
}

if (auto stmt = unhandled.dyn_cast<Stmt *>()) {
emitDiagnostic(asNote ? diag::note_result_builder_control_flow
: diag::result_builder_control_flow,
Expand Down
7 changes: 7 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,13 @@ class SkipUnhandledConstructInResultBuilderFailure final

bool diagnoseAsError() override;
bool diagnoseAsNote() override;

private:
/// Tailored diagnostics for an unsupported variable declaration.
bool diagnosePatternBinding(PatternBindingDecl *PB) const;

/// Tailored diagnostics for lazy/wrapped/computed variable declarations.
bool diagnoseStorage(VarDecl *var) const;
};

/// Diagnose situation when a single "tuple" parameter is given N arguments e.g.
Expand Down
12 changes: 10 additions & 2 deletions test/Constraints/result_builder_diags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ enum Either<T,U> {
}

@resultBuilder
struct TupleBuilder { // expected-note 2 {{struct 'TupleBuilder' declared here}}
struct TupleBuilder { // expected-note 3 {{struct 'TupleBuilder' declared here}}
static func buildBlock() -> () { }

static func buildBlock<T1>(_ t1: T1) -> T1 {
Expand Down Expand Up @@ -99,10 +99,18 @@ func testDiags() {
tuplify(true) { _ in
17
let x = 17
let y: Int // expected-error{{closure containing a declaration cannot be used with result builder 'TupleBuilder'}}
let y: Int // expected-error{{local variable 'y' requires explicit initializer to be used with result builder 'TupleBuilder'}} {{15-15= = <#value#>}}
x + 25
}

tuplify(true) { _ in
17
let y: Int, z: String
// expected-error@-1 {{local variable 'y' requires explicit initializer to be used with result builder 'TupleBuilder'}} {{15-15= = <#value#>}}
// expected-error@-2 {{local variable 'z' requires explicit initializer to be used with result builder 'TupleBuilder'}} {{26-26= = <#value#>}}
y + 25
}

// Statements unsupported by the particular builder.
tuplifyWithoutIf(true) {
if $0 { // expected-error{{closure containing control flow statement cannot be used with result builder 'TupleBuilderWithoutIf'}}
Expand Down
10 changes: 5 additions & 5 deletions test/Constraints/result_builder_invalid_vars.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ struct DummyBuilder { // expected-note 5 {{struct 'DummyBuilder' declared here}}
func dummy<T>(@DummyBuilder _: () -> T) {}

dummy {
var computedVar: Int { return 123 } // expected-error {{closure containing a declaration cannot be used with result builder 'DummyBuilder'}}
var computedVar: Int { return 123 } // expected-error {{cannot declare local computed variable in result builder}}
()
}

dummy {
lazy var lazyVar: Int = 123 // expected-error {{closure containing a declaration cannot be used with result builder 'DummyBuilder'}}
lazy var lazyVar: Int = 123 // expected-error {{cannot declare local lazy variable in result builder}}
()
}

dummy {
var observedVar: Int = 123 { // expected-error {{closure containing a declaration cannot be used with result builder 'DummyBuilder'}}
var observedVar: Int = 123 { // expected-error {{cannot declare local observed variable in result builder}}
didSet {}
}

()
}

dummy {
var observedVar: Int = 123 { // expected-error {{closure containing a declaration cannot be used with result builder 'DummyBuilder'}}
var observedVar: Int = 123 { // expected-error {{cannot declare local observed variable in result builder}}
willSet {}
}

Expand All @@ -40,7 +40,7 @@ dummy {
}

dummy {
@Wrapper var wrappedVar: Int = 123 // expected-error {{closure containing a declaration cannot be used with result builder 'DummyBuilder'}}
@Wrapper var wrappedVar: Int = 123 // expected-error {{cannot declare local wrapped variable in result builder}}
()
}

Expand Down