//===--- 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/LazyResolver.h" #include "swift/AST/NameLookup.h" #include "llvm/Support/raw_ostream.h" using namespace swift; using namespace namelookup; /// Returns true if this particular ValueDecl is overloadable. static bool isOverloadable(const ValueDecl *VD) { // FIXME: This is very suspect; it doesn't really match how the rest of the // compiler works anymore. (With extensions imported from different modules, // properties can have the same base names as methods.) How do we want // cross-module shadowing to work? return isa(VD) || isa(VD) || isa(VD); } namespace { /// Encapsulates the work done for a recursive qualified lookup into a module. /// /// The \p LookupStrategy handles the non-recursive part of the lookup, as well /// as how to combine results from across modules (e.g. handling shadowing). It /// must be a subclass of ModuleNameLookup. template class ModuleNameLookup { /// An alias for the LookupStrategy subclass's nested OverloadSetTy whose /// resolution can be delayed until after the subclass type is /// considered "complete". template using OverloadSetTy = typename std::enable_if::type::OverloadSetTy; /// The usable results of a lookup in a particular module may differ based on /// where the lookup is happening. using ModuleLookupCacheKey = std::pair; using ModuleLookupCache = llvm::SmallDenseMap, 32>; ModuleLookupCache cache; const ResolutionKind resolutionKind; const bool respectAccessControl; LookupStrategy *getDerived() { static_assert(std::is_base_of, LookupStrategy>::value, "ModuleNameLookup is a CRTP class"); return static_cast(this); } /// After finding decls by name lookup, filter based on the given /// resolution kind and existing overload set and add them to \p results. /// /// \p overloads is updated based on the new declarations. /// /// \returns true if lookup is (locally) complete and does not need to recurse /// further. template bool recordImportDecls( SmallVectorImpl &results, ArrayRef newDecls, OverloadSetTy &overloads); /// Given a list of imports and an access path to limit by, perform a lookup /// into each of them and record the results in \p decls, filtering based on /// the given resolution kind and existing overload set. template void collectLookupResultsFromImports( SmallVectorImpl &decls, ArrayRef imports, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, OverloadSetTy &overloads); /// Performs a qualified lookup into the given module and, if necessary, its /// reexports, observing proper shadowing rules. The lookup into \p module /// itself will not check or update the lookup cache. /// /// The results are appended to \p decls. /// /// \returns The slice of \p decls that includes the newly-found /// declarations. /// /// \see lookupInModule ArrayRef lookupInModuleUncached( SmallVectorImpl &decls, ModuleDecl *module, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, ArrayRef extraImports); public: ModuleNameLookup(ASTContext &ctx, ResolutionKind resolutionKind) : resolutionKind(resolutionKind), respectAccessControl(!ctx.isAccessControlDisabled()) {} /// Performs a qualified lookup into the given module and, if necessary, its /// reexports, observing proper shadowing rules. /// /// The results are appended to \p decls. void lookupInModule(SmallVectorImpl &decls, ModuleDecl *module, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, ArrayRef extraImports = {}); }; /// Encapsulates the work done for a recursive qualified lookup into a module /// by full name. class LookupByName : public ModuleNameLookup { using Super = ModuleNameLookup; friend Super; friend class LookupVisibleDecls; const DeclName name; const NLKind lookupKind; public: LookupByName(ASTContext &ctx, ResolutionKind resolutionKind, DeclName name, NLKind lookupKind) : Super(ctx, resolutionKind), name(name), lookupKind(lookupKind) {} private: class SortCanType { public: bool operator()(CanType lhs, CanType rhs) const { return std::less()(lhs.getPointer(), rhs.getPointer()); } }; using OverloadSetTy = llvm::SmallSet; /// Does \p VD conflict with the \p overloads we've already seen? static bool isValidOverload(const OverloadSetTy &overloads, const ValueDecl *VD) { if (!isOverloadable(VD)) return overloads.empty(); return !overloads.count(VD->getInterfaceType()->getCanonicalType()); } /// Updates \p overloads with the types of the given decls. /// /// \returns true if all of the given decls are overloadable, false if not. static bool updateOverloadSet(OverloadSetTy &overloads, ArrayRef decls) { for (auto result : decls) { if (!isOverloadable(result)) return false; if (!result->hasInterfaceType()) continue; // FIXME: This relies on the interface type including argument labels. // FIXME: ...and it doesn't handle different generic requirements. overloads.insert(result->getInterfaceType()->getCanonicalType()); } return true; } /// 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 &localDecls) { module->lookupValue(path, name, lookupKind, localDecls); } }; /// Encapsulates the work done for a recursive qualified lookup into a module /// to find all visible decls. class LookupVisibleDecls : public ModuleNameLookup { using Super = ModuleNameLookup; friend Super; const NLKind lookupKind; public: LookupVisibleDecls(ASTContext &ctx, ResolutionKind resolutionKind, NLKind lookupKind) : ModuleNameLookup(ctx, resolutionKind), lookupKind(lookupKind) {} private: using OverloadSetEntry = std::pair; using OverloadSetTy = llvm::DenseMap; static_assert(ResolutionKind() == ResolutionKind::Overloadable, "Entries in NamedCanTypeSet should be overloadable initially"); /// Does \p VD conflict with the \p overloads we've already seen? static bool isValidOverload(OverloadSetTy &overloads, const ValueDecl *VD) { // Note: 'overloads' is not const because it's cheaper to create the empty // set value under this name once and check it repeatedly. const OverloadSetEntry &entry = overloads[VD->getBaseName()]; if (entry.first != ResolutionKind::Overloadable) return false; return LookupByName::isValidOverload(entry.second, VD); } /// Updates \p overloads with the types of the given decls. /// /// \returns true, since there can always be more overloadable decls. static bool updateOverloadSet(OverloadSetTy &overloads, ArrayRef decls) { for (auto result : decls) { OverloadSetEntry &entry = overloads[result->getBaseName()]; if (!isOverloadable(result)) { entry.first = ResolutionKind::Exact; entry.second.clear(); continue; } if (!result->hasInterfaceType()) continue; // FIXME: This relies on the interface type including argument labels. // FIXME: ...and it doesn't handle different generic requirements. entry.second.insert(result->getInterfaceType()->getCanonicalType()); } return true; } /// 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 &localDecls) { VectorDeclConsumer consumer(localDecls); module->lookupVisibleDecls(path, consumer, lookupKind); } }; } // end anonymous namespace template template bool ModuleNameLookup::recordImportDecls( SmallVectorImpl &results, ArrayRef newDecls, OverloadSetTy &overloads) { static_assert( std::is_same::value, "Template params should be inferred."); const size_t originalSize = results.size(); switch (resolutionKind) { case ResolutionKind::Overloadable: { // Add new decls if they provide a new overload. Note that the new decls // may be ambiguous with respect to each other, just not any decls already // in the overload set. llvm::copy_if(newDecls, std::back_inserter(results), [&](ValueDecl *result) -> bool { if (!result->hasInterfaceType()) { if (auto *typeResolver = result->getASTContext().getLazyResolver()) { typeResolver->resolveDeclSignature(result); if (result->isInvalid()) return true; } else { return true; } } return getDerived()->isValidOverload(overloads, result); }); // Update the overload set. bool stillOverloadable = getDerived()->updateOverloadSet(overloads, newDecls); if (stillOverloadable) return false; break; } case ResolutionKind::Exact: // Add all decls. If they're ambiguous, they're ambiguous; if we got to this // point, the caller hasn't found anything that shadows these decls. results.append(newDecls.begin(), newDecls.end()); break; case ResolutionKind::TypesOnly: // Add type decls only. If they're ambiguous, they're ambiguous; if we got // to this point, the caller hasn't found anything that shadows these decls. llvm::copy_if(newDecls, std::back_inserter(results), [](const ValueDecl *VD) { return isa(VD); }); break; } return results.size() != originalSize && getDerived()->canReturnEarly(); } template template void ModuleNameLookup::collectLookupResultsFromImports( SmallVectorImpl &decls, ArrayRef reexports, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, OverloadSetTy &overloads) { static_assert( std::is_same::value, "Template params should be inferred."); // Prefer scoped imports (those importing a specific name from a module, like // `import func Swift.max`) to whole-module imports. SmallVector unscopedValues; SmallVector scopedValues; for (auto next : reexports) { // Filter any whole-module imports, and skip specific-decl imports if the // import path doesn't match exactly. ModuleDecl::AccessPathTy combinedAccessPath; if (accessPath.empty()) { combinedAccessPath = next.first; } else if (!next.first.empty() && !ModuleDecl::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; } auto &resultSet = next.first.empty() ? unscopedValues : scopedValues; lookupInModule(resultSet, next.second, combinedAccessPath, moduleScopeContext); } // Add the results from scoped imports, then the results from unscoped // imports if needed. const bool canReturnEarly = recordImportDecls(decls, scopedValues, overloads); if (!canReturnEarly) (void)recordImportDecls(decls, unscopedValues, overloads); } template ArrayRef ModuleNameLookup::lookupInModuleUncached( SmallVectorImpl &decls, ModuleDecl *module, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, ArrayRef extraImports) { // Do the lookup. SmallVector localDecls; getDerived()->doLocalLookup(module, accessPath, localDecls); if (respectAccessControl) { llvm::erase_if(localDecls, [=](ValueDecl *VD) { return !VD->isAccessibleFrom(moduleScopeContext); }); } // Record the decls by overload signature. const size_t initialCount = decls.size(); typename LookupStrategy::OverloadSetTy overloads; const bool canReturnEarly = recordImportDecls(decls, localDecls, overloads); // If needed, search for decls in re-exported modules as well. if (!canReturnEarly) { SmallVector reexports; module->getImportedModulesForLookup(reexports); assert(llvm::none_of(reexports, [module](ModuleDecl::ImportedModule import) -> bool { return import.second == nullptr || import.second == module; })); reexports.append(extraImports.begin(), extraImports.end()); // Special treatment based on the use site only applies to immediate // imports of the top-level module. // FIXME: It ought to apply to anything re-exported by those immediate // imports as well, since re-exports are supposed to be treated like part of // the module they're re-exported from. const DeclContext *moduleScopeContextForReexports = moduleScopeContext; if (moduleScopeContext && moduleScopeContext->getParentModule() != module) moduleScopeContextForReexports = nullptr; collectLookupResultsFromImports(decls, reexports, accessPath, moduleScopeContextForReexports, overloads); } // Remove duplicated declarations, which can happen when the same module is // imported indirectly through two intermediate modules. llvm::SmallPtrSet knownDecls; decls.erase(std::remove_if(decls.begin() + initialCount, decls.end(), [&](ValueDecl *d) -> bool { return !knownDecls.insert(d).second; }), decls.end()); return llvm::makeArrayRef(decls).slice(initialCount); } template void ModuleNameLookup::lookupInModule( SmallVectorImpl &decls, ModuleDecl *module, ModuleDecl::AccessPathTy accessPath, const DeclContext *moduleScopeContext, ArrayRef extraImports) { assert(module); assert(llvm::none_of(extraImports, [](ModuleDecl::ImportedModule import) -> bool { return import.second == nullptr; })); const ModuleDecl::ImportedModule import{accessPath, module}; const ModuleLookupCacheKey cacheKey{import, moduleScopeContext}; { // Explicitly scope the cache lookup here, because the iterator won't be // valid after we do all the work to populate the cache. const auto iter = cache.find(cacheKey); if (iter != cache.end()) { decls.append(iter->second.begin(), iter->second.end()); return; } } const ArrayRef lookupResults = lookupInModuleUncached(decls, module, accessPath, moduleScopeContext, extraImports); cache.try_emplace(cacheKey, lookupResults); } void namelookup::lookupInModule(const DeclContext *moduleOrFile, DeclName name, SmallVectorImpl &decls, NLKind lookupKind, ResolutionKind resolutionKind, const DeclContext *moduleScopeContext) { assert(moduleScopeContext->isModuleScopeContext()); auto *startModule = moduleOrFile->getParentModule(); auto &ctx = startModule->getASTContext(); auto *stats = ctx.Stats; if (stats) stats->getFrontendCounters().NumLookupInModule++; FrontendStatsTracer tracer(stats, "lookup-in-module"); // Add private imports to the extra search list. SmallVector extraImports; if (auto *file = dyn_cast(moduleOrFile)) { ModuleDecl::ImportFilter importFilter; importFilter |= ModuleDecl::ImportFilterKind::Private; importFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly; file->getImportedModules(extraImports, importFilter); } LookupByName lookup(ctx, resolutionKind, name, lookupKind); lookup.lookupInModule(decls, startModule, {}, moduleScopeContext, extraImports); } void namelookup::lookupVisibleDeclsInModule( const DeclContext *moduleOrFile, ModuleDecl::AccessPathTy accessPath, SmallVectorImpl &decls, NLKind lookupKind, ResolutionKind resolutionKind, const DeclContext *moduleScopeContext) { assert(moduleScopeContext->isModuleScopeContext()); auto *startModule = moduleOrFile->getParentModule(); auto &ctx = startModule->getASTContext(); // Add private imports to the extra search list. SmallVector extraImports; if (auto *file = dyn_cast(moduleOrFile)) { ModuleDecl::ImportFilter importFilter; importFilter |= ModuleDecl::ImportFilterKind::Private; importFilter |= ModuleDecl::ImportFilterKind::ImplementationOnly; file->getImportedModules(extraImports, importFilter); } LookupVisibleDecls lookup(ctx, resolutionKind, lookupKind); lookup.lookupInModule(decls, startModule, accessPath, moduleScopeContext, extraImports); }