Skip to content

[WIP] Sema: Resolve type witnesses for conditional conformances early #30700

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions lib/AST/DiagnosticEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ static bool isInterestingTypealias(Type type) {
/// Decide whether to show the desugared type or not. We filter out some
/// cases to avoid too much noise.
static bool shouldShowAKA(Type type, StringRef typeName) {
// (aka '<<error type>>') does not contribute to a message whatsoever.
if (type->is<ErrorType>())
return false;

// Canonical types are already desugared.
if (type->isCanonical())
return false;
Expand Down
16 changes: 16 additions & 0 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
// Check for circular inheritance of the raw type.
(void)ED->hasCircularRawValue();

// Resolve all the type witnesses we need now to correctly diagnose
// references to default type witnesses with unsatisfied requirements
// once we start realizing types in members.
TypeChecker::resolveTypeWitnessesForConditionalConformances(ED);

for (Decl *member : ED->getMembers())
visit(member);

Expand Down Expand Up @@ -1775,6 +1780,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {

checkGenericParams(SD);

// Resolve all the type witnesses we need now to correctly diagnose
// references to default type witnesses with unsatisfied requirements
// once we start realizing types in members.
TypeChecker::resolveTypeWitnessesForConditionalConformances(SD);

// Force lowering of stored properties.
(void) SD->getStoredProperties();

Expand Down Expand Up @@ -1905,6 +1915,11 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
// Check for circular inheritance.
(void)CD->hasCircularInheritance();

// Resolve all the type witnesses we need now to correctly diagnose
// references to default type witnesses with unsatisfied requirements
// once we start realizing types in members.
TypeChecker::resolveTypeWitnessesForConditionalConformances(CD);

// Force lowering of stored properties.
(void) CD->getStoredProperties();

Expand Down Expand Up @@ -2061,6 +2076,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
GenericSignature::get({PD->getProtocolSelfType()},
PD->getRequirementSignature());

llvm::errs() << "\n";
llvm::errs() << "Protocol requirement signature:\n";
PD->dumpRef(llvm::errs());
llvm::errs() << "\n";
Expand Down
5 changes: 3 additions & 2 deletions lib/Sema/TypeCheckGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -469,12 +469,13 @@ GenericSignature TypeChecker::checkGenericSignature(
// Debugging of the generic signature builder and generic signature
// generation.
if (dc->getASTContext().TypeCheckerOpts.DebugGenericSignatures) {
llvm::errs() << "\n";
if (auto *VD = dyn_cast_or_null<ValueDecl>(dc->getAsDecl())) {
VD->dumpRef(llvm::errs());
llvm::errs() << "\n";
} else {
dc->printContext(llvm::errs());
}
llvm::errs() << "\n";
llvm::errs() << "Generic signature: ";
sig->print(llvm::errs());
llvm::errs() << "\n";
Expand Down Expand Up @@ -591,8 +592,8 @@ GenericSignatureRequest::evaluate(Evaluator &evaluator,
// Debugging of the generic signature builder and generic signature
// generation.
if (GC->getASTContext().TypeCheckerOpts.DebugGenericSignatures) {
PD->printContext(llvm::errs());
llvm::errs() << "\n";
PD->printContext(llvm::errs());
llvm::errs() << "Generic signature: ";
sig->print(llvm::errs());
llvm::errs() << "\n";
Expand Down
84 changes: 82 additions & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3795,6 +3795,20 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
if (typeAliasDecl->getDeclaredInterfaceType()->is<UnboundGenericType>())
continue;

// A candidate is nonviable if its context has requirements not
// satisfied by those of the confomance context.
{
const auto candidateCtxSig = candidate.Member->getDeclContext()
->getGenericSignatureOfContext();
const auto conformanceCtxSig = DC->getGenericSignatureOfContext();

if(candidateCtxSig && conformanceCtxSig &&
!candidateCtxSig->requirementsNotSatisfiedBy(
conformanceCtxSig).empty()) {
continue;
}
}

// Check this type against the protocol requirements.
if (auto checkResult =
checkTypeWitness(DC, Proto, assocType, candidate.MemberType)) {
Expand Down Expand Up @@ -5055,6 +5069,69 @@ LookupAllConformancesInContextRequest::evaluate(
return DC->getLocalConformances(ConformanceLookupKind::All);
}

void TypeChecker::resolveTypeWitnessesForConditionalConformances(
const NominalTypeDecl *nominal) {
// Non-generic types won't have conditional conformances.
const auto genericSig = nominal->getGenericSignature();
if (!genericSig)
return;

// Collect all the normal conformances for this nominal.
SmallVector<NormalProtocolConformance *, 2> conformances;
for (const auto conf: nominal->getAllConformances())
if (const auto normal = dyn_cast<NormalProtocolConformance>(conf))
conformances.push_back(normal);

// If the conditional requirements of a conformance are satisfied by those
// of another conformance, we want default type witnesses for the former,
// more general conformance, to be able to satisfy type requirements for
// the latter conformance in case of associated type collisions – but not
// vice versa (granted that the protocols do not match, of course).
// E.g., both conformances below should associate 'A' with a single
// type witness synthesized for the less constrained conformance to P1.
//
// protocol P1 { associatedtype A = Int }
// protocol P2 { associatedtype A }
//
// struct Foo<T>: P1 { }
// extension Foo: P2 where T == String { }
//
// To achieve this behavior, conformance X, whose conditional requirements
// are satisfied by those of conformance Y, must be checked before Y.
//
// First off, move all regular conformances to the front.
const auto it = llvm::partition(conformances,
[](const NormalProtocolConformance *conf) {
const auto reqs = conf->getConditionalRequirementsIfAvailable();
return reqs && reqs->empty();
});

// If there are no conditional conformances, there's nothing to resolve.
if (it == conformances.end())
return;

// Then, sort the remaining conditional conformances from less
// to more constrained.
std::sort(it, conformances.end(),
[](const NormalProtocolConformance *conf1,
const NormalProtocolConformance *conf2) {
const auto ext1 = cast<ExtensionDecl>(conf1->getDeclContext());
const auto ext2 = cast<ExtensionDecl>(conf2->getDeclContext());

return ext1->getGenericSignature()
->requirementsNotSatisfiedBy(ext2->getGenericSignature()).empty();
});

for (const auto conf: conformances) {
llvm::SetVector<ValueDecl *> missingWitnesses;
ConformanceChecker checker(nominal->getASTContext(), conf,
missingWitnesses,
/*suppressDiagnostics*/true);
checker.resolveTypeWitnesses();
checker.diagnoseMissingWitnesses(MissingWitnessDiagnosisKind::ErrorFixIt);
}
}

void TypeChecker::checkConformancesInContext(DeclContext *dc,
IterableDeclContext *idc) {
// For anything imported from Clang, lazily check conformances.
Expand Down Expand Up @@ -5115,12 +5192,15 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
// Check all conformances.
groupChecker.checkAllConformances();

if (Context.TypeCheckerOpts.DebugGenericSignatures) {
if (Context.TypeCheckerOpts.DebugGenericSignatures &&
!conformances.empty()) {
// Now that they're filled out, print out information about the conformances
// here, when requested.
llvm::errs() << "\n";
dc->printContext(llvm::errs());
for (auto conformance : conformances) {
dc->printContext(llvm::errs());
conformance->dump(llvm::errs());
llvm::errs() << "\n";
}
}

Expand Down
18 changes: 11 additions & 7 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,9 @@ static void maybeDiagnoseBadConformanceRef(DeclContext *dc,
Type parentTy,
SourceLoc loc,
TypeDecl *typeDecl) {
auto protocol = dyn_cast<ProtocolDecl>(typeDecl->getDeclContext());
assert(isa<AssociatedTypeDecl>(typeDecl) || isa<TypeAliasDecl>(typeDecl));

const auto protocol = dyn_cast<ProtocolDecl>(typeDecl->getDeclContext());

// If we weren't given a conformance, go look it up.
ProtocolConformance *conformance = nullptr;
Expand All @@ -991,16 +993,18 @@ static void maybeDiagnoseBadConformanceRef(DeclContext *dc,

// If any errors have occurred, don't bother diagnosing this cross-file
// issue.
ASTContext &ctx = dc->getASTContext();
const auto &ctx = dc->getASTContext();
if (ctx.Diags.hadAnyError())
return;

auto diagCode =
(!protocol || (conformance && !conformance->getConditionalRequirementsIfAvailable()))
? diag::unsupported_recursion_in_associated_type_reference
: diag::broken_associated_type_witness;
const auto diagCode =
((!protocol && !cast<TypeAliasDecl>(typeDecl)->isImplicit()) ||
(conformance && !conformance->getConditionalRequirementsIfAvailable()))
? diag::unsupported_recursion_in_associated_type_reference
: diag::broken_associated_type_witness;

ctx.Diags.diagnose(loc, diagCode, isa<TypeAliasDecl>(typeDecl), typeDecl->getFullName(), parentTy);
ctx.Diags.diagnose(loc, diagCode, isa<TypeAliasDecl>(typeDecl),
typeDecl->getFullName(), parentTy);
}

/// Returns a valid type or ErrorType in case of an error.
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,9 @@ class LookUpConformance {
/// Check all of the conformances in the given context.
void checkConformancesInContext(DeclContext *dc, IterableDeclContext *idc);

void resolveTypeWitnessesForConditionalConformances(
const NominalTypeDecl *nominal);

/// Check that the type of the given property conforms to NSCopying.
ProtocolConformanceRef checkConformanceToNSCopying(VarDecl *var);

Expand Down
107 changes: 104 additions & 3 deletions test/Generics/conditional_conformances.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// RUN: %target-typecheck-verify-swift
// RUN: %target-typecheck-verify-swift -print-ast | %FileCheck %s -check-prefix=DEFAULT_TYPE_WITNESS
// RUN: %target-typecheck-verify-swift -debug-generic-signatures > %t.dump 2>&1
// RUN: %FileCheck %s < %t.dump
// RUN: %target-typecheck-verify-swift

protocol P1 {}
protocol P2 {}
Expand Down Expand Up @@ -208,11 +209,11 @@ func subclass_bad() {
struct InheritEqual<T> {}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritEqual
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritEqual
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritEqual
// CHECK-NEXT: (normal_conformance type=InheritEqual<T> protocol=P2
// CHECK-NEXT: conforms_to: T P1)
extension InheritEqual: P2 where T: P1 {} // expected-note {{requirement from conditional conformance of 'InheritEqual<U>' to 'P2'}}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritEqual
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritEqual
// CHECK-NEXT: (normal_conformance type=InheritEqual<T> protocol=P5
// CHECK-NEXT: (normal_conformance type=InheritEqual<T> protocol=P2
// CHECK-NEXT: conforms_to: T P1)
Expand All @@ -238,11 +239,11 @@ extension InheritLess: P5 {} // expected-error{{type 'T' does not conform to pro
struct InheritMore<T> {}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritMore
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritMore
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritMore
// CHECK-NEXT: (normal_conformance type=InheritMore<T> protocol=P2
// CHECK-NEXT: conforms_to: T P1)
extension InheritMore: P2 where T: P1 {} // expected-note {{requirement from conditional conformance of 'InheritMore<U>' to 'P2'}}
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritMore
// CHECK-LABEL: ExtensionDecl line={{.*}} base=InheritMore
// CHECK-NEXT: (normal_conformance type=InheritMore<T> protocol=P5
// CHECK-NEXT: (normal_conformance type=InheritMore<T> protocol=P2
// CHECK-NEXT: conforms_to: T P1)
Expand Down Expand Up @@ -429,3 +430,103 @@ func sr_10992_foo(_ fn: (SR_10992_S<String>) -> Void) {}
func sr_10992_bar(_ fn: (SR_10992_P) -> Void) {
sr_10992_foo(fn) // expected-error {{global function 'sr_10992_foo' requires that 'String' conform to 'SR_10992_P'}}
}

// SR-7516

protocol SR_7516_P1 { associatedtype X = Void } // expected-note 4{{protocol requires nested type 'X'; do you want to add it?}}
protocol SR_7516_P2 { associatedtype X = Bool }
protocol SR_7516_P3 { associatedtype X = Void }
protocol SR_7516_P4 { associatedtype X } // expected-note {{protocol requires nested type 'X'; do you want to add it?}}

struct SR_7516_S1<T> {
class InnerBase<U: Sequence> where U.Element == X {}
class InnerDerived: InnerBase<[X]> {}
// expected-error@-2 {{'SR_7516_S1<T>.X' (aka '()') requires the types 'T' and 'Never' be equivalent}}
// expected-error@-2 {{'SR_7516_S1<T>.X' (aka '()') requires the types 'T' and 'Never' be equivalent}}
}
extension SR_7516_S1: SR_7516_P1 where T == Never { // expected-note 2{{requirement specified as 'T' == 'Never' [with T = T]}}
func bar(arg: X) {}
// DEFAULT_TYPE_WITNESS: extension SR_7516_S1 : SR_7516_P1
// DEFAULT_TYPE_WITNESS-NEXT: internal func bar(arg: X)
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}

struct SR_7516_S2<T>: SR_7516_P1 {
// DEFAULT_TYPE_WITNESS: struct SR_7516_S2<T> : SR_7516_P1
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}
extension SR_7516_S2: SR_7516_P2 where T == Never {}
extension SR_7516_S2 {
func foo(arg: X) {}
}

struct SR_7516_S3<T>: SR_7516_P1 {
let bar: X
// DEFAULT_TYPE_WITNESS: struct SR_7516_S3<T> : SR_7516_P1
// DEFAULT_TYPE_WITNESS-NEXT: internal let bar: X
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}
extension SR_7516_S3: SR_7516_P4 where T == Never {}

struct SR_7516_S4<T>: SR_7516_P4 { // expected-error {{type 'SR_7516_S4<T>' does not conform to protocol 'SR_7516_P4'}}
let bar: X
}
extension SR_7516_S4: SR_7516_P1 where T == Never {} // expected-error {{type 'SR_7516_S4<T>' does not conform to protocol 'SR_7516_P1'}}

class SR_7516_C1<T> {
var bar: X { fatalError() } // expected-error {{'SR_7516_C1<T>.X' (aka '()') requires the types 'T' and 'Never' be equivalent}}
}
// FIXME: Should this be an ambiguous situation where the user is
// required to provide an explicit witness for X?
extension SR_7516_C1: SR_7516_P1 where T == Never {
func foo(arg: X) {}
}
extension SR_7516_C1: SR_7516_P3 where T == Never { // expected-note 2{{requirement specified as 'T' == 'Never' [with T = T]}}

// DEFAULT_TYPE_WITNESS: extension SR_7516_C1 : SR_7516_P3
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}
extension SR_7516_C1 {
typealias Y = X // expected-error {{'SR_7516_C1<T>.X' (aka '()') requires the types 'T' and 'Never' be equivalent}}
}

enum SR_7516_E1<T, U> {
// FIXME: Diagnostics QoI
case never(X) // expected-error {{type 'T' does not conform to protocol 'Equatable'}}
}
extension SR_7516_E1: SR_7516_P1 where T: Equatable {
// DEFAULT_TYPE_WITNESS: extension SR_7516_E1 : SR_7516_P1
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}
extension SR_7516_E1: SR_7516_P4 where T: Equatable, U: Equatable {
func foo(arg: X) {}
}

struct SR_7516_S5<T> {
var bar: X { fatalError() }
}
extension SR_7516_S5: SR_7516_P2 where T == Never {}
extension SR_7516_S5: SR_7516_P3 where T == String {}
extension SR_7516_S5: SR_7516_P1 {
func foo(arg: X) {}
// DEFAULT_TYPE_WITNESS: extension SR_7516_S5 : SR_7516_P1
// DEFAULT_TYPE_WITNESS-NEXT: internal func foo(arg: X)
// DEFAULT_TYPE_WITNESS-NEXT: internal typealias X = Void
}
extension SR_7516_S5: SR_7516_P4 where T: Equatable {}

struct SR_7516_S6<T> {}
extension SR_7516_S6: SR_7516_P2 where T == Void {}
extension SR_7516_S6: SR_7516_P1 where T == Never { // expected-error {{type 'SR_7516_S6<T>' does not conform to protocol 'SR_7516_P1'}}
}

struct SR_7516_S7<T> {}
extension SR_7516_S7: SR_7516_P1 where T == Void {} // expected-error {{type 'SR_7516_S7<T>' does not conform to protocol 'SR_7516_P1'}}
extension SR_7516_S7: SR_7516_P2 where T == Never {
typealias X = Bool
}

struct SR_7516_S8<T>: SR_7516_P1 {} // expected-error {{type 'SR_7516_S8<T>' does not conform to protocol 'SR_7516_P1'}}
extension SR_7516_S8: SR_7516_P2 where T == Never {
typealias X = Bool
}