Skip to content

Commit 52479ca

Browse files
committed
AST: Fix source break with new shadowing rules
My recent refactoring of top-level lookup replaced the old shadowing done as part of top-level lookup with two separate rules: - If all paths from the current source file to a module 'B' go through a module 'A', then declarations in 'A' shadow declarations in 'B'. - If a declaration in module 'A' was found via a scoped import and a declaration with the same name in module 'B' was found via an unscoped import, prefer the declaration from module 'A'. However this caused a source break when you have a scenario like the following: - A source file imports 'A', 'B', and 'B.Foo'. - 'A' re-exports 'B'. - Both 'A' and 'B' define a type named 'Foo'. The problem is that the scoped import 'B.Foo' can actually find both 'A.Foo' and 'B.Foo', since 'B' re-exports 'A'. Furthermore, since the source file explicitly imports 'A', 'B' does not shadow 'A' in the import graph. As a result neither shadowing rule would eliminate the ambiguity. The new rule combines the scoped import check and the shadowing check by considering all access paths to 'A' that are not shadowed by 'B'. Using this rule, 'A.Foo' is only seen via the access path 'A', whereas 'B.Foo' is seen under both 'B' and 'B.Foo'. Since 'B.Foo' is seen via a scoped import and 'A.Foo' is only seen via an unscoped import, we can conclude that 'B.Foo' shadows 'A.Foo'. Fixes <rdar://problem/55205050>.
1 parent cd8fddd commit 52479ca

File tree

4 files changed

+58
-49
lines changed

4 files changed

+58
-49
lines changed

include/swift/AST/ImportCache.h

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -140,52 +140,13 @@ class alignas(ModuleDecl::ImportedModule) ImportCache {
140140
return !getAllVisibleAccessPaths(mod, dc).empty();
141141
}
142142

143-
/// Determines if 'mod' is visible from 'dc' as a result of a scoped import.
144-
/// Note that if 'mod' was not imported from 'dc' at all, this also returns
145-
/// false.
146-
bool isScopedImport(const ModuleDecl *mod, DeclBaseName name,
147-
const DeclContext *dc) {
148-
auto accessPaths = getAllVisibleAccessPaths(mod, dc);
149-
for (auto accessPath : accessPaths) {
150-
if (accessPath.empty())
151-
continue;
152-
if (ModuleDecl::matchesAccessPath(accessPath, name))
153-
return true;
154-
}
155-
156-
return false;
157-
}
158-
159143
/// Returns all access paths in 'mod' that are visible from 'dc' if we
160144
/// subtract imports of 'other'.
161145
ArrayRef<ModuleDecl::AccessPathTy>
162146
getAllAccessPathsNotShadowedBy(const ModuleDecl *mod,
163147
const ModuleDecl *other,
164148
const DeclContext *dc);
165149

166-
/// Returns 'true' if a declaration named 'name' defined in 'other' shadows
167-
/// defined in 'mod', because no access paths can find 'name' in 'mod' from
168-
/// 'dc' if we ignore imports of 'other'.
169-
bool isShadowedBy(const ModuleDecl *mod,
170-
const ModuleDecl *other,
171-
DeclBaseName name,
172-
const DeclContext *dc) {
173-
auto accessPaths = getAllAccessPathsNotShadowedBy(mod, other, dc);
174-
return llvm::none_of(accessPaths,
175-
[&](ModuleDecl::AccessPathTy accessPath) {
176-
return ModuleDecl::matchesAccessPath(accessPath, name);
177-
});
178-
}
179-
180-
/// Qualified lookup into types uses a slightly different check that does not
181-
/// take access paths into account.
182-
bool isShadowedBy(const ModuleDecl *mod,
183-
const ModuleDecl *other,
184-
const DeclContext *dc) {
185-
auto accessPaths = getAllAccessPathsNotShadowedBy(mod, other, dc);
186-
return accessPaths.empty();
187-
}
188-
189150
/// This is a hack to cope with main file parsing and REPL parsing, where
190151
/// we can add ImportDecls after name binding.
191152
void clear() {

lib/AST/NameLookup.cpp

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,27 @@ static void recordShadowedDeclsAfterSignatureMatch(
193193
auto firstDecl = decls[firstIdx];
194194
auto firstModule = firstDecl->getModuleContext();
195195
auto name = firstDecl->getBaseName();
196+
197+
auto isShadowed = [&](ArrayRef<ModuleDecl::AccessPathTy> paths) {
198+
for (auto path : paths) {
199+
if (ModuleDecl::matchesAccessPath(path, name))
200+
return false;
201+
}
202+
203+
return true;
204+
};
205+
206+
auto isScopedImport = [&](ArrayRef<ModuleDecl::AccessPathTy> paths) {
207+
for (auto path : paths) {
208+
if (path.empty())
209+
continue;
210+
if (ModuleDecl::matchesAccessPath(path, name))
211+
return true;
212+
}
213+
214+
return false;
215+
};
216+
196217
for (unsigned secondIdx : range(firstIdx + 1, decls.size())) {
197218
// Determine whether one module takes precedence over another.
198219
auto secondDecl = decls[secondIdx];
@@ -206,22 +227,28 @@ static void recordShadowedDeclsAfterSignatureMatch(
206227
if (firstModule != secondModule &&
207228
firstDecl->getDeclContext()->isModuleScopeContext() &&
208229
secondDecl->getDeclContext()->isModuleScopeContext()) {
209-
// First, scoped imports shadow unscoped imports.
210-
bool firstScoped = imports.isScopedImport(firstModule, name, dc);
211-
bool secondScoped = imports.isScopedImport(secondModule, name, dc);
212-
if (!firstScoped && secondScoped) {
230+
auto firstPaths = imports.getAllAccessPathsNotShadowedBy(
231+
firstModule, secondModule, dc);
232+
auto secondPaths = imports.getAllAccessPathsNotShadowedBy(
233+
secondModule, firstModule, dc);
234+
235+
// Check if one module shadows the other.
236+
if (isShadowed(firstPaths)) {
213237
shadowed.insert(firstDecl);
214238
break;
215-
} else if (firstScoped && !secondScoped) {
239+
} else if (isShadowed(secondPaths)) {
216240
shadowed.insert(secondDecl);
217241
continue;
218242
}
219243

220-
// Now check if one module shadows the other.
221-
if (imports.isShadowedBy(firstModule, secondModule, name, dc)) {
244+
// We might be in a situation where neither module shadows the
245+
// other, but one declaration is visible via a scoped import.
246+
bool firstScoped = isScopedImport(firstPaths);
247+
bool secondScoped = isScopedImport(secondPaths);
248+
if (!firstScoped && secondScoped) {
222249
shadowed.insert(firstDecl);
223250
break;
224-
} else if (imports.isShadowedBy(secondModule, firstModule, name, dc)) {
251+
} else if (firstScoped && !secondScoped) {
225252
shadowed.insert(secondDecl);
226253
continue;
227254
}
@@ -278,10 +305,16 @@ static void recordShadowedDeclsAfterSignatureMatch(
278305
if (firstModule != secondModule &&
279306
!firstDecl->getDeclContext()->isModuleScopeContext() &&
280307
!secondDecl->getDeclContext()->isModuleScopeContext()) {
281-
if (imports.isShadowedBy(firstModule, secondModule, dc)) {
308+
auto firstPaths = imports.getAllAccessPathsNotShadowedBy(
309+
firstModule, secondModule, dc);
310+
auto secondPaths = imports.getAllAccessPathsNotShadowedBy(
311+
secondModule, firstModule, dc);
312+
313+
// Check if one module shadows the other.
314+
if (isShadowed(firstPaths)) {
282315
shadowed.insert(firstDecl);
283316
break;
284-
} else if (imports.isShadowedBy(secondModule, firstModule, dc)) {
317+
} else if (isShadowed(secondPaths)) {
285318
shadowed.insert(secondDecl);
286319
continue;
287320
}

test/NameBinding/Inputs/sdf.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@_exported import asdf
2+
3+
public struct S { public init(x: Int) {} }
4+
public struct D { public init(x: Int) {} }
5+
public struct F { public init(x: Int) {} }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/asdf.swift
3+
// RUN: %target-swift-frontend -emit-module -o %t %S/Inputs/sdf.swift -I %t
4+
// RUN: %target-swift-frontend -typecheck %s -I %t -verify
5+
6+
import asdf
7+
import sdf
8+
import struct sdf.S
9+
10+
var uS: S = sdf.S(x: 123)

0 commit comments

Comments
 (0)