From c278717887c46cdf4e4f624f4f26c6895abb9539 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 7 Oct 2025 00:20:51 -0700 Subject: [PATCH] [CSSimplify] Avoid simplifying dependent members until pack expansions in the based are bound Dependent members cannot be simplified if base type contains unresolved pack expansion type variables because they don't give enough information to substitution logic to form a correct type. For example: ``` protocol P { associatedtype V } struct S : P { typealias V = (repeat (each T)?) } ``` If pack expansion is represented as `$T1` and its pattern is `$T2`, a reference to `V` would get a type `S.V` and simplified version would be `Optional` instead of `Pack{repeat Optional<$T2>}` because `$T1` is treated as a substitution for `each T` until bound. Resolves: rdar://161207705 --- include/swift/AST/Types.h | 3 +- lib/Sema/CSSimplify.cpp | 48 +++++++++++++++++-- .../pack-expansion-expressions.swift | 20 +++++++- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index c019ea5d60231..fa45363a8d9f3 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -850,12 +850,11 @@ class alignas(1 << TypeAlignInBits) TypeBase /// type variables referenced by this type. void getTypeVariables(SmallPtrSetImpl &typeVariables); -private: +public: /// If the receiver is a `DependentMemberType`, returns its root. Otherwise, /// returns the receiver. Type getDependentMemberRoot(); -public: /// Determine whether this type is a type parameter, which is either a /// GenericTypeParamType or a DependentMemberType. /// diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 27675a08699d0..269280c946d70 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -7099,6 +7099,20 @@ static bool isTupleWithUnresolvedPackExpansion(Type type) { return false; } +static bool isDependentMemberTypeWithBaseThatContainsUnresolvedPackExpansions( + ConstraintSystem &cs, Type type) { + if (!type->is()) + return false; + + auto baseTy = cs.getFixedTypeRecursive(type->getDependentMemberRoot(), + /*wantRValue=*/true); + llvm::SmallPtrSet typeVars; + baseTy->getTypeVariables(typeVars); + return llvm::any_of(typeVars, [](const TypeVariableType *typeVar) { + return typeVar->getImpl().isPackExpansion(); + }); +} + ConstraintSystem::TypeMatchResult ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, TypeMatchOptions flags, @@ -7135,7 +7149,7 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, // // along any unsolved path. No other returns should produce // SolutionKind::Unsolved or inspect TMF_GenerateConstraints. - auto formUnsolvedResult = [&] { + auto formUnsolvedResult = [&](bool useOriginalTypes = false) { // If we're supposed to generate constraints (i.e., this is a // newly-generated constraint), do so now. if (flags.contains(TMF_GenerateConstraints)) { @@ -7144,8 +7158,13 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, // this new constraint will be solved at a later point. // Obviously, this must not happen at the top level, or the // algorithm would not terminate. - addUnsolvedConstraint(Constraint::create(*this, kind, type1, type2, - getConstraintLocator(locator))); + if (useOriginalTypes) { + addUnsolvedConstraint(Constraint::create( + *this, kind, origType1, origType2, getConstraintLocator(locator))); + } else { + addUnsolvedConstraint(Constraint::create( + *this, kind, type1, type2, getConstraintLocator(locator))); + } return getTypeMatchSuccess(); } @@ -7396,6 +7415,29 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind, } } + // Dependent members cannot be simplified if base type contains unresolved + // pack expansion type variables because they don't give enough information + // to substitution logic to form a correct type. For example: + // + // ``` + // protocol P { associatedtype V } + // struct S : P { typealias V = (repeat (each T)?) } + // ``` + // + // If pack expansion is represented as `$T1` and its pattern is `$T2`, a + // reference to `V` would get a type `S.V` and simplified version + // would be `Optional` instead of `Pack{repeat Optional<$T2>}` + // because `$T1` is treated as a substitution for `each T` until bound. + if (isDependentMemberTypeWithBaseThatContainsUnresolvedPackExpansions( + *this, origType1) || + isDependentMemberTypeWithBaseThatContainsUnresolvedPackExpansions( + *this, origType2)) { + // It's important to preserve the original types here because any attempt + // at simplification or canonicalization wouldn't produce a correct type + // util pack expansion type variables are bound. + return formUnsolvedResult(/*useOriginalTypes=*/true); + } + llvm::SmallVector conversionsOrFixes; // Decompose parallel structure. diff --git a/test/Constraints/pack-expansion-expressions.swift b/test/Constraints/pack-expansion-expressions.swift index c9b6d65fae32a..aac7b76880256 100644 --- a/test/Constraints/pack-expansion-expressions.swift +++ b/test/Constraints/pack-expansion-expressions.swift @@ -360,7 +360,7 @@ func test_pack_expansion_specialization(tuple: (Int, String, Float)) { } // rdar://107280056 - "Ambiguous without more context" with opaque return type + variadics -protocol Q { +protocol Q { associatedtype B } @@ -815,3 +815,21 @@ func testPackToScalarShortFormConstructor() { S(repeat each xs) // expected-error {{cannot pass value pack expansion to non-pack parameter of type 'Int'}} } } + + +func test_dependent_members() { + struct Variadic: Q { + typealias B = (repeat (each T)?) + + init(_: repeat each T) {} + static func f(_: repeat each T) -> Self {} + } + + func test_init(_ c1: C1, _ c2: C2) -> some Q<(C1?, C2?)> { + return Variadic(c1, c2) // Ok + } + + func test_static(_ c1: C1, _ c2: C2) -> some Q<(C1?, C2?)> { + return Variadic.f(c1, c2) // Ok + } +}