Skip to content

Commit 1341516

Browse files
authored
[cxx-interop] Fix spurious ambiguous member lookup for eagerly imported members (#78673)
Follow-up from #78132, which did not fix issues related to eagerly imported members like subscripts. This patch restructures recursive ClangRecordMemberLookup requests to importBaseMemberDecl() in the recursive calls, rather than propagating base member decls up to the initial lookup request and doing the import. Doing so seems to fix lingering resolution issues (which I've added to the regression tests). rdar://141069984
1 parent e17c36d commit 1341516

File tree

6 files changed

+200
-79
lines changed

6 files changed

+200
-79
lines changed

include/swift/ClangImporter/ClangImporterRequests.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,29 +134,43 @@ class CXXNamespaceMemberLookup
134134
/// The input type for a record member lookup request.
135135
struct ClangRecordMemberLookupDescriptor final {
136136
NominalTypeDecl *recordDecl;
137+
NominalTypeDecl *inheritingDecl;
137138
DeclName name;
138-
bool inherited;
139139

140-
ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name,
141-
bool inherited = false)
142-
: recordDecl(recordDecl), name(name), inherited(inherited) {
140+
ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name)
141+
: recordDecl(recordDecl), inheritingDecl(recordDecl), name(name) {
143142
assert(isa<clang::RecordDecl>(recordDecl->getClangDecl()));
144143
}
145144

146145
friend llvm::hash_code
147146
hash_value(const ClangRecordMemberLookupDescriptor &desc) {
148-
return llvm::hash_combine(desc.name, desc.recordDecl);
147+
return llvm::hash_combine(desc.name, desc.recordDecl, desc.inheritingDecl);
149148
}
150149

151150
friend bool operator==(const ClangRecordMemberLookupDescriptor &lhs,
152151
const ClangRecordMemberLookupDescriptor &rhs) {
153-
return lhs.name == rhs.name && lhs.recordDecl == rhs.recordDecl;
152+
return lhs.name == rhs.name && lhs.recordDecl == rhs.recordDecl &&
153+
lhs.inheritingDecl == rhs.inheritingDecl;
154154
}
155155

156156
friend bool operator!=(const ClangRecordMemberLookupDescriptor &lhs,
157157
const ClangRecordMemberLookupDescriptor &rhs) {
158158
return !(lhs == rhs);
159159
}
160+
161+
private:
162+
friend class ClangRecordMemberLookup;
163+
164+
// This private constructor should only be used in ClangRecordMemberLookup,
165+
// for recursively traversing base classes that inheritingDecl inherites from.
166+
ClangRecordMemberLookupDescriptor(NominalTypeDecl *recordDecl, DeclName name,
167+
NominalTypeDecl *inheritingDecl)
168+
: recordDecl(recordDecl), inheritingDecl(inheritingDecl), name(name) {
169+
assert(isa<clang::RecordDecl>(recordDecl->getClangDecl()));
170+
assert(isa<clang::CXXRecordDecl>(inheritingDecl->getClangDecl()));
171+
assert(recordDecl != inheritingDecl &&
172+
"recursive calls should lookup elsewhere");
173+
}
160174
};
161175

162176
void simple_display(llvm::raw_ostream &out,

lib/AST/NameLookupRequests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,9 @@ void swift::simple_display(llvm::raw_ostream &out,
539539
simple_display(out, desc.name);
540540
out << " in ";
541541
simple_display(out, desc.recordDecl);
542+
if (desc.recordDecl != desc.inheritingDecl)
543+
out << " inherited by ";
544+
simple_display(out, desc.inheritingDecl);
542545
}
543546

544547
SourceLoc

lib/ClangImporter/ClangImporter.cpp

Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/AST/DiagnosticEngine.h"
2828
#include "swift/AST/DiagnosticsClangImporter.h"
2929
#include "swift/AST/DiagnosticsSema.h"
30+
#include "swift/AST/Evaluator.h"
3031
#include "swift/AST/IRGenOptions.h"
3132
#include "swift/AST/ImportCache.h"
3233
#include "swift/AST/LinkLibrary.h"
@@ -41,6 +42,7 @@
4142
#include "swift/AST/Types.h"
4243
#include "swift/Basic/Assertions.h"
4344
#include "swift/Basic/Defer.h"
45+
#include "swift/Basic/LLVM.h"
4446
#include "swift/Basic/Platform.h"
4547
#include "swift/Basic/Range.h"
4648
#include "swift/Basic/SourceLoc.h"
@@ -6147,28 +6149,57 @@ cloneBaseMemberDecl(ValueDecl *decl, DeclContext *newContext) {
61476149
TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
61486150
Evaluator &evaluator, ClangRecordMemberLookupDescriptor desc) const {
61496151
NominalTypeDecl *recordDecl = desc.recordDecl;
6152+
NominalTypeDecl *inheritingDecl = desc.inheritingDecl;
61506153
DeclName name = desc.name;
6151-
bool inherited = desc.inherited;
61526154

61536155
auto &ctx = recordDecl->getASTContext();
6154-
auto allResults = evaluateOrDefault(
6156+
auto directResults = evaluateOrDefault(
61556157
ctx.evaluator,
61566158
ClangDirectLookupRequest({recordDecl, recordDecl->getClangDecl(), name}),
61576159
{});
61586160

61596161
// Find the results that are actually a member of "recordDecl".
61606162
TinyPtrVector<ValueDecl *> result;
61616163
ClangModuleLoader *clangModuleLoader = ctx.getClangModuleLoader();
6162-
for (auto found : allResults) {
6163-
auto named = found.get<clang::NamedDecl *>();
6164-
if (dyn_cast<clang::Decl>(named->getDeclContext()) ==
6165-
recordDecl->getClangDecl()) {
6166-
// Don't import constructors on foreign reference types.
6167-
if (isa<clang::CXXConstructorDecl>(named) && isa<ClassDecl>(recordDecl))
6164+
for (auto foundEntry : directResults) {
6165+
auto found = foundEntry.get<clang::NamedDecl *>();
6166+
if (dyn_cast<clang::Decl>(found->getDeclContext()) !=
6167+
recordDecl->getClangDecl())
6168+
continue;
6169+
6170+
// Don't import constructors on foreign reference types.
6171+
if (isa<clang::CXXConstructorDecl>(found) && isa<ClassDecl>(recordDecl))
6172+
continue;
6173+
6174+
auto imported = clangModuleLoader->importDeclDirectly(found);
6175+
if (!imported)
6176+
continue;
6177+
6178+
// If this member is found due to inheritance, clone it from the base class
6179+
// by synthesizing getters and setters.
6180+
if (inheritingDecl != recordDecl) {
6181+
imported = clangModuleLoader->importBaseMemberDecl(
6182+
cast<ValueDecl>(imported), inheritingDecl);
6183+
if (!imported)
61686184
continue;
6185+
}
6186+
result.push_back(cast<ValueDecl>(imported));
6187+
}
61696188

6170-
if (auto import = clangModuleLoader->importDeclDirectly(named))
6171-
result.push_back(cast<ValueDecl>(import));
6189+
if (inheritingDecl != recordDecl) {
6190+
// For inheritied members, add members that are synthesized eagerly, such as
6191+
// subscripts. This is not necessary for non-inherited members because those
6192+
// should already be in the lookup table.
6193+
for (auto member :
6194+
cast<NominalTypeDecl>(recordDecl)->getCurrentMembersWithoutLoading()) {
6195+
auto namedMember = dyn_cast<ValueDecl>(member);
6196+
if (!namedMember || !namedMember->hasName() ||
6197+
namedMember->getName().getBaseName() != name)
6198+
continue;
6199+
6200+
if (auto imported = clangModuleLoader->importBaseMemberDecl(
6201+
namedMember, inheritingDecl))
6202+
result.push_back(cast<ValueDecl>(imported));
61726203
}
61736204
}
61746205

@@ -6202,48 +6233,19 @@ TinyPtrVector<ValueDecl *> ClangRecordMemberLookup::evaluate(
62026233
continue;
62036234

62046235
// Add Clang members that are imported lazily.
6205-
auto baseResults =
6206-
evaluateOrDefault(ctx.evaluator,
6207-
ClangRecordMemberLookup(
6208-
{cast<NominalTypeDecl>(import), name, true}),
6209-
{});
6210-
// Add members that are synthesized eagerly, such as subscripts.
6211-
for (auto member :
6212-
cast<NominalTypeDecl>(import)->getCurrentMembersWithoutLoading()) {
6213-
if (auto namedMember = dyn_cast<ValueDecl>(member)) {
6214-
if (namedMember->hasName() &&
6215-
namedMember->getName().getBaseName() == name &&
6216-
// Make sure we don't add duplicate entries, as that would
6217-
// wrongly imply that lookup is ambiguous.
6218-
!llvm::is_contained(baseResults, namedMember)) {
6219-
baseResults.push_back(namedMember);
6220-
}
6221-
}
6222-
}
6236+
auto baseResults = evaluateOrDefault(
6237+
ctx.evaluator,
6238+
ClangRecordMemberLookup(
6239+
{cast<NominalTypeDecl>(import), name, inheritingDecl}),
6240+
{});
6241+
62236242
for (auto foundInBase : baseResults) {
62246243
// Do not add duplicate entry with the same arity,
62256244
// as that would cause an ambiguous lookup.
62266245
if (foundNameArities.count(getArity(foundInBase)))
62276246
continue;
62286247

6229-
// Do not importBaseMemberDecl() if this is a recursive lookup into
6230-
// some class's superclass. importBaseMemberDecl() caches synthesized
6231-
// members, which does not work if we call it on its result, e.g.:
6232-
//
6233-
// importBaseMemberDecl(importBaseMemberDecl(foundInBase,
6234-
// recorDecl), recordDecl)
6235-
//
6236-
// Instead, we simply pass on the imported decl (foundInBase) as is,
6237-
// so that only the top-most request calls importBaseMemberDecl().
6238-
if (inherited) {
6239-
result.push_back(foundInBase);
6240-
continue;
6241-
}
6242-
6243-
if (auto newDecl = clangModuleLoader->importBaseMemberDecl(
6244-
foundInBase, recordDecl)) {
6245-
result.push_back(newDecl);
6246-
}
6248+
result.push_back(foundInBase);
62476249
}
62486250
}
62496251
}
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
#pragma once
22

3-
struct Base1 {
4-
int methodBase(void) const { return 1; }
3+
struct One {
4+
int method(void) const { return 1; }
5+
int operator[](int i) const { return 1; }
56
};
67

7-
struct IBase1 : Base1 {
8-
int methodIBase(void) const { return 11; }
8+
struct IOne : One {
9+
int methodI(void) const { return -1; }
910
};
1011

11-
struct IIBase1 : IBase1 {
12-
int methodIIBase(void) const { return 111; }
12+
struct IIOne : IOne {
13+
int methodII(void) const { return -11; }
14+
};
15+
16+
struct IIIOne : IIOne {
17+
int methodIII(void) const { return -111; }
1318
};

test/Interop/Cxx/class/inheritance/inherited-lookup-executable.swift

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,46 @@ import StdlibUnittest
66

77
var InheritedMemberTestSuite = TestSuite("Test if inherited lookup works")
88

9-
InheritedMemberTestSuite.test("IIBase1::method() resolves to grandparent") {
10-
let iibase1 = IIBase1()
11-
expectEqual(iibase1.methodBase(), 1)
12-
expectEqual(iibase1.methodIBase(), 11)
13-
expectEqual(iibase1.methodIIBase(), 111)
9+
InheritedMemberTestSuite.test("Regular methods resolve to base classes") {
10+
// No inheritance (sanity check)
11+
let one = One()
12+
expectEqual(one.method(), 1)
13+
14+
// One level of inheritance
15+
let iOne = IOne()
16+
expectEqual(iOne.method(), 1)
17+
expectEqual(iOne.methodI(), -1)
18+
19+
// Two levels of inheritance
20+
let iiOne = IIOne()
21+
expectEqual(iiOne.method(), 1)
22+
expectEqual(iiOne.methodI(), -1)
23+
expectEqual(iiOne.methodII(), -11)
24+
25+
// Three levels of inheritance
26+
let iiiOne = IIIOne()
27+
expectEqual(iiiOne.method(), 1)
28+
expectEqual(iiiOne.methodI(), -1)
29+
expectEqual(iiiOne.methodII(), -11)
30+
expectEqual(iiiOne.methodIII(), -111)
31+
}
32+
33+
InheritedMemberTestSuite.test("Eagerly imported methods resolve to base classes") {
34+
// No inheritance (sanity check)
35+
let one = One()
36+
expectEqual(one[0], 1)
37+
38+
// One level of inheritance
39+
let iOne = IOne()
40+
expectEqual(iOne[0], 1)
41+
42+
// Two levels of inheritance
43+
let iiOne = IIOne()
44+
expectEqual(iiOne[0], 1)
45+
46+
// Three levels of inheritance
47+
let iiiOne = IIIOne()
48+
expectEqual(iiiOne[0], 1)
1449
}
1550

1651
runAllTests()
Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,86 @@
11
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown -I %S/Inputs -cxx-interoperability-mode=default
22
import InheritedLookup
33

4-
extension IIBase1 {
4+
extension One {
5+
// Swift extensions of a base class should not affect its derived classes.
6+
// We later attempt to call baseExt() in derived classes, which should fail.
7+
func baseExt() -> Int32 { return 0 }
8+
9+
func ext() {
10+
let _ = baseExt()
11+
let _ = self[0]
12+
let _ = method()
13+
let _ = methodI() // expected-error {{cannot find 'methodI' in scope}}
14+
let _ = methodII() // expected-error {{cannot find 'methodII' in scope}}
15+
let _ = methodIII() // expected-error {{cannot find 'methodIII' in scope}}
16+
}
17+
}
18+
19+
func fOne(v: One) {
20+
let _ = v.baseExt()
21+
let _ = v[0]
22+
let _ = v.method()
23+
let _ = v.methodI() // expected-error {{'One' has no member 'methodI'}}
24+
let _ = v.methodII() // expected-error {{'One' has no member 'methodII'}}
25+
let _ = v.methodIII() // expected-error {{'One' has no member 'methodIII'}}
26+
}
27+
28+
extension IOne {
29+
func ext() {
30+
let _ = baseExt() // expected-error {{cannot find 'baseExt' in scope}}
31+
let _ = self[0]
32+
let _ = method()
33+
let _ = methodI()
34+
let _ = methodII() // expected-error {{cannot find 'methodII' in scope}}
35+
let _ = methodIII() // expected-error {{cannot find 'methodIII' in scope}}
36+
}
37+
}
38+
39+
func fIOne(v: IOne) {
40+
let _ = v.baseExt() // expected-error {{'IOne' has no member 'baseExt'}}
41+
let _ = v[0]
42+
let _ = v.method()
43+
let _ = v.methodI()
44+
let _ = v.methodII() // expected-error {{'IOne' has no member 'methodII'}}
45+
let _ = v.methodIII() // expected-error {{'IOne' has no member 'methodIII'}}
46+
}
47+
48+
extension IIOne {
49+
func ext() {
50+
let _ = baseExt() // expected-error {{cannot find 'baseExt' in scope}}
51+
let _ = self[0]
52+
let _ = method()
53+
let _ = methodI()
54+
let _ = methodII()
55+
let _ = methodIII() // expected-error {{cannot find 'methodIII' in scope}}
56+
}
57+
}
58+
59+
func fIIOne(v: IIOne) {
60+
let _ = v.baseExt() // expected-error {{'IIOne' has no member 'baseExt'}}
61+
let _ = v[0]
62+
let _ = v.method()
63+
let _ = v.methodI()
64+
let _ = v.methodII()
65+
let _ = v.methodIII() // expected-error {{'IIOne' has no member 'methodIII'}}
66+
}
67+
68+
extension IIIOne {
569
func ext() {
6-
// NOTE: we deliberately look up a missing member above because doing so
7-
// forces multiple ClangRecordMemberLookup requests, which should be
8-
// idempotent (though this hasn't always been the case, because bugs).
9-
missing() // expected-error {{cannot find 'missing' in scope}}
10-
11-
// For instance, a non-idempotent ClangRecordMemberLookup would cause
12-
// the following to appear ambiguous:
13-
methodBase()
14-
methodIBase()
15-
methodIIBase()
70+
let _ = baseExt() // expected-error {{cannot find 'baseExt' in scope}}
71+
let _ = self[0]
72+
let _ = method()
73+
let _ = methodI()
74+
let _ = methodII()
75+
let _ = methodIII()
1676
}
1777
}
1878

19-
func f(v: IIBase1) {
20-
v.missing() // expected-error {{'IIBase1' has no member 'missing'}}
21-
v.methodBase()
22-
v.methodIBase()
23-
v.methodIIBase()
79+
func fIIIOne(v: IIIOne) {
80+
let _ = v.baseExt() // expected-error {{'IIIOne' has no member 'baseExt'}}
81+
let _ = v[0]
82+
let _ = v.method()
83+
let _ = v.methodI()
84+
let _ = v.methodII()
85+
let _ = v.methodIII()
2486
}

0 commit comments

Comments
 (0)