From 6701e5b39b77e0537923fde0417e7ccf704b63b2 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Tue, 9 Dec 2025 19:43:28 -0500 Subject: [PATCH] Sema: Tighten ridiculous disambiguation hack for overloaded type declarations We noticed that a project failed to build with prepared overloads enabled, but built successfully with prepared overloads disabled. It turns out that the code clearly should never have been accepted in the first place, because the type checker was making an arbitrary choice between two nominal type declarations with the same name. Further inspection revealed this was because of a FIXME added in 2013 which was far too broad. Tighten up the logic here to only disambiguate if at least one of the two declarations is a type alias, and it has the same underlying type as the other one. A choice between unrelated nominal type declarations should always be ambiguous. This still isn't perfect, because we might have two generic type aliases that are referenced in both solutions with different substitution maps, in which case we will still erroneously pick the first one. But this is better than the old logic, at least. Fixes rdar://165863775. --- lib/Sema/CSRanking.cpp | 32 ++++++++++----- ..._witness_from_parameterized_protocol.swift | 6 ++- ...biguous_specialized_name_diagnostics.swift | 6 +-- test/Constraints/invalid_decl_ref.swift | 6 +-- test/Constraints/rdar165863775.swift | 40 +++++++++++++++++++ .../NameLookup/member_import_visibility.swift | 2 +- validation-test/Sema/rdar84879566.swift | 2 +- .../compiler_crashers_fixed/issue-85364.swift | 2 +- 8 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 test/Constraints/rdar165863775.swift diff --git a/lib/Sema/CSRanking.cpp b/lib/Sema/CSRanking.cpp index 9830c66dbe91e..58bfe17faa44d 100644 --- a/lib/Sema/CSRanking.cpp +++ b/lib/Sema/CSRanking.cpp @@ -195,16 +195,28 @@ static bool sameDecl(Decl *decl1, Decl *decl2) { if (decl1 == decl2) return true; - // All types considered identical. - // FIXME: This is a hack. What we really want is to have substituted the - // base type into the declaration reference, so that we can compare the - // actual types to which two type declarations resolve. If those types are - // equivalent, then it doesn't matter which declaration is chosen. - if (isa(decl1) && isa(decl2)) - return true; - - if (decl1->getKind() != decl2->getKind()) - return false; + // Special hack to allow type aliases with same underlying type. + // + // FIXME: Check substituted types instead. + // + // FIXME: Perhaps this should be handled earlier in name lookup. + auto *typeDecl1 = dyn_cast(decl1); + auto *typeDecl2 = dyn_cast(decl2); + if (typeDecl1 && typeDecl2) { + auto type1 = typeDecl1->getDeclaredInterfaceType(); + auto type2 = typeDecl2->getDeclaredInterfaceType(); + + // Handle unbound generic type aliases, eg + // + // struct Array {} + // typealias MyArray = Array + if (type1->is()) + type1 = type1->getAnyNominal()->getDeclaredInterfaceType(); + if (type2->is()) + type2 = type2->getAnyNominal()->getDeclaredInterfaceType(); + + return type1->isEqual(type2); + } return false; } diff --git a/test/AssociatedTypeInference/type_witness_from_parameterized_protocol.swift b/test/AssociatedTypeInference/type_witness_from_parameterized_protocol.swift index 81aff5087217a..aef02ec1946f6 100644 --- a/test/AssociatedTypeInference/type_witness_from_parameterized_protocol.swift +++ b/test/AssociatedTypeInference/type_witness_from_parameterized_protocol.swift @@ -43,10 +43,12 @@ struct S4: (P & Q) & R { } struct Bad: P { // expected-error {{type 'Bad' does not conform to protocol 'P'}} - typealias A = String // expected-note {{possibly intended match}} + typealias A = String + // expected-note@-1 {{possibly intended match}} + // expected-note@-2 {{found this candidate}} } -let x = Bad.A.self +let x = Bad.A.self // expected-error {{ambiguous use of 'A'}} g(x) struct Circle: Q {} diff --git a/test/Constraints/ambiguous_specialized_name_diagnostics.swift b/test/Constraints/ambiguous_specialized_name_diagnostics.swift index 3ecf7d8d6cfe9..84107a18ec25d 100644 --- a/test/Constraints/ambiguous_specialized_name_diagnostics.swift +++ b/test/Constraints/ambiguous_specialized_name_diagnostics.swift @@ -38,10 +38,10 @@ import A import B func test() { - _ = S(t: 42) // expected-error {{ambiguous use of 'init(t:)'}} + _ = S(t: 42) // expected-error {{ambiguous use of 'S'}} - S(t: 42).test() // expected-error {{ambiguous use of 'init(t:)'}} + S(t: 42).test() // expected-error {{ambiguous use of 'S'}} S.staticFn() - // expected-error@-1 {{ambiguous use of 'staticFn()'}} + // expected-error@-1 {{ambiguous use of 'S'}} } diff --git a/test/Constraints/invalid_decl_ref.swift b/test/Constraints/invalid_decl_ref.swift index 7c65fa085435a..ca80822bc6e69 100644 --- a/test/Constraints/invalid_decl_ref.swift +++ b/test/Constraints/invalid_decl_ref.swift @@ -1,9 +1,7 @@ -// RUN: %target-swift-frontend -module-name SomeModule -typecheck -verify -dump-ast -import-objc-header %S/Inputs/imported_type.h %s | %FileCheck %s +// RUN: %target-swift-frontend -module-name SomeModule -typecheck -verify -import-objc-header %S/Inputs/imported_type.h %s -verify-ignore-unrelated // REQUIRES: objc_interop import Foundation -// CHECK: declref_expr type="module" -// CHECK-NEXT: type_expr type="Data.Type" -let type = SomeModule.Data.self +let type = SomeModule.Data.self // expected-error {{ambiguous use of 'Data'}} diff --git a/test/Constraints/rdar165863775.swift b/test/Constraints/rdar165863775.swift new file mode 100644 index 0000000000000..46770eecf2e98 --- /dev/null +++ b/test/Constraints/rdar165863775.swift @@ -0,0 +1,40 @@ +// RUN: %empty-directory(%t) +// RUN: %empty-directory(%t/src) +// RUN: split-file %s %t/src + +// RUN: %target-swift-frontend -emit-module %t/src/A.swift \ +// RUN: -module-name A \ +// RUN: -emit-module-path %t/A.swiftmodule + +// RUN: %target-swift-frontend -emit-module %t/src/B.swift \ +// RUN: -module-name B \ +// RUN: -emit-module-path %t/B.swiftmodule + +// RUN: %target-swift-frontend -typecheck -verify -verify-ignore-unrelated -module-name main -I %t %t/src/main.swift + +//--- A.swift +public struct Bag { +} + +//--- B.swift +public struct Bag { +} + +//--- main.swift +import A +import B + +protocol P { +} + +struct Test { + func inject(_: T.Type = T.self) {} + + func inject(_: T) {} + + func inject(_: T) async -> T {} +} + +func test(t: Test) { + t.inject(Bag.self) // expected-error {{ambiguous use of 'Bag'}} +} diff --git a/test/NameLookup/member_import_visibility.swift b/test/NameLookup/member_import_visibility.swift index 741dadc37b27c..6b664630aba87 100644 --- a/test/NameLookup/member_import_visibility.swift +++ b/test/NameLookup/member_import_visibility.swift @@ -85,7 +85,7 @@ extension X { _ = (NestedInA, NestedInB, NestedInC).self // expected-member-visibility-error{{struct 'NestedInB' is not available due to missing import of defining module 'members_B'}} _ = GenericType.self // expected-member-visibility-error{{struct 'NestedInB' is not available due to missing import of defining module 'members_B'}} _ = NestedInC.self - _ = AmbiguousNestedType.self + _ = AmbiguousNestedType.self // expected-ambiguity-error{{ambiguous use of 'AmbiguousNestedType'}} } var hasNestedInAType: NestedInA { fatalError() } diff --git a/validation-test/Sema/rdar84879566.swift b/validation-test/Sema/rdar84879566.swift index a4d1e24b42ec5..9c9a1ba7a0dc3 100644 --- a/validation-test/Sema/rdar84879566.swift +++ b/validation-test/Sema/rdar84879566.swift @@ -20,6 +20,6 @@ struct TupleBuilder { struct MyApp: Tupled { var tuple: some Any { - MyView() // expected-error {{ambiguous use of 'init()'}} + MyView() // expected-error {{ambiguous use of 'MyView'}} } } diff --git a/validation-test/compiler_crashers_fixed/issue-85364.swift b/validation-test/compiler_crashers_fixed/issue-85364.swift index ce66778201aab..e4290d48174cc 100644 --- a/validation-test/compiler_crashers_fixed/issue-85364.swift +++ b/validation-test/compiler_crashers_fixed/issue-85364.swift @@ -9,7 +9,7 @@ struct S1 { // expected-note {{found this candidate}} \ var c: () -> Void } -S1 {} // expected-error {{ambiguous use of 'init'}} +S1 {} // expected-error {{ambiguous use of 'S1'}} struct S1 { // expected-note {{found this candidate}} \ // expected-error {{invalid redeclaration of 'S1'}}