From d0f14a6a6e70b3ef2478c337e7b08975c9a48904 Mon Sep 17 00:00:00 2001 From: John Hui Date: Mon, 22 Sep 2025 18:57:29 -0700 Subject: [PATCH] [cxx-interop] Fix inherited nested types When a derived class inherits a nested type from a base class in C++, we expect that derived type member to be the same as the base nested type. This patch fixes the ClangImporter's base member cloning to make that the case, and also fixes up some test cases (renames "sub-type" to "nested type" because the term "subtype" is overloaded and misleading here). rdar://161137420 --- lib/ClangImporter/ClangImporter.cpp | 2 +- .../class/inheritance/Inputs/module.modulemap | 4 +- .../Inputs/{sub-types.h => nested-types.h} | 2 + ...ft => nested-types-module-interface.swift} | 32 +++-- .../nested-types-typechecker.swift | 113 ++++++++++++++++++ .../type-aliases-module-interface.swift | 4 +- .../type-aliases-typechecker.swift | 51 ++++++++ 7 files changed, 196 insertions(+), 12 deletions(-) rename test/Interop/Cxx/class/inheritance/Inputs/{sub-types.h => nested-types.h} (83%) rename test/Interop/Cxx/class/inheritance/{sub-types-module-interface.swift => nested-types-module-interface.swift} (55%) create mode 100644 test/Interop/Cxx/class/inheritance/nested-types-typechecker.swift create mode 100644 test/Interop/Cxx/class/inheritance/type-aliases-typechecker.swift diff --git a/lib/ClangImporter/ClangImporter.cpp b/lib/ClangImporter/ClangImporter.cpp index 6182b43e83a50..655cd59b263fd 100644 --- a/lib/ClangImporter/ClangImporter.cpp +++ b/lib/ClangImporter/ClangImporter.cpp @@ -6273,7 +6273,7 @@ static ValueDecl *cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext, auto out = new (rawMemory) TypeAliasDecl( typeDecl->getLoc(), typeDecl->getLoc(), typeDecl->getName(), typeDecl->getLoc(), nullptr, newContext); - out->setUnderlyingType(typeDecl->getInterfaceType()); + out->setUnderlyingType(typeDecl->getDeclaredInterfaceType()); out->setAccess(access); inheritance.setUnavailableIfNecessary(decl, out); return out; diff --git a/test/Interop/Cxx/class/inheritance/Inputs/module.modulemap b/test/Interop/Cxx/class/inheritance/Inputs/module.modulemap index c4702d40aa4fb..007e8f550b970 100644 --- a/test/Interop/Cxx/class/inheritance/Inputs/module.modulemap +++ b/test/Interop/Cxx/class/inheritance/Inputs/module.modulemap @@ -16,8 +16,8 @@ module ReferenceToDerived { requires cplusplus } -module SubTypes { - header "sub-types.h" +module NestedTypes { + header "nested-types.h" } module Subscripts { diff --git a/test/Interop/Cxx/class/inheritance/Inputs/sub-types.h b/test/Interop/Cxx/class/inheritance/Inputs/nested-types.h similarity index 83% rename from test/Interop/Cxx/class/inheritance/Inputs/sub-types.h rename to test/Interop/Cxx/class/inheritance/Inputs/nested-types.h index b5f013fe2eb32..866c21925a909 100644 --- a/test/Interop/Cxx/class/inheritance/Inputs/sub-types.h +++ b/test/Interop/Cxx/class/inheritance/Inputs/nested-types.h @@ -20,3 +20,5 @@ struct Base { }; struct Derived : Base {}; +struct Derived1 : Base {}; +struct Derived2 : Derived1 {}; diff --git a/test/Interop/Cxx/class/inheritance/sub-types-module-interface.swift b/test/Interop/Cxx/class/inheritance/nested-types-module-interface.swift similarity index 55% rename from test/Interop/Cxx/class/inheritance/sub-types-module-interface.swift rename to test/Interop/Cxx/class/inheritance/nested-types-module-interface.swift index 4029fc7ccb08c..201556b074e3e 100644 --- a/test/Interop/Cxx/class/inheritance/sub-types-module-interface.swift +++ b/test/Interop/Cxx/class/inheritance/nested-types-module-interface.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-ide-test -print-module -module-to-print=SubTypes -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s +// RUN: %target-swift-ide-test -print-module -module-to-print=NestedTypes -I %S/Inputs -source-filename=x -cxx-interoperability-mode=default | %FileCheck %s // CHECK: struct Base { // CHECK-NEXT: init() @@ -39,11 +39,29 @@ // CHECK-NEXT: } // CHECK-NEXT: } -// CHECK-NEXT: struct Derived { +// CHECK: struct Derived { // CHECK-NEXT: init() -// CHECK-NEXT: typealias EnumClass = Base.EnumClass.Type -// CHECK-NEXT: typealias Enum = Base.Enum.Type -// CHECK-NEXT: typealias Struct = Base.Struct.Type -// CHECK-NEXT: typealias Parent = Base.Parent.Type -// CHECK-NEXT: typealias Union = Base.Union.Type +// CHECK-NEXT: typealias EnumClass = Base.EnumClass +// CHECK-NEXT: typealias Enum = Base.Enum +// CHECK-NEXT: typealias Struct = Base.Struct +// CHECK-NEXT: typealias Parent = Base.Parent +// CHECK-NEXT: typealias Union = Base.Union +// CHECK-NEXT: } + +// CHECK: struct Derived1 { +// CHECK-NEXT: init() +// CHECK-NEXT: typealias EnumClass = Base.EnumClass +// CHECK-NEXT: typealias Enum = Base.Enum +// CHECK-NEXT: typealias Struct = Base.Struct +// CHECK-NEXT: typealias Parent = Base.Parent +// CHECK-NEXT: typealias Union = Base.Union +// CHECK-NEXT: } + +// CHECK: struct Derived2 { +// CHECK-NEXT: init() +// CHECK-NEXT: typealias EnumClass = Base.EnumClass +// CHECK-NEXT: typealias Enum = Base.Enum +// CHECK-NEXT: typealias Struct = Base.Struct +// CHECK-NEXT: typealias Parent = Base.Parent +// CHECK-NEXT: typealias Union = Base.Union // CHECK-NEXT: } diff --git a/test/Interop/Cxx/class/inheritance/nested-types-typechecker.swift b/test/Interop/Cxx/class/inheritance/nested-types-typechecker.swift new file mode 100644 index 0000000000000..f56f8672d275c --- /dev/null +++ b/test/Interop/Cxx/class/inheritance/nested-types-typechecker.swift @@ -0,0 +1,113 @@ +// RUN: %target-typecheck-verify-swift -I %S/Inputs -cxx-interoperability-mode=default + +// This test ensures that nested type inherited from the base type are +// interchangeable with the base types themselves. + +import NestedTypes + +func parent( + bc: Base.EnumClass, + be: Base.Enum, + bs: Base.Struct, + bp: Base.Parent, + bpc: Base.Parent.Child, + bu: Base.Union, +) { + let dc: Derived.EnumClass = bc + let de: Derived.Enum = be + let ds: Derived.Struct = bs + let dp: Derived.Parent = bp + let dpc: Derived.Parent.Child = bpc + let du: Derived.Union = bu + + let _: Base.EnumClass = dc + let _: Base.Enum = de + let _: Base.Struct = ds + let _: Base.Parent = dp + let _: Base.Parent.Child = dpc + let _: Base.Union = du +} + +func grandparent( + bc: Base.EnumClass, + be: Base.Enum, + bs: Base.Struct, + bp: Base.Parent, + bpc: Base.Parent.Child, + bu: Base.Union, +) { + let dc: Derived2.EnumClass = bc + let de: Derived2.Enum = be + let ds: Derived2.Struct = bs + let dp: Derived2.Parent = bp + let dpc: Derived2.Parent.Child = bpc + let du: Derived2.Union = bu + + let _: Derived.EnumClass = dc + let _: Derived.Enum = de + let _: Derived.Struct = ds + let _: Derived.Parent = dp + let _: Derived.Parent.Child = dpc + let _: Derived.Union = du +} + +func siblings( + dc: Derived.EnumClass, + de: Derived.Enum, + ds: Derived.Struct, + dp: Derived.Parent, + dpc: Derived.Parent.Child, + du: Derived.Union, +) { + let _: Derived1.EnumClass = dc + let _: Derived1.Enum = de + let _: Derived1.Struct = ds + let _: Derived1.Parent = dp + let _: Derived1.Parent.Child = dpc + let _: Derived1.Union = du +} + +// Instances created from derived class can be type-annotated with base class +// and vice versa +func assigner() { + let _: Base.EnumClass = Derived.EnumClass.ecb + let _: Base.Enum = Derived.ea // expected-error {{type 'Derived' has no member 'ea'}} + // ^FIXME: nested enums are broken, so inherited nested enums are broken too + let _: Base.Struct = Derived.Struct(sa: 4, sb: 2) + let _: Base.Parent = Derived.Parent() + let _: Base.Parent.Child = Derived.Parent.Child(pca: 42) + let _: Base.Union = Derived.Union(ua: 42) + + let _: Derived.EnumClass = Base.EnumClass.ecc + let _: Derived.Enum = Base.ea // expected-error {{type 'Base' has no member 'ea'}} + // ^FIXME: nested enums are broken, so inherited nested enums are broken too + let _: Derived.Struct = Base.Struct(sa: 4, sb: 2) + let _: Derived.Parent = Base.Parent() + let _: Derived.Parent.Child = Base.Parent.Child(pca: 42) + let _: Derived.Union = Base.Union(ua: 42) +} + +// Extensions on base type should be "seen" in derived types too, and vice versa +extension Base.Parent { + static func getChild1() -> Child { + return Child(pca: 111) + } +} + +extension Derived { + func haveChild1() { + let _: Parent.Child = Parent.getChild1() + } +} + +extension Derived.Parent { + static func getChild2() -> Child { + return Child(pca: 111) + } +} + +extension Base { + func haveChild2() { + let _: Parent.Child = Parent.getChild2() + } +} diff --git a/test/Interop/Cxx/class/inheritance/type-aliases-module-interface.swift b/test/Interop/Cxx/class/inheritance/type-aliases-module-interface.swift index 4dde31f117869..66a28d63424ef 100644 --- a/test/Interop/Cxx/class/inheritance/type-aliases-module-interface.swift +++ b/test/Interop/Cxx/class/inheritance/type-aliases-module-interface.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-ide-test -print-module -module-to-print=TypeAliases -I %S/Inputs -source-filename=x -enable-experimental-cxx-interop | %FileCheck %s +// RUN: %target-swift-ide-test -print-module -module-to-print=TypeAliases -I %S/Inputs -source-filename=x -cxx-interoperability-mode=default | %FileCheck %s // CHECK: struct Base { // CHECK-NEXT: init() @@ -11,7 +11,7 @@ // CHECK-NEXT: struct Derived { // CHECK-NEXT: init() -// CHECK-NEXT: typealias Struct = Base.Struct.Type +// CHECK-NEXT: typealias Struct = Base.Struct // CHECK-NEXT: typealias T = Int32 // CHECK-NEXT: typealias U = Base.Struct // CHECK-NEXT: } diff --git a/test/Interop/Cxx/class/inheritance/type-aliases-typechecker.swift b/test/Interop/Cxx/class/inheritance/type-aliases-typechecker.swift new file mode 100644 index 0000000000000..a59bc3e3cfedb --- /dev/null +++ b/test/Interop/Cxx/class/inheritance/type-aliases-typechecker.swift @@ -0,0 +1,51 @@ +// RUN: %target-typecheck-verify-swift -I %S/Inputs -cxx-interoperability-mode=default + +// This test ensures that nested type aliases inherited from the base type are +// interchangeable with the base types themselves. + +import TypeAliases + +extension Derived { + func takesBaseTypes(bs: Base.Struct, bt: Base.T, bu: Base.U) { + let _: Struct = bs + let _: U = bs + + let _: T = bt + let _: Int32 = bt + + let _: U = bu + let _: Struct = bu + } + + func takesDerivedTypes(ds: Struct, dt: T, du: U) { + let _: Base.Struct = ds + let _: Base.U = ds + + let _: Base.T = dt + let _: Int32 = dt + + let _: Base.U = du + let _: Base.Struct = du + } +} +func takesBaseTypes(bs: Base.Struct, bt: Base.T, bu: Base.U) { + let _: Derived.Struct = bs + let _: Derived.U = bs + + let _: Derived.T = bt + let _: Int32 = bt + + let _: Derived.U = bu + let _: Derived.Struct = bu +} + +func takesDerivedTypes(ds: Derived.Struct, dt: Derived.T, du: Derived.U) { + let _: Base.Struct = ds + let _: Base.U = ds + + let _: Base.T = dt + let _: Int32 = dt + + let _: Base.U = du + let _: Base.Struct = du +}