Implement proper shadowing rules for qualified lookup.

This required a general reworking of the algorithm for qualified name lookup.
Originally, qualified lookup only applied to a module -- not to its exports.
This meant that "Cocoa.NSWindow" would fail, so that was changed to grovel
through all exported modules looking for decls if the top-level module didn't
provide any.

Now, we actually do a breadth-based search, stopping at each level if decls
are provided for a given name. We also now prefer scoped imports to unscoped
imports, so "import abcde" and "import struct asdf.D" will result in a
(qualified) reference to 'D' being unambiguous.

Not working yet:
 - Shadowing for unqualified lookup.
 - Shadowing by types, so that overloads from this module can merge with
   overloads from its exports.

Swift SVN r7014
This commit is contained in:
Jordan Rose
2013-08-07 22:56:49 +00:00
parent 42a109674d
commit 027565a9a9
4 changed files with 83 additions and 43 deletions

View File

@@ -562,12 +562,6 @@ UnqualifiedLookup::UnqualifiedLookup(Identifier Name, DeclContext *DC,
return true;
}
));
auto last = std::unique(Results.begin(), Results.end(),
[](const Result &LHS, const Result &RHS) {
return LHS.getNamedModule() == RHS.getNamedModule();
});
Results.erase(last, Results.end());
}
Optional<UnqualifiedLookup>
@@ -735,6 +729,76 @@ ArrayRef<ValueDecl *> NominalTypeDecl::lookupDirect(Identifier name) {
return { known->second.begin(), known->second.size() };
}
/// A cache used by lookupInModule().
class ModuleLookupCache {
public:
llvm::SmallDenseMap<Module *, TinyPtrVector<ValueDecl *>, 32> Map;
bool SearchedClangModule = false;
};
/// Performs a qualified lookup into the given module and, if necessary, its
/// reexports, observing proper shadowing rules.
static void lookupInModule(Module *module, Module::AccessPathTy accessPath,
Identifier name, SmallVectorImpl<ValueDecl *> &decls,
ModuleLookupCache &cache) {
if (accessPath.empty()) {
if (module->getContextKind() == DeclContextKind::ClangModule) {
if (cache.SearchedClangModule)
return;
cache.SearchedClangModule = true;
}
auto iter = cache.Map.find(module);
if (iter != cache.Map.end()) {
decls.append(iter->second.begin(), iter->second.end());
return;
}
}
size_t count = decls.size();
module->lookupValue(accessPath, name, NLKind::QualifiedLookup, decls);
if (decls.size() == count) {
SmallVector<Module::ImportedModule, 8> reexports;
module->getReexportedModules(reexports);
// Prefer scoped imports (import func swift.max) to whole-module imports.
SmallVector<ValueDecl *, 8> unscopedValues;
for (auto next : reexports) {
// Filter any whole-module imports, and skip specific-decl imports if the
// import path doesn't match exactly.
Module::AccessPathTy combinedAccessPath;
if (accessPath.empty()) {
combinedAccessPath = next.first;
} else if (!next.first.empty() &&
!Module::isSameAccessPath(next.first, accessPath)) {
// If we ever allow importing non-top-level decls, it's possible the
// rule above isn't what we want.
assert(next.first.size() == 1 && "import of non-top-level decl");
continue;
} else {
combinedAccessPath = accessPath;
}
lookupInModule(next.second, combinedAccessPath, name,
next.first.empty() ? unscopedValues : decls,
cache);
}
// If there were no scoped imports, add the unscoped results.
if (decls.size() == count)
decls.append(unscopedValues.begin(), unscopedValues.end());
}
if (accessPath.empty()) {
auto &cachedValues = cache.Map[module];
cachedValues.insert(cachedValues.end(),
llvm::makeArrayRef(decls).slice(count).begin(),
llvm::makeArrayRef(decls).end());
}
}
bool Module::lookupQualified(Type type,
Identifier name,
unsigned options,
@@ -755,38 +819,8 @@ bool Module::lookupQualified(Type type,
// Look for module references.
if (auto moduleTy = type->getAs<ModuleType>()) {
Module *module = moduleTy->getModule();
module->lookupValue(Module::AccessPathTy(), name,
NLKind::QualifiedLookup, decls);
// Prefer decls from the module itself, rather than imported modules.
if (!decls.empty())
return true;
// Track whether we've already searched the Clang modules.
// FIXME: This is a weird hack. We either need to filter within the
// Clang module importer, or we need to change how this works.
bool searchedClangModule =
module->getContextKind() == DeclContextKind::ClangModule;
module->forAllVisibleModules(Nothing,
makeStackLambda(
[&](const Module::ImportedModule &ImpEntry) {
// FIXME: Only searching Clang modules once.
if (ImpEntry.first.empty() &&
ImpEntry.second->getContextKind() == DeclContextKind::ClangModule) {
if (searchedClangModule)
return;
searchedClangModule = true;
}
// FIXME: Is the re-exported lookup really unqualified? We do want it
// to ignore the Builtin module, but no one should be re-exporting that.
ImpEntry.second->lookupValue(ImpEntry.first, name,
NLKind::UnqualifiedLookup, decls);
}
));
ModuleLookupCache cache;
lookupInModule(moduleTy->getModule(), {}, name, decls, cache);
return !decls.empty();
}