Always infer AsyncSequence.Failure from AsyncIteratorProtocol.Failure

The newly-introduced associated type `AsyncSequence.Failure` must
always be equivalent to the `Failure` type of the
`AsyncIteratorProtocol`. If the `AsyncSequence` type itself defines a
nested `Failure` type (say, for another purpose), associated type inference
would pick it and reject the `AsyncSequence`, causing a source compatibility
problem.

Work around the issue in two ways. First, always infer the type
witness for `AsyncSequence.Failure` from the type witness for
`AsyncIteratorProtocol.Failure`, so they can't be out of sync. This
means that we'll never even consider a nested `Failure` type in the
`AsyncSequence`-conforming type. This hack only applies prior to Swift 6.

Second, when we have inferred a `Failure` type and there is already
something else called `Failure` within that same nominal type, don't
print the inferred typelias into a module interface because it will
cause a conflict.

Fixes rdar://123543633.
This commit is contained in:
Doug Gregor
2024-02-28 13:40:23 -08:00
parent 34dd632889
commit 0652bb7abe
4 changed files with 124 additions and 18 deletions

View File

@@ -6374,6 +6374,32 @@ void Decl::printInherited(ASTPrinter &Printer, const PrintOptions &Opts) const {
printer.printInherited(this); printer.printInherited(this);
} }
/// Determine whether this typealias is an inferred typealias "Failure" that
/// would conflict with another entity named failure in the same type.
static bool isConflictingFailureTypeWitness(
const TypeAliasDecl *typealias) {
if (!typealias->isImplicit())
return false;
ASTContext &ctx = typealias->getASTContext();
if (typealias->getName() != ctx.Id_Failure)
return false;
auto nominal = typealias->getDeclContext()->getSelfNominalTypeDecl();
if (!nominal)
return false;
// Look for another entity with the same name.
auto lookupResults = nominal->lookupDirect(
typealias->getName(), typealias->getLoc());
for (auto found : lookupResults) {
if (found != typealias)
return true;
}
return false;
}
bool Decl::shouldPrintInContext(const PrintOptions &PO) const { bool Decl::shouldPrintInContext(const PrintOptions &PO) const {
// Skip getters/setters. They are part of the variable or subscript. // Skip getters/setters. They are part of the variable or subscript.
if (isa<AccessorDecl>(this)) if (isa<AccessorDecl>(this))
@@ -6413,6 +6439,14 @@ bool Decl::shouldPrintInContext(const PrintOptions &PO) const {
return PO.PrintIfConfig; return PO.PrintIfConfig;
} }
// Prior to Swift 6, we shouldn't print the inferred associated type
// witness for AsyncSequence.Failure. It is always determined from the
// AsyncIteratorProtocol witness.
if (auto typealias = dyn_cast<TypeAliasDecl>(this)) {
if (isConflictingFailureTypeWitness(typealias))
return false;
}
// Print everything else. // Print everything else.
return true; return true;
} }

View File

@@ -389,6 +389,35 @@ static void recordTypeWitness(NormalProtocolConformance *conformance,
} }
} }
/// Determine whether this is the AsyncIteratorProtocol.Failure associated type.
static bool isAsyncIteratorProtocolFailure(AssociatedTypeDecl *assocType) {
auto proto = assocType->getProtocol();
if (!proto->isSpecificProtocol(KnownProtocolKind::AsyncIteratorProtocol))
return false;
return assocType->getName() == assocType->getASTContext().Id_Failure;
}
/// Determine whether this is the AsyncSequence.Failure associated type.
static bool isAsyncSequenceFailure(AssociatedTypeDecl *assocType) {
auto proto = assocType->getProtocol();
if (!proto->isSpecificProtocol(KnownProtocolKind::AsyncSequence))
return false;
return assocType->getName() == assocType->getASTContext().Id_Failure;
}
/// Determine whether this is the AsyncIteratorProtocol.Failure or
/// AsyncSequence.Failure associated type.
static bool isAsyncIteratorOrSequenceFailure(AssociatedTypeDecl *assocType) {
auto proto = assocType->getProtocol();
if (!proto->isSpecificProtocol(KnownProtocolKind::AsyncIteratorProtocol) &&
!proto->isSpecificProtocol(KnownProtocolKind::AsyncSequence))
return false;
return assocType->getName() == assocType->getASTContext().Id_Failure;
}
/// Attempt to resolve a type witness via member name lookup. /// Attempt to resolve a type witness via member name lookup.
static ResolveWitnessResult resolveTypeWitnessViaLookup( static ResolveWitnessResult resolveTypeWitnessViaLookup(
NormalProtocolConformance *conformance, NormalProtocolConformance *conformance,
@@ -396,6 +425,13 @@ static ResolveWitnessResult resolveTypeWitnessViaLookup(
auto *dc = conformance->getDeclContext(); auto *dc = conformance->getDeclContext();
auto &ctx = dc->getASTContext(); auto &ctx = dc->getASTContext();
// Prior to Swift 6, don't look for a named type witness for
// AsyncSequence.Failure. We'll always infer it from
// AsyncIteratorProtocol.Failure.
if (isAsyncSequenceFailure(assocType) &&
!ctx.LangOpts.isSwiftVersionAtLeast(6))
return ResolveWitnessResult::Missing;
// Conformances constructed by the ClangImporter should have explicit type // Conformances constructed by the ClangImporter should have explicit type
// witnesses already. // witnesses already.
if (isa<ClangModuleUnit>(dc->getModuleScopeContext())) { if (isa<ClangModuleUnit>(dc->getModuleScopeContext())) {
@@ -1816,17 +1852,6 @@ next_witness:;
return result; return result;
} }
/// Determine whether this is AsyncIteratorProtocol.Failure or
/// AsyncSequenceProtoco.Failure associated type.
static bool isAsyncIteratorProtocolFailure(AssociatedTypeDecl *assocType) {
auto proto = assocType->getProtocol();
if (!proto->isSpecificProtocol(KnownProtocolKind::AsyncIteratorProtocol) &&
!proto->isSpecificProtocol(KnownProtocolKind::AsyncSequence))
return false;
return assocType->getName() == assocType->getASTContext().Id_Failure;
}
/// Determine whether this is AsyncIteratorProtocol.next() function. /// Determine whether this is AsyncIteratorProtocol.next() function.
static bool isAsyncIteratorProtocolNext(ValueDecl *req) { static bool isAsyncIteratorProtocolNext(ValueDecl *req) {
auto proto = dyn_cast<ProtocolDecl>(req->getDeclContext()); auto proto = dyn_cast<ProtocolDecl>(req->getDeclContext());
@@ -1834,7 +1859,8 @@ static bool isAsyncIteratorProtocolNext(ValueDecl *req) {
!proto->isSpecificProtocol(KnownProtocolKind::AsyncIteratorProtocol)) !proto->isSpecificProtocol(KnownProtocolKind::AsyncIteratorProtocol))
return false; return false;
return req->getName().getBaseName() == req->getASTContext().Id_next; return req->getName().getBaseName() == req->getASTContext().Id_next &&
req->getName().getArgumentNames().empty();
} }
InferredAssociatedTypes InferredAssociatedTypes
@@ -2537,8 +2563,7 @@ std::optional<AbstractTypeWitness>
AssociatedTypeInference::computeFailureTypeWitness( AssociatedTypeInference::computeFailureTypeWitness(
AssociatedTypeDecl *assocType, AssociatedTypeDecl *assocType,
ArrayRef<std::pair<ValueDecl *, ValueDecl *>> valueWitnesses) const { ArrayRef<std::pair<ValueDecl *, ValueDecl *>> valueWitnesses) const {
// Inference only applies to AsyncIteratorProtocol.Failure and // Inference only applies to AsyncIteratorProtocol.Failure.
// AsyncSequence.Failure.
if (!isAsyncIteratorProtocolFailure(assocType)) if (!isAsyncIteratorProtocolFailure(assocType))
return std::nullopt; return std::nullopt;
@@ -2582,7 +2607,7 @@ AssociatedTypeInference::computeDefaultTypeWitness(
AssociatedTypeDecl *assocType) const { AssociatedTypeDecl *assocType) const {
// Ignore the default for AsyncIteratorProtocol.Failure and // Ignore the default for AsyncIteratorProtocol.Failure and
// AsyncSequence.Failure. // AsyncSequence.Failure.
if (isAsyncIteratorProtocolFailure(assocType)) if (isAsyncIteratorOrSequenceFailure(assocType))
return std::nullopt; return std::nullopt;
// Go find a default definition. // Go find a default definition.
@@ -2683,8 +2708,8 @@ AssociatedTypeInference::computeAbstractTypeWitness(
// Don't consider the generic parameter names for AsyncSequence.Failure or // Don't consider the generic parameter names for AsyncSequence.Failure or
// AsyncIteratorProtocol.Failure; we always rely on inference from next() or // AsyncIteratorProtocol.Failure; we always rely on inference from next() or
// next(_:). // next(isolation:).
if (isAsyncIteratorProtocolFailure(assocType)) { if (isAsyncIteratorOrSequenceFailure(assocType)) {
// If this is specifically AsyncSequence.Failure with the older associated // If this is specifically AsyncSequence.Failure with the older associated
// type inference implementation, our abstract witness is // type inference implementation, our abstract witness is
// "AsyncIterator.Failure". The new implementation is smart enough to do // "AsyncIterator.Failure". The new implementation is smart enough to do
@@ -2727,7 +2752,7 @@ Type AssociatedTypeInference::computeGenericParamWitness(
if (auto genericSig = dc->getGenericSignatureOfContext()) { if (auto genericSig = dc->getGenericSignatureOfContext()) {
// Ignore the generic parameters for AsyncIteratorProtocol.Failure and // Ignore the generic parameters for AsyncIteratorProtocol.Failure and
// AsyncSequence.Failure. // AsyncSequence.Failure.
if (!isAsyncIteratorProtocolFailure(assocType)) { if (!isAsyncIteratorOrSequenceFailure(assocType)) {
for (auto *gp : genericSig.getInnermostGenericParams()) { for (auto *gp : genericSig.getInnermostGenericParams()) {
// Packs cannot witness associated type requirements. // Packs cannot witness associated type requirements.
if (gp->isParameterPack()) if (gp->isParameterPack())

View File

@@ -99,3 +99,23 @@ func forTryAwaitReturningExistentialType() async throws {
for try await _ in S().seq() { // Ok for try await _ in S().seq() { // Ok
} }
} }
@available(SwiftStdlib 5.1, *)
public struct ReaderSeq: AsyncSequence, Sendable {
public enum Failure: Error {
case x
}
public typealias Element = Int
public func makeAsyncIterator() -> Reader {}
public actor Reader: AsyncIteratorProtocol {
public func next() async throws -> Element? {}
}
}
@available(SwiftStdlib 5.1, *)
func test1() -> Error {
return ReaderSeq.Failure.x
}

View File

@@ -25,3 +25,30 @@ public struct SequenceAdapter<Base: AsyncSequence>: AsyncSequence {
// CHECK: @available( // CHECK: @available(
// CHECK-NEXT: public typealias Failure = Base.Failure // CHECK-NEXT: public typealias Failure = Base.Failure
} }
// CHECK: @available(
// CHECK-NEXT: public struct OtherSequenceAdapte
@available(SwiftStdlib 5.1, *)
public struct OtherSequenceAdapter<Base: AsyncSequence>: AsyncSequence {
// CHECK: public typealias Element = Base.Element
// CHECK-NOT: public typealias Failure
// CHECK: public struct Failure
// CHECK-LABEL: public struct AsyncIterator
// CHECK: @available{{.*}}macOS 10.15
// CHECK: @available(
// CHECK-NEXT: public typealias Failure = Base.Failure
public typealias Element = Base.Element
public struct Failure: Error { }
// CHECK-NOT: public typealias Failure
public struct AsyncIterator: AsyncIteratorProtocol {
public mutating func next() async rethrows -> Base.Element? { nil }
}
// CHECK: public func makeAsyncIterator
public func makeAsyncIterator() -> AsyncIterator { AsyncIterator() }
// CHECK-NOT: public typealias Failure
}