Skip to content

Commit f141b4d

Browse files
committed
[TypeCheckAttr] Provide better error messages for a typeEraser that
has invalid init candidates.
1 parent ebb727c commit f141b4d

File tree

3 files changed

+54
-19
lines changed

3 files changed

+54
-19
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4426,6 +4426,10 @@ ERROR(type_eraser_missing_init,none,
44264426

44274427
NOTE(type_eraser_declared_here,none,
44284428
"type eraser declared here",())
4429+
NOTE(type_eraser_failable_init,none,
4430+
"'init(erasing:)' cannot be failable",())
4431+
NOTE(type_eraser_init_unsatisfied_requirements,none,
4432+
"'init(erasing:)' has unsatisfied requirements",())
44294433

44304434
//------------------------------------------------------------------------------
44314435
// MARK: @available

lib/Sema/TypeCheckAttr.cpp

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2418,10 +2418,17 @@ TypeEraserHasViableInitRequest::evaluate(Evaluator &evaluator,
24182418
// 3. The type eraser must have an init of the form init<T: Protocol>(erasing: T)
24192419
auto lookupResult = TypeChecker::lookupMember(dc, typeEraser,
24202420
DeclNameRef::createConstructor());
2421+
2422+
// Keep track of unviable init candidates for diagnostics
2423+
enum class UnviableReason {
2424+
Failable,
2425+
UnsatisfiedRequirements,
2426+
};
2427+
SmallVector<std::pair<ConstructorDecl *, UnviableReason>, 2> unviable;
2428+
24212429
bool foundMatch = llvm::any_of(lookupResult, [&](const LookupResultEntry &entry) {
24222430
auto *init = cast<ConstructorDecl>(entry.getValueDecl());
2423-
if (init->isFailable() || !init->isGeneric() ||
2424-
init->getGenericParams()->size() != 1)
2431+
if (!init->isGeneric() || init->getGenericParams()->size() != 1)
24252432
return false;
24262433

24272434
auto genericSignature = init->getGenericSignature();
@@ -2455,31 +2462,49 @@ TypeEraserHasViableInitRequest::evaluate(Evaluator &evaluator,
24552462
return getSubstitution(type);
24562463
}, TypeChecker::LookUpConformance(dc));
24572464

2458-
// Listener to suppress diagnostics while checking if the generic
2459-
// requirements are satisfied.
2460-
class Listener : public GenericRequirementsCheckListener {
2461-
bool diagnoseUnsatisfiedRequirement(
2462-
const Requirement &req, Type first, Type second,
2463-
ArrayRef<ParentConditionalConformance> parents) override {
2464-
return true;
2465-
}
2466-
} listener;
2467-
2468-
auto loc = attr->getLocation();
2465+
// Use invalid 'SourceLoc's to suppress diagnostics.
24692466
auto result = TypeChecker::checkGenericArguments(
2470-
protocol, loc, loc, typeEraser,
2467+
protocol, SourceLoc(), SourceLoc(), typeEraser,
24712468
genericSignature->getGenericParams(),
24722469
genericSignature->getRequirements(),
24732470
QuerySubstitutionMap{subMap},
24742471
TypeChecker::LookUpConformance(dc),
2475-
None, &listener);
2476-
return result == RequirementCheckResult::Success;
2472+
None);
2473+
2474+
if (result != RequirementCheckResult::Success) {
2475+
unviable.push_back({init, UnviableReason::UnsatisfiedRequirements});
2476+
return false;
2477+
}
2478+
2479+
if (init->isFailable()) {
2480+
unviable.push_back({init, UnviableReason::Failable});
2481+
return false;
2482+
}
2483+
2484+
return true;
24772485
});
24782486

24792487
if (!foundMatch) {
24802488
diags.diagnose(attr->getLocation(), diag::type_eraser_missing_init,
24812489
typeEraser, protocolType);
2482-
diags.diagnose(nominalTypeDecl->getLoc(), diag::type_eraser_declared_here);
2490+
if (unviable.empty())
2491+
diags.diagnose(nominalTypeDecl->getLoc(),
2492+
diag::type_eraser_declared_here);
2493+
2494+
for (auto &candidate: unviable) {
2495+
auto init = candidate.first;
2496+
auto reason = candidate.second;
2497+
2498+
switch (reason) {
2499+
case UnviableReason::Failable:
2500+
diags.diagnose(init->getLoc(), diag::type_eraser_failable_init);
2501+
break;
2502+
case UnviableReason::UnsatisfiedRequirements:
2503+
diags.diagnose(init->getLoc(),
2504+
diag::type_eraser_init_unsatisfied_requirements);
2505+
break;
2506+
}
2507+
}
24832508
return false;
24842509
}
24852510

test/attr/typeEraser.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,14 @@ class NoLabel: D5 { // expected-note {{type eraser declared here}}
8989
@_typeEraser(NoLabel) // expected-error {{type eraser 'NoLabel' must have an initializer of the form 'init<T: 'D5'>(erasing: T)'}}
9090
protocol D5 {}
9191

92-
class FailableInit: D6 { // expected-note {{type eraser declared here}}
93-
init?<T: D6>(erasing t: T) {}
92+
class FailableInit: D6 {
93+
init?<T: D6>(erasing t: T) {} // expected-note {{'init(erasing:)' cannot be failable}}
9494
}
9595
@_typeEraser(FailableInit) // expected-error {{type eraser 'FailableInit' must have an initializer of the form 'init<T: 'D6'>(erasing: T)'}}
9696
protocol D6 {}
97+
98+
class UnsatisfiedReq: D7 {
99+
init<T: D7>(erasing t: T) where T: Hashable {} // expected-note {{'init(erasing:)' has unsatisfied requirements}}
100+
}
101+
@_typeEraser(UnsatisfiedReq) // expected-error {{type eraser 'UnsatisfiedReq' must have an initializer of the form 'init<T: 'D7'>(erasing: T)'}}
102+
protocol D7 {}

0 commit comments

Comments
 (0)