mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
A request is intended to be a pure function of its inputs. That function could, in theory, fail. In practice, there were basically no requests taking advantage of this ability - the few that were using it to explicitly detect cycles can just return reasonable defaults instead of forwarding the error on up the stack. This is because cycles are checked by *the Evaluator*, and are unwound by the Evaluator. Therefore, restore the idea that the evaluate functions are themselves pure, but keep the idea that *evaluation* of those requests may fail. This model enables the best of both worlds: we not only keep the evaluator flexible enough to handle future use cases like cancellation and diagnostic invalidation, but also request-based dependencies using the values computed at the evaluation points. These aforementioned use cases would use the llvm::Expected interface and the regular evaluation-point interface respectively.
297 lines
10 KiB
C++
297 lines
10 KiB
C++
//===--- ModuleNameLookup.cpp - Name lookup within a module ---------------===//
|
||
//
|
||
// This source file is part of the Swift.org open source project
|
||
//
|
||
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
||
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||
//
|
||
// See https://swift.org/LICENSE.txt for license information
|
||
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
||
//
|
||
//===----------------------------------------------------------------------===//
|
||
|
||
#include "swift/AST/ModuleNameLookup.h"
|
||
#include "swift/AST/ASTContext.h"
|
||
#include "swift/AST/ClangModuleLoader.h"
|
||
#include "swift/AST/ImportCache.h"
|
||
#include "swift/AST/NameLookup.h"
|
||
#include "swift/AST/NameLookupRequests.h"
|
||
#include "llvm/Support/raw_ostream.h"
|
||
|
||
using namespace swift;
|
||
using namespace namelookup;
|
||
|
||
namespace {
|
||
|
||
/// Encapsulates the work done for a recursive qualified lookup into a module.
|
||
///
|
||
/// The \p LookupStrategy handles the non-recursive part of the lookup. It
|
||
/// must be a subclass of ModuleNameLookup.
|
||
template <typename LookupStrategy>
|
||
class ModuleNameLookup {
|
||
ASTContext &ctx;
|
||
const ResolutionKind resolutionKind;
|
||
const bool respectAccessControl;
|
||
|
||
LookupStrategy *getDerived() {
|
||
static_assert(std::is_base_of<ModuleNameLookup<LookupStrategy>,
|
||
LookupStrategy>::value,
|
||
"ModuleNameLookup is a CRTP class");
|
||
return static_cast<LookupStrategy *>(this);
|
||
}
|
||
|
||
public:
|
||
ModuleNameLookup(ASTContext &ctx, ResolutionKind resolutionKind)
|
||
: ctx(ctx),
|
||
resolutionKind(resolutionKind),
|
||
respectAccessControl(!ctx.isAccessControlDisabled()) {}
|
||
|
||
/// Performs a qualified lookup into the given module and, if necessary, its
|
||
/// reexports.
|
||
///
|
||
/// The results are appended to \p decls.
|
||
void lookupInModule(SmallVectorImpl<ValueDecl *> &decls,
|
||
const DeclContext *moduleOrFile,
|
||
ModuleDecl::AccessPathTy accessPath,
|
||
const DeclContext *moduleScopeContext);
|
||
};
|
||
|
||
|
||
/// Encapsulates the work done for a recursive qualified lookup into a module
|
||
/// by full name.
|
||
class LookupByName : public ModuleNameLookup<LookupByName> {
|
||
using Super = ModuleNameLookup<LookupByName>;
|
||
friend Super;
|
||
|
||
const DeclName name;
|
||
const NLKind lookupKind;
|
||
|
||
public:
|
||
LookupByName(ASTContext &ctx, ResolutionKind resolutionKind,
|
||
DeclName name, NLKind lookupKind)
|
||
: Super(ctx, resolutionKind), name(name),
|
||
lookupKind(lookupKind) {}
|
||
|
||
private:
|
||
/// Returns whether it's okay to stop recursively searching imports, given
|
||
/// that we found something non-overloadable.
|
||
static bool canReturnEarly() {
|
||
return true;
|
||
}
|
||
|
||
void doLocalLookup(ModuleDecl *module, ModuleDecl::AccessPathTy path,
|
||
SmallVectorImpl<ValueDecl *> &localDecls) {
|
||
// If this import is specific to some named decl ("import Swift.Int")
|
||
// then filter out any lookups that don't match.
|
||
if (!ModuleDecl::matchesAccessPath(path, name))
|
||
return;
|
||
module->lookupValue(name, lookupKind, localDecls);
|
||
}
|
||
};
|
||
|
||
/// Encapsulates the work done for a recursive qualified lookup into a module
|
||
/// to find all visible decls.
|
||
class LookupVisibleDecls : public ModuleNameLookup<LookupVisibleDecls> {
|
||
using Super = ModuleNameLookup<LookupVisibleDecls>;
|
||
friend Super;
|
||
|
||
const NLKind lookupKind;
|
||
|
||
public:
|
||
LookupVisibleDecls(ASTContext &ctx, ResolutionKind resolutionKind,
|
||
NLKind lookupKind)
|
||
: ModuleNameLookup(ctx, resolutionKind),
|
||
lookupKind(lookupKind) {}
|
||
|
||
private:
|
||
/// Returns whether it's okay to stop recursively searching imports, given
|
||
/// that we found something non-overloadable.
|
||
static bool canReturnEarly() {
|
||
return false;
|
||
}
|
||
|
||
void doLocalLookup(ModuleDecl *module, ModuleDecl::AccessPathTy path,
|
||
SmallVectorImpl<ValueDecl *> &localDecls) {
|
||
VectorDeclConsumer consumer(localDecls);
|
||
module->lookupVisibleDecls(path, consumer, lookupKind);
|
||
}
|
||
};
|
||
|
||
} // end anonymous namespace
|
||
|
||
template <typename LookupStrategy>
|
||
void ModuleNameLookup<LookupStrategy>::lookupInModule(
|
||
SmallVectorImpl<ValueDecl *> &decls,
|
||
const DeclContext *moduleOrFile,
|
||
ModuleDecl::AccessPathTy accessPath,
|
||
const DeclContext *moduleScopeContext) {
|
||
assert(moduleOrFile->isModuleScopeContext());
|
||
|
||
// Does the module scope have any separately-imported overlays shadowing
|
||
// the module we're looking into?
|
||
SmallVector<ModuleDecl *, 4> overlays;
|
||
moduleScopeContext->getSeparatelyImportedOverlays(
|
||
moduleOrFile->getParentModule(), overlays);
|
||
if (!overlays.empty()) {
|
||
// If so, look in each of those overlays.
|
||
for (auto overlay : overlays)
|
||
lookupInModule(decls, overlay, accessPath, moduleScopeContext);
|
||
// FIXME: This may not work gracefully if more than one of these lookups
|
||
// finds something.
|
||
return;
|
||
}
|
||
|
||
const size_t initialCount = decls.size();
|
||
size_t currentCount = decls.size();
|
||
|
||
auto updateNewDecls = [&](const DeclContext *moduleScopeContext) {
|
||
if (decls.size() == currentCount)
|
||
return;
|
||
|
||
auto newEnd = std::remove_if(
|
||
decls.begin() + currentCount, decls.end(),
|
||
[&](ValueDecl *VD) {
|
||
if (resolutionKind == ResolutionKind::TypesOnly && !isa<TypeDecl>(VD))
|
||
return true;
|
||
if (respectAccessControl && !VD->isAccessibleFrom(moduleScopeContext))
|
||
return true;
|
||
return false;
|
||
});
|
||
decls.erase(newEnd, decls.end());
|
||
|
||
currentCount = decls.size();
|
||
};
|
||
|
||
// Do the lookup into the current module.
|
||
auto *module = moduleOrFile->getParentModule();
|
||
getDerived()->doLocalLookup(module, accessPath, decls);
|
||
updateNewDecls(moduleScopeContext);
|
||
|
||
bool canReturnEarly = (initialCount != decls.size() &&
|
||
getDerived()->canReturnEarly());
|
||
if (canReturnEarly &&
|
||
resolutionKind == ResolutionKind::Overloadable) {
|
||
// If we only found top-level functions, keep looking, since we may
|
||
// find additional overloads.
|
||
if (std::all_of(decls.begin() + initialCount, decls.end(),
|
||
[](ValueDecl *VD) { return isa<FuncDecl>(VD); }))
|
||
canReturnEarly = false;
|
||
}
|
||
|
||
// If needed, search for decls in re-exported modules as well.
|
||
if (!canReturnEarly) {
|
||
auto &imports = ctx.getImportCache().getImportSet(moduleOrFile);
|
||
|
||
auto visitImport = [&](ModuleDecl::ImportedModule import,
|
||
const DeclContext *moduleScopeContext) {
|
||
if (import.first.empty())
|
||
import.first = accessPath;
|
||
else if (!accessPath.empty() &&
|
||
!ModuleDecl::isSameAccessPath(import.first, accessPath))
|
||
return;
|
||
|
||
getDerived()->doLocalLookup(import.second, import.first, decls);
|
||
updateNewDecls(moduleScopeContext);
|
||
};
|
||
|
||
// If the ClangImporter's special header import module appears in the
|
||
// import set, we must visit it first.
|
||
ModuleDecl *headerImportModule = nullptr;
|
||
if (imports.hasHeaderImportModule()) {
|
||
if (auto *loader = ctx.getClangModuleLoader()) {
|
||
headerImportModule = loader->getImportedHeaderModule();
|
||
if (headerImportModule) {
|
||
ModuleDecl::ImportedModule import({}, headerImportModule);
|
||
visitImport(import, nullptr);
|
||
}
|
||
}
|
||
}
|
||
|
||
for (auto import : imports.getTopLevelImports()) {
|
||
// A module appears in its own top-level import list; since we checked
|
||
// it already, skip it.
|
||
if (import.second == module)
|
||
continue;
|
||
|
||
// Skip the special import set module; we've already visited it.
|
||
if (import.second == headerImportModule)
|
||
continue;
|
||
|
||
visitImport(import, moduleScopeContext);
|
||
}
|
||
|
||
for (auto import : imports.getTransitiveImports()) {
|
||
// Skip the special import set module; we've already visited it.
|
||
if (import.second == headerImportModule)
|
||
continue;
|
||
|
||
visitImport(import, nullptr);
|
||
}
|
||
}
|
||
|
||
// Nothing more to do if we don't have ambiguous results.
|
||
if (decls.size() - initialCount <= 1)
|
||
return;
|
||
|
||
// Remove duplicated declarations, which can happen when the same module is
|
||
// imported with multiple access paths.
|
||
llvm::SmallPtrSet<ValueDecl *, 4> knownDecls;
|
||
decls.erase(std::remove_if(decls.begin() + initialCount, decls.end(),
|
||
[&](ValueDecl *d) -> bool {
|
||
return !knownDecls.insert(d).second;
|
||
}),
|
||
decls.end());
|
||
}
|
||
|
||
QualifiedLookupResult
|
||
LookupInModuleRequest::evaluate(
|
||
Evaluator &evaluator, const DeclContext *moduleOrFile, DeclName name,
|
||
NLKind lookupKind, ResolutionKind resolutionKind,
|
||
const DeclContext *moduleScopeContext) const {
|
||
assert(moduleScopeContext->isModuleScopeContext());
|
||
|
||
QualifiedLookupResult decls;
|
||
LookupByName lookup(moduleOrFile->getASTContext(), resolutionKind,
|
||
name, lookupKind);
|
||
lookup.lookupInModule(decls, moduleOrFile, {}, moduleScopeContext);
|
||
return decls;
|
||
}
|
||
|
||
void namelookup::lookupInModule(const DeclContext *moduleOrFile,
|
||
DeclName name,
|
||
SmallVectorImpl<ValueDecl *> &decls,
|
||
NLKind lookupKind,
|
||
ResolutionKind resolutionKind,
|
||
const DeclContext *moduleScopeContext) {
|
||
auto &ctx = moduleOrFile->getASTContext();
|
||
LookupInModuleRequest req(moduleOrFile, name, lookupKind, resolutionKind,
|
||
moduleScopeContext);
|
||
auto results = evaluateOrDefault(ctx.evaluator, req, {});
|
||
decls.append(results.begin(), results.end());
|
||
}
|
||
|
||
void namelookup::lookupVisibleDeclsInModule(
|
||
const DeclContext *moduleOrFile,
|
||
ModuleDecl::AccessPathTy accessPath,
|
||
SmallVectorImpl<ValueDecl *> &decls,
|
||
NLKind lookupKind,
|
||
ResolutionKind resolutionKind,
|
||
const DeclContext *moduleScopeContext) {
|
||
assert(moduleScopeContext->isModuleScopeContext());
|
||
auto &ctx = moduleOrFile->getASTContext();
|
||
LookupVisibleDecls lookup(ctx, resolutionKind, lookupKind);
|
||
lookup.lookupInModule(decls, moduleOrFile, accessPath, moduleScopeContext);
|
||
}
|
||
|
||
void namelookup::simple_display(llvm::raw_ostream &out, ResolutionKind kind) {
|
||
switch (kind) {
|
||
case ResolutionKind::Overloadable:
|
||
out << "Overloadable";
|
||
return;
|
||
case ResolutionKind::TypesOnly:
|
||
out << "TypesOnly";
|
||
return;
|
||
}
|
||
llvm_unreachable("Unhandled case in switch");
|
||
}
|