From 0b2e168c56e00dc3ad372b8fc9d7119e6974fc01 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 20 Feb 2024 15:27:02 -0500 Subject: [PATCH 1/4] Sema: Tweak abstract witness inference priority again We used to do attempt things in this order: - abstract witnesses, defaults, generic parameters I tried this but it broke things: - generic parameters, abstract witnesses, defaults Hoping this sticks: - abstract witnesses, generic parameters, defaults Fixes rdar://123262178. --- lib/Sema/AssociatedTypeInference.cpp | 52 +++++++++++-------- .../req/associated_type_generic_param.swift | 5 +- ...ce_fixed_type_experimental_inference.swift | 4 +- test/decl/protocol/req/rdar123262178.swift | 14 +++++ 4 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 test/decl/protocol/req/rdar123262178.swift diff --git a/lib/Sema/AssociatedTypeInference.cpp b/lib/Sema/AssociatedTypeInference.cpp index 4be7a3c94ba9c..ec0ad169c68df 100644 --- a/lib/Sema/AssociatedTypeInference.cpp +++ b/lib/Sema/AssociatedTypeInference.cpp @@ -2389,6 +2389,7 @@ Type AssociatedTypeInference::computeFixedTypeWitness( // any fix this associated type to a concrete type. for (auto conformance : getPeerConformances(conformance)) { auto *conformedProto = conformance->getProtocol(); + auto sig = conformedProto->getGenericSignature(); // FIXME: The RequirementMachine will assert on re-entrant construction. @@ -2704,35 +2705,40 @@ void AssociatedTypeInference::collectAbstractTypeWitnesses( if (system.hasResolvedTypeWitness(assocType->getName())) continue; - // If we find a default type definition, feed it to the system. - if (const auto &typeWitness = computeDefaultTypeWitness(assocType)) { - bool preferred = (typeWitness->getDefaultedAssocType()->getDeclContext() - == conformance->getProtocol()); - system.addDefaultTypeWitness(typeWitness->getType(), - typeWitness->getDefaultedAssocType(), - preferred); - } else { - // As a last resort, look for a generic parameter that matches the name - // of the associated type. - if (auto genericSig = dc->getGenericSignatureOfContext()) { - // Ignore the generic parameters for AsyncIteratorProtocol.Failure and - // AsyncSequence.Failure. - if (!isAsyncIteratorProtocolFailure(assocType)) { - for (auto *gp : genericSig.getInnermostGenericParams()) { - // Packs cannot witness associated type requirements. - if (gp->isParameterPack()) - continue; + bool found = false; - if (gp->getName() == assocType->getName()) { - system.addTypeWitness(assocType->getName(), - dc->mapTypeIntoContext(gp), - /*preferred=*/true); - } + // Look for a generic parameter that matches the name of the + // associated type. + if (auto genericSig = dc->getGenericSignatureOfContext()) { + // Ignore the generic parameters for AsyncIteratorProtocol.Failure and + // AsyncSequence.Failure. + if (!isAsyncIteratorProtocolFailure(assocType)) { + for (auto *gp : genericSig.getInnermostGenericParams()) { + // Packs cannot witness associated type requirements. + if (gp->isParameterPack()) + continue; + + if (gp->getName() == assocType->getName()) { + system.addTypeWitness(assocType->getName(), + dc->mapTypeIntoContext(gp), + /*preferred=*/true); + found = true; + break; } } } } + if (!found) { + // If we find a default type definition, feed it to the system. + if (const auto &typeWitness = computeDefaultTypeWitness(assocType)) { + bool preferred = (typeWitness->getDefaultedAssocType()->getDeclContext() + == conformance->getProtocol()); + system.addDefaultTypeWitness(typeWitness->getType(), + typeWitness->getDefaultedAssocType(), + preferred); + } + } } } diff --git a/test/decl/protocol/req/associated_type_generic_param.swift b/test/decl/protocol/req/associated_type_generic_param.swift index bfb44c3fe0f42..0a29eb1fd1bd2 100644 --- a/test/decl/protocol/req/associated_type_generic_param.swift +++ b/test/decl/protocol/req/associated_type_generic_param.swift @@ -1,5 +1,5 @@ // RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference -// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference +// RUN: not %target-typecheck-verify-swift -disable-experimental-associated-type-inference protocol P { associatedtype A = Int @@ -7,5 +7,4 @@ protocol P { struct S: P {} -// This is unfortunate but it is the behavior of Swift 5.10. -let x: Int.Type = S.A.self +let x: String.Type = S.A.self diff --git a/test/decl/protocol/req/associated_type_inference_fixed_type_experimental_inference.swift b/test/decl/protocol/req/associated_type_inference_fixed_type_experimental_inference.swift index fcee15a435c73..ce3ac5fc50063 100644 --- a/test/decl/protocol/req/associated_type_inference_fixed_type_experimental_inference.swift +++ b/test/decl/protocol/req/associated_type_inference_fixed_type_experimental_inference.swift @@ -212,7 +212,7 @@ do { // CHECK-NEXT: } struct Conformer1: P17a {} // expected-error {{type 'Conformer1' does not conform to protocol 'P17a'}} // CHECK-LABEL: Abstract type witness system for conformance of Conformer2 to P17b: { - // CHECK-NEXT: A => (unresolved), [[EQUIV_CLASS:0x[0-9a-f]+]] + // CHECK-NEXT: A => A (preferred), [[EQUIV_CLASS:0x[0-9a-f]+]] // CHECK-NEXT: B => (unresolved) // CHECK-NEXT: } struct Conformer2: P17b {} // expected-error {{type 'Conformer2' does not conform to protocol 'P17b'}} @@ -221,7 +221,7 @@ do { // CHECK-NEXT: } struct Conformer3: P17c {} // CHECK-LABEL: Abstract type witness system for conformance of Conformer4 to P17d: { - // CHECK-NEXT: A => Int (preferred), [[EQUIV_CLASS:0x[0-9a-f]+]] + // CHECK-NEXT: A => A (preferred), [[EQUIV_CLASS:0x[0-9a-f]+]] // CHECK-NEXT: B => Int (preferred), [[EQUIV_CLASS:0x[0-9a-f]+]] // CHECK-NEXT: } struct Conformer4: P17d {} diff --git a/test/decl/protocol/req/rdar123262178.swift b/test/decl/protocol/req/rdar123262178.swift new file mode 100644 index 0000000000000..c2825caa07194 --- /dev/null +++ b/test/decl/protocol/req/rdar123262178.swift @@ -0,0 +1,14 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +public protocol P { + associatedtype A = Never +} + +public struct G: P {} + +public struct ConcreteA {} + +public protocol Q: P where Self.A == ConcreteA {} + +extension G: Q where A == ConcreteA {} From b489baac95a2d5ee1d467556e342dbe784af6b98 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 20 Feb 2024 15:33:46 -0500 Subject: [PATCH 2/4] Add two regression tests for bugs already fixed --- test/decl/protocol/req/rdar123261282.swift | 21 ++++++++++++++++ test/decl/protocol/req/rdar123270042.swift | 28 ++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/decl/protocol/req/rdar123261282.swift create mode 100644 test/decl/protocol/req/rdar123270042.swift diff --git a/test/decl/protocol/req/rdar123261282.swift b/test/decl/protocol/req/rdar123261282.swift new file mode 100644 index 0000000000000..1d30564208786 --- /dev/null +++ b/test/decl/protocol/req/rdar123261282.swift @@ -0,0 +1,21 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +struct CustomCollection: RandomAccessCollection { + struct CustomIndices: RandomAccessCollection { + var count: Int { fatalError() } + + var startIndex: Int { fatalError() } + var endIndex: Int { fatalError() } + + subscript(index: Int) -> Int { fatalError() } + } + + var count: Int { fatalError() } + var indices: CustomIndices { fatalError() } + var startIndex: Int { fatalError() } + var endIndex: Int { fatalError() } + func index(before i: Int) -> Int { fatalError() } + func index(after i: Int) -> Int { fatalError() } + subscript(index: Int) -> T { fatalError() } +} diff --git a/test/decl/protocol/req/rdar123270042.swift b/test/decl/protocol/req/rdar123270042.swift new file mode 100644 index 0000000000000..32b88cbb87523 --- /dev/null +++ b/test/decl/protocol/req/rdar123270042.swift @@ -0,0 +1,28 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: not %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +func f(_: T) -> T.Element {} + +let x: Int = f(CustomCollection()) + +struct CustomCollection: RandomAccessCollection, MutableCollection { + typealias SubSequence = ArraySlice + typealias Index = Int + typealias Indices = Range + + var startIndex: Int { fatalError() } + var endIndex: Int { fatalError() } + var first: Element? { fatalError() } + var last: Element? { fatalError() } + + subscript(position: Index) -> Element { + get { fatalError() } + set { fatalError() } + } + + subscript(bounds: Indices) -> SubSequence { + get { fatalError() } + set { fatalError() } + } +} + From 29b08a92a5f097d84d873c65ed76ef0db515aedb Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 20 Feb 2024 23:25:48 -0500 Subject: [PATCH 3/4] Add two test cases that regressed after a previous attempt at fixing a bug --- ...sociated_type_inference_foundation_1.swift | 33 ++++++++++++++ ...sociated_type_inference_foundation_2.swift | 45 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 test/decl/protocol/req/associated_type_inference_foundation_1.swift create mode 100644 test/decl/protocol/req/associated_type_inference_foundation_2.swift diff --git a/test/decl/protocol/req/associated_type_inference_foundation_1.swift b/test/decl/protocol/req/associated_type_inference_foundation_1.swift new file mode 100644 index 0000000000000..f7498179c176e --- /dev/null +++ b/test/decl/protocol/req/associated_type_inference_foundation_1.swift @@ -0,0 +1,33 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +// REQUIRES: objc_interop + +import Foundation + +public struct CustomCollection: RandomAccessCollection { + public typealias Indices = Range + + public var startIndex: Int { fatalError() } + public var endIndex: Int { fatalError() } + public var count: Int { fatalError() } + + public subscript(position: Int) -> T { + get { fatalError() } + set { fatalError() } + } + + public var underestimatedCount: Int { fatalError() } +} + +extension CustomCollection: ContiguousBytes where Element == UInt8 { + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + fatalError() + } +} + +extension CustomCollection: DataProtocol where Element == UInt8 { + public var regions: CollectionOfOne> { + fatalError() + } +} diff --git a/test/decl/protocol/req/associated_type_inference_foundation_2.swift b/test/decl/protocol/req/associated_type_inference_foundation_2.swift new file mode 100644 index 0000000000000..8bb23dd01b1b7 --- /dev/null +++ b/test/decl/protocol/req/associated_type_inference_foundation_2.swift @@ -0,0 +1,45 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +// REQUIRES: objc_interop + +import Foundation + +public struct CustomCollection: RandomAccessCollection, MutableCollection, ExpressibleByArrayLiteral { + public typealias Indices = Range + public typealias Index = Int + + public init() {} + + public init(arrayLiteral elements: Element ...) { + fatalError() + } +} + +extension CustomCollection { + public var startIndex: Int { fatalError() } + public var endIndex: Int { fatalError() } + + public subscript(position: Int) -> Element { + get { fatalError() } + set { fatalError() } + } +} + +extension CustomCollection: RangeReplaceableCollection { + public mutating func append(_ newElement: Element) { } + public mutating func append(contentsOf newElements: S) where S.Element == Element { } + public mutating func reserveCapacity(_ minimumCapacity: Int) { } + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { } + public mutating func replaceSubrange(_ subRange: Range, with newElements: C) where C.Element == Element { } +} + +extension CustomCollection { + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { fatalError() } +} + +extension CustomCollection: ContiguousBytes where Element == UInt8 { } + +extension CustomCollection: DataProtocol where Element == UInt8 { + public var regions: CollectionOfOne> { fatalError() } +} From ba9086160283d9f3f73b543b12c3964409a3469e Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 20 Feb 2024 23:59:13 -0500 Subject: [PATCH 4/4] Sema: Try a little harder to infer associated types to generic parameters if all else fails If we have an abstract witness, we don't attempt a generic parameter binding at all. But if simplifying the abstract witness failed, we should still attempt it. This would be cleaner as a disjunction in the solver but I want to change behavior as little as possible, so this adds a new fallback that we run when all else fails. Fixes rdar://problem/123345520. --- lib/Sema/AssociatedTypeInference.cpp | 78 ++++++++++--------- .../associated_type_inference_stdlib_3.swift | 4 +- test/decl/protocol/req/rdar123345520.swift | 28 +++++++ 3 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 test/decl/protocol/req/rdar123345520.swift diff --git a/lib/Sema/AssociatedTypeInference.cpp b/lib/Sema/AssociatedTypeInference.cpp index ec0ad169c68df..bc4d8ea92e617 100644 --- a/lib/Sema/AssociatedTypeInference.cpp +++ b/lib/Sema/AssociatedTypeInference.cpp @@ -1017,6 +1017,10 @@ class AssociatedTypeInference { std::pair computeDerivedTypeWitness(AssociatedTypeDecl *assocType); + /// See if we have a generic parameter named the same as this associated + /// type. + Type computeGenericParamWitness(AssociatedTypeDecl *assocType) const; + /// Compute a type witness without using a specific potential witness. llvm::Optional computeAbstractTypeWitness(AssociatedTypeDecl *assocType); @@ -2657,6 +2661,28 @@ AssociatedTypeInference::computeAbstractTypeWitness( return llvm::None; } +/// Look for a generic parameter that matches the name of the +/// associated type. +Type AssociatedTypeInference::computeGenericParamWitness( + AssociatedTypeDecl *assocType) const { + if (auto genericSig = dc->getGenericSignatureOfContext()) { + // Ignore the generic parameters for AsyncIteratorProtocol.Failure and + // AsyncSequence.Failure. + if (!isAsyncIteratorProtocolFailure(assocType)) { + for (auto *gp : genericSig.getInnermostGenericParams()) { + // Packs cannot witness associated type requirements. + if (gp->isParameterPack()) + continue; + + if (gp->getName() == assocType->getName()) + return dc->mapTypeIntoContext(gp); + } + } + } + + return Type(); +} + void AssociatedTypeInference::collectAbstractTypeWitnesses( TypeWitnessSystem &system, ArrayRef unresolvedAssocTypes) const { @@ -2705,39 +2731,14 @@ void AssociatedTypeInference::collectAbstractTypeWitnesses( if (system.hasResolvedTypeWitness(assocType->getName())) continue; - bool found = false; - - // Look for a generic parameter that matches the name of the - // associated type. - if (auto genericSig = dc->getGenericSignatureOfContext()) { - // Ignore the generic parameters for AsyncIteratorProtocol.Failure and - // AsyncSequence.Failure. - if (!isAsyncIteratorProtocolFailure(assocType)) { - for (auto *gp : genericSig.getInnermostGenericParams()) { - // Packs cannot witness associated type requirements. - if (gp->isParameterPack()) - continue; - - if (gp->getName() == assocType->getName()) { - system.addTypeWitness(assocType->getName(), - dc->mapTypeIntoContext(gp), - /*preferred=*/true); - found = true; - break; - } - } - } - } - - if (!found) { - // If we find a default type definition, feed it to the system. - if (const auto &typeWitness = computeDefaultTypeWitness(assocType)) { - bool preferred = (typeWitness->getDefaultedAssocType()->getDeclContext() - == conformance->getProtocol()); - system.addDefaultTypeWitness(typeWitness->getType(), - typeWitness->getDefaultedAssocType(), - preferred); - } + if (auto gpType = computeGenericParamWitness(assocType)) { + system.addTypeWitness(assocType->getName(), gpType, /*preferred=*/true); + } else if (const auto &typeWitness = computeDefaultTypeWitness(assocType)) { + bool preferred = (typeWitness->getDefaultedAssocType()->getDeclContext() + == conformance->getProtocol()); + system.addDefaultTypeWitness(typeWitness->getType(), + typeWitness->getDefaultedAssocType(), + preferred); } } } @@ -3156,8 +3157,15 @@ AssociatedTypeDecl *AssociatedTypeInference::inferAbstractTypeWitnesses( // If simplification failed, give up. if (type->hasTypeParameter()) { - LLVM_DEBUG(llvm::dbgs() << "-- Simplification failed: " << type << "\n"); - return assocType; + if (auto gpType = computeGenericParamWitness(assocType)) { + LLVM_DEBUG(llvm::dbgs() << "-- Found generic parameter as last resort: " + << gpType << "\n"); + type = gpType; + typeWitnesses.insert(assocType, {type, reqDepth}); + } else { + LLVM_DEBUG(llvm::dbgs() << "-- Simplification failed: " << type << "\n"); + return assocType; + } } if (const auto failed = diff --git a/test/decl/protocol/req/associated_type_inference_stdlib_3.swift b/test/decl/protocol/req/associated_type_inference_stdlib_3.swift index 4eaf78b1e90a3..274197f17ba72 100644 --- a/test/decl/protocol/req/associated_type_inference_stdlib_3.swift +++ b/test/decl/protocol/req/associated_type_inference_stdlib_3.swift @@ -1,8 +1,6 @@ -// RUN: not %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference // RUN: not %target-typecheck-verify-swift -disable-experimental-associated-type-inference -// FIXME: Get this passing with -enable-experimental-associated-type-inference again. - struct FooIterator: IteratorProtocol { typealias Element = T.Element diff --git a/test/decl/protocol/req/rdar123345520.swift b/test/decl/protocol/req/rdar123345520.swift new file mode 100644 index 0000000000000..26ec1fa489fde --- /dev/null +++ b/test/decl/protocol/req/rdar123345520.swift @@ -0,0 +1,28 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-associated-type-inference +// RUN: %target-typecheck-verify-swift -disable-experimental-associated-type-inference + +struct G1 {} + +struct G2: AP { + func f1(_: G1<(B) -> A>, _: G1) -> G1 { fatalError() } + func f2(_: (A) -> C) -> G1 { fatalError() } +} + +protocol OP: EP { + associatedtype L + associatedtype R + + func f1(_: G1, _: G1) -> G1 +} + +extension OP { + func f1(_: G1?, _: G1?) -> G1 { fatalError() } +} + +protocol AP: OP where L == (B) -> A, R == B {} + +protocol EP { + associatedtype A + associatedtype B + func f2(_: (A) -> C) -> G1 +}