Skip to content

[6.2] Sema: Implement missing part of SE-0346 #81950

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

Closed
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
2 changes: 0 additions & 2 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -3616,8 +3616,6 @@ ERROR(inheritance_from_non_protocol,none,
"inheritance from non-protocol type %0", (Type))
ERROR(inheritance_from_anyobject,none,
"only protocols can inherit from 'AnyObject'", ())
ERROR(inheritance_from_parameterized_protocol,none,
"cannot inherit from protocol type with generic argument %0", (Type))
ERROR(superclass_not_first,none,
"superclass %0 must appear first in the inheritance clause", (Type))
ERROR(superclass_not_open,none,
Expand Down
90 changes: 71 additions & 19 deletions lib/Sema/AssociatedTypeInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,28 @@ static bool isAsyncSequenceFailure(AssociatedTypeDecl *assocType) {
return assocType->getName() == assocType->getASTContext().Id_Failure;
}

static Type resolveTypeWitnessViaParameterizedProtocol(
Type t, AssociatedTypeDecl *assocType) {
if (auto *pct = t->getAs<ProtocolCompositionType>()) {
for (auto member : pct->getMembers()) {
if (auto result = resolveTypeWitnessViaParameterizedProtocol(
member, assocType)) {
return result;
}
}
} else if (auto *ppt = t->getAs<ParameterizedProtocolType>()) {
auto *proto = ppt->getProtocol();
unsigned i = 0;
for (auto *otherAssocType : proto->getPrimaryAssociatedTypes()) {
if (otherAssocType->getName() == assocType->getName())
return ppt->getArgs()[i];
++i;
}
}

return Type();
}

/// Attempt to resolve a type witness via member name lookup.
static ResolveWitnessResult resolveTypeWitnessViaLookup(
NormalProtocolConformance *conformance,
Expand All @@ -455,6 +477,23 @@ static ResolveWitnessResult resolveTypeWitnessViaLookup(
abort();
}

// Look for a parameterized protocol type in the conformance context's
// inheritance clause.
bool deducedFromParameterizedProtocolType = false;
auto inherited = (isa<NominalTypeDecl>(dc)
? cast<NominalTypeDecl>(dc)->getInherited()
: cast<ExtensionDecl>(dc)->getInherited());
for (auto index : inherited.getIndices()) {
if (auto inheritedTy = inherited.getResolvedType(index)) {
if (auto typeWitness = resolveTypeWitnessViaParameterizedProtocol(
inheritedTy, assocType)) {
recordTypeWitness(conformance, assocType, typeWitness, nullptr);
deducedFromParameterizedProtocolType = true;
}
}
}

// Next, look for a member type declaration with this name.
NLOptions subOptions = (NL_QualifiedDefault | NL_OnlyTypes |
NL_ProtocolMembers | NL_IncludeAttributeImplements);

Expand Down Expand Up @@ -561,27 +600,40 @@ static ResolveWitnessResult resolveTypeWitnessViaLookup(
}
}

// If there are no viable witnesses, and all nonviable candidates came from
// protocol extensions, treat this as "missing".
if (viable.empty() &&
std::find_if(nonViable.begin(), nonViable.end(),
[](const std::pair<TypeDecl *, CheckTypeWitnessResult> &x) {
return x.first->getDeclContext()
->getSelfProtocolDecl() == nullptr;
}) == nonViable.end())
return ResolveWitnessResult::Missing;
if (!deducedFromParameterizedProtocolType) {
// If there are no viable witnesses, and all nonviable candidates came from
// protocol extensions, treat this as "missing".
if (viable.empty() &&
std::find_if(nonViable.begin(), nonViable.end(),
[](const std::pair<TypeDecl *, CheckTypeWitnessResult> &x) {
return x.first->getDeclContext()
->getSelfProtocolDecl() == nullptr;
}) == nonViable.end())
return ResolveWitnessResult::Missing;

// If there is a single viable candidate, form a substitution for it.
if (viable.size() == 1) {
auto interfaceType = viable.front().MemberType;
recordTypeWitness(conformance, assocType, interfaceType,
viable.front().Member);
return ResolveWitnessResult::Success;
}

// If there is a single viable candidate, form a substitution for it.
if (viable.size() == 1) {
auto interfaceType = viable.front().MemberType;
recordTypeWitness(conformance, assocType, interfaceType,
viable.front().Member);
return ResolveWitnessResult::Success;
}
// Record an error.
recordTypeWitness(conformance, assocType,
ErrorType::get(ctx), nullptr);
} else {
// We deduced the type witness from a parameterized protocol type, so just
// make sure there was nothing else.
if (viable.size() == 1 &&
isa<TypeAliasDecl>(viable[0].Member) &&
viable[0].Member->isSynthesized()) {
// We found the type alias synthesized above.
return ResolveWitnessResult::Success;
}

// Record an error.
recordTypeWitness(conformance, assocType,
ErrorType::get(ctx), nullptr);
// Otherwise fall through.
}

// If we had multiple viable types, diagnose the ambiguity.
if (!viable.empty()) {
Expand Down
75 changes: 30 additions & 45 deletions lib/Sema/TypeCheckDeclPrimary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,6 @@ using namespace swift;

#define DEBUG_TYPE "TypeCheckDeclPrimary"

static Type containsParameterizedProtocolType(Type inheritedTy) {
if (inheritedTy->is<ParameterizedProtocolType>()) {
return inheritedTy;
}

if (auto *compositionTy = inheritedTy->getAs<ProtocolCompositionType>()) {
for (auto memberTy : compositionTy->getMembers()) {
if (auto paramTy = containsParameterizedProtocolType(memberTy))
return paramTy;
}
}

return Type();
}

class CheckRepressions {
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> declUnion;
ASTContext &ctx;
Expand Down Expand Up @@ -303,14 +288,6 @@ static void checkInheritanceClause(
inheritedAnyObject = { i, inherited.getSourceRange() };
}

if (auto paramTy = containsParameterizedProtocolType(inheritedTy)) {
if (!isa<ProtocolDecl>(decl)) {
decl->diagnose(diag::inheritance_from_parameterized_protocol,
paramTy);
}
continue;
}

if (inheritedTy->isConstraintType()) {
auto layout = inheritedTy->getExistentialLayout();

Expand Down Expand Up @@ -1801,6 +1778,33 @@ static TypeRepr *unwrapAttributedRepr(TypeRepr *repr) {
return repr;
}

static void collectProtocolsFromInheritedEntry(
const InheritedEntry &entry,
Type inheritedTy,
llvm::SmallPtrSetImpl<ProtocolDecl *> &protocolsWithRetroactiveAttr,
SmallVectorImpl<ProtocolDecl *> &protos) {

if (auto *protoTy = inheritedTy->getAs<ProtocolType>()) {
auto *proto = protoTy->getDecl();

// As a fallback, to support previous language versions, also allow
// this through if the protocol has been explicitly module-qualified.
TypeRepr *repr = unwrapAttributedRepr(entry.getTypeRepr());
if (isModuleQualified(repr, proto->getParentModule())) {
protocolsWithRetroactiveAttr.insert(proto);
}

protos.push_back(proto);
} else if (auto *pct = inheritedTy->getAs<ProtocolCompositionType>()) {
for (auto member : pct->getMembers()) {
collectProtocolsFromInheritedEntry(entry, member,
protocolsWithRetroactiveAttr, protos);
}
} else if (auto *ppt = inheritedTy->getAs<ParameterizedProtocolType>()) {
protos.push_back(ppt->getProtocol());
}
}

/// Determines if this extension declares a conformance of a type declared
/// outside this module to a protocol declared outside this module (but only
/// in library evolution mode)
Expand Down Expand Up @@ -1837,7 +1841,7 @@ static void diagnoseRetroactiveConformances(
// At this point, we know we're extending a type declared outside this module.
// We better only be conforming it to protocols declared within this module.
llvm::SmallMapVector<ProtocolDecl *, bool, 8> protocols;
llvm::SmallSet<ProtocolDecl *, 8> protocolsWithRetroactiveAttr;
llvm::SmallPtrSet<ProtocolDecl *, 2> protocolsWithRetroactiveAttr;

for (auto *conformance : ext->getLocalConformances()) {
auto *proto = conformance->getProtocol();
Expand Down Expand Up @@ -1865,27 +1869,8 @@ static void diagnoseRetroactiveConformances(
}

SmallVector<ProtocolDecl *, 2> protos;
if (auto *protoTy = inheritedTy->getAs<ProtocolType>()) {
auto *proto = protoTy->getDecl();

// As a fallback, to support previous language versions, also allow
// this through if the protocol has been explicitly module-qualified.
TypeRepr *repr = unwrapAttributedRepr(entry.getTypeRepr());
if (isModuleQualified(repr, proto->getParentModule())) {
protocolsWithRetroactiveAttr.insert(proto);
continue;
}

protos.push_back(proto);
} else if (auto *compositionTy = inheritedTy->getAs<ProtocolCompositionType>()) {
for (auto memberTy : compositionTy->getMembers()) {
if (auto *protoTy = memberTy->getAs<ProtocolType>()) {
protos.push_back(protoTy->getDecl());
}
}
} else {
continue;
}
collectProtocolsFromInheritedEntry(entry, inheritedTy,
protocolsWithRetroactiveAttr, protos);

for (auto *proto : protos) {
proto->walkInheritedProtocols([&](ProtocolDecl *decl) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// RUN: %target-typecheck-verify-swift

protocol P<A, B> {
associatedtype A // expected-note {{multiple matching types named 'A'}}
associatedtype B

func a(_: A)
}

extension P {
func a(_: A) {}
}

struct S: P<Int, String> {}

func f(_: Int.Type) {}
func g(_: String.Type) {}

f(S.A.self)
g(S.B.self)

struct G<T, U>: P<T, U> {}

f(G<Int, String>.A.self)
g(G<Int, String>.B.self)

protocol Q<B> {
associatedtype B // expected-note {{protocol requires nested type 'B'}}
}

struct S2: P, Q<String> {
func a(_: Int) {}
}

struct S3: P & Q<String> {
func a(_: Int) {}
}

protocol R {}

struct S4: (P & Q<String>) & R {
func a(_: Int) {}
}

struct Bad: P<Int, Float> { // expected-error {{type 'Bad' does not conform to protocol 'P'}}
typealias A = String // expected-note {{possibly intended match}}
}

let x = Bad.A.self
g(x)

struct Circle: Q<Circle.A> {}
// expected-note@-1 {{'Circle' declared here}}
// expected-error@-2 {{type 'Circle' does not conform to protocol 'Q'}}
// expected-note@-3 {{add stubs for conformance}}
// expected-error@-4 {{'A' is not a member type of struct 'type_witness_from_parameterized_protocol.Circle'}}
1 change: 0 additions & 1 deletion test/Generics/rdar94848868.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ protocol MyCollectionProtocol: Collection where Iterator == MyCollectionIterator
struct MyCollectionIterator<MyCollection: MyCollectionProtocol>: IteratorProtocol {
// expected-note@-1 3{{through reference here}}
mutating func next() -> MyCollection.Element? {
// expected-note@-1 2{{through reference here}}
return nil
}
}
Expand Down
13 changes: 13 additions & 0 deletions test/Sema/extension_retroactive_conformances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ public protocol SampleProtocol2 {}
public protocol SampleProtocol1a: SampleProtocol1 {}
public protocol SampleProtocol1b: SampleProtocol1 {}

public protocol SampleProtocol3<A> {
associatedtype A
}

public struct Sample1 {}
public struct Sample2 {}
public struct Sample2a {}
Expand All @@ -27,6 +31,9 @@ public struct SampleAlreadyConforms: SampleProtocol1 {}

public struct GenericSample1<T> {}

public struct Sample9 {}
public struct Sample10 {}

#else

import Library
Expand Down Expand Up @@ -118,4 +125,10 @@ extension Sample7: SampleProtocol1 & SampleProtocol2 {}

extension Sample8: @retroactive SampleProtocol1 & SampleProtocol2 {} // ok

extension Sample9: SampleProtocol3<Int> {}
// expected-warning@-1 {{extension declares a conformance of imported type 'Sample9' to imported protocol 'SampleProtocol3'; this will not behave correctly if the owners of 'Library' introduce this conformance in the future}}
// expected-note@-2 {{add '@retroactive' to silence this warning}}

extension Sample10: @retroactive SampleProtocol3<Int> {}

#endif
15 changes: 0 additions & 15 deletions test/type/parameterized_protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ protocol Invalid5<Element, Element> {

protocol Sequence<Element> {
associatedtype Element
// expected-note@-1 2{{protocol requires nested type 'Element'}}
}

extension Sequence {
Expand All @@ -56,14 +55,6 @@ struct ConcreteEquatableSequence<Element : Equatable> : EquatableSequence {}
protocol IntSequence : Sequence<Int> {}


/// Concrete types cannot inherit from a parameterized protocol

struct SillyStruct : Sequence<Int> {}
// expected-error@-1 {{cannot inherit from protocol type with generic argument 'Sequence<Int>'}}
// expected-error@-2 {{type 'SillyStruct' does not conform to protocol 'Sequence'}}
// expected-note@-3 {{add stubs for conformance}}


/// Parameterized protocol in generic parameter inheritance clause

// CHECK-LABEL: parameterized_protocol.(file).IntSequenceWrapper@
Expand Down Expand Up @@ -213,12 +204,6 @@ protocol TestCompositionProtocol1 {
associatedtype S : Sequence<Int> & Sendable
}

struct TestStructComposition : Sequence<Int> & Sendable {}
// expected-error@-1 {{cannot inherit from protocol type with generic argument 'Sequence<Int>'}}
// expected-error@-2 {{type 'TestStructComposition' does not conform to protocol 'Sequence'}}
// expected-note@-3 {{add stubs for conformance}}


/// Conflicts

protocol Pair<X, Y> where Self.X == Self.Y {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@ protocol P {
struct Type<Param> {}
extension Type: P where Param: P, Param.A == Type<Param> {
// expected-error@-1 {{extension of generic struct 'Type' has self-referential generic requirements}}
// expected-note@-2 {{through reference here}}
// expected-note@-2 2 {{through reference here}}
// expected-error@-3 {{type 'Type<Param>' does not conform to protocol 'P'}}
// expected-note@-4 {{add stubs for conformance}}
typealias A = Param
// expected-note@-1 3{{through reference here}}
// expected-note@-2 {{possibly intended match 'Type<Param>.A' (aka 'Param') does not conform to 'P'}}
// expected-note@-1 {{possibly intended match 'Type<Param>.A' (aka 'Param') does not conform to 'P'}}
}