Fix precedencegroup and operator decl lookup

Re-implement operator and precedencegroup decl
lookup to use `namelookup::getAllImports` and
existing decl shadowing logic. This allows us to
find operator decls through `@_exported` imports,
prefer operator decls defined in the same module
over imported decls, and fixes a couple of other
subtle issues.

Because this new implementation is technically
source breaking, as we can find multiple results
where we used to only find one result, it's placed
behind the new Frontend flag
`-enable-new-operator-lookup` (with the aim of
enabling it by default when we get a new language
mode).

However the new logic will always be used if the
result is unambiguous. This means that e.g
`@_exported` operators will be instantly available
as long as there's only one candidate. If multiple
candidates are found, we fall back to the old
logic.

Resolves SR-12132.
Resolves rdar://59198796.
This commit is contained in:
Hamish Knight
2020-05-18 14:33:43 -07:00
parent 912baa8a8b
commit cc062ee2bb
24 changed files with 760 additions and 451 deletions

View File

@@ -1051,72 +1051,6 @@ LookupConformanceInModuleRequest::evaluate(
return ProtocolConformanceRef(conformance);
}
namespace {
template <typename T>
struct OperatorLookup {
// Don't fold this into the static_assert: this would trigger an MSVC bug
// that causes the assertion to fail.
static constexpr T* ptr = static_cast<T*>(nullptr);
static_assert(ptr, "Only usable with operators");
};
template <>
struct OperatorLookup<PrefixOperatorDecl> {
static PrefixOperatorDecl *lookup(Evaluator &eval,
const OperatorLookupDescriptor &desc) {
// We can return the first prefix operator. All prefix operators of the
// same name are equivalent.
DirectOperatorLookupRequest req{desc, OperatorFixity::Prefix};
auto results = evaluateOrDefault(eval, req, {});
return results.empty() ? nullptr : cast<PrefixOperatorDecl>(results[0]);
}
};
template <>
struct OperatorLookup<InfixOperatorDecl> {
static InfixOperatorDecl *lookup(Evaluator &eval,
const OperatorLookupDescriptor &desc) {
// Return the first result if it exists.
DirectOperatorLookupRequest req{desc, OperatorFixity::Infix};
auto results = evaluateOrDefault(eval, req, {});
return results.empty() ? nullptr : cast<InfixOperatorDecl>(results[0]);
}
};
template <>
struct OperatorLookup<PostfixOperatorDecl> {
static PostfixOperatorDecl *lookup(Evaluator &eval,
const OperatorLookupDescriptor &desc) {
// We can return the first postfix operator. All postfix operators of the
// same name are equivalent.
DirectOperatorLookupRequest req{desc, OperatorFixity::Postfix};
auto results = evaluateOrDefault(eval, req, {});
return results.empty() ? nullptr : cast<PostfixOperatorDecl>(results[0]);
}
};
template <>
struct OperatorLookup<PrecedenceGroupDecl> {
static PrecedenceGroupDecl *lookup(Evaluator &eval,
const OperatorLookupDescriptor &desc) {
// Return the first result if it exists.
auto results =
evaluateOrDefault(eval, DirectPrecedenceGroupLookupRequest{desc}, {});
return results.empty() ? nullptr : results[0];
}
};
} // end anonymous namespace
/// A helper class to sneak around C++ access control rules.
class SourceFile::Impl {
public:
/// Only intended for use by lookupOperatorDeclForName.
static ArrayRef<SourceFile::ImportedModuleDesc>
getImportsForSourceFile(const SourceFile &SF) {
return *SF.Imports;
}
};
struct SourceFile::SourceFileSyntaxInfo {
const bool Enable;
/// The root of the syntax tree representing the source file.
@@ -1137,180 +1071,6 @@ void SourceFile::setSyntaxRoot(syntax::SourceFileSyntax &&Root) {
SyntaxInfo->SyntaxRoot.emplace(Root);
}
template<typename OP_DECL>
static Optional<OP_DECL *>
lookupOperatorDeclForName(ModuleDecl *M, SourceLoc Loc, Identifier Name,
bool isCascading);
template<typename OP_DECL>
using ImportedOperatorsMap = llvm::SmallDenseMap<OP_DECL*, bool, 16>;
template<typename OP_DECL>
static typename ImportedOperatorsMap<OP_DECL>::iterator
checkOperatorConflicts(const SourceFile &SF, SourceLoc loc,
ImportedOperatorsMap<OP_DECL> &importedOperators) {
// Check for conflicts.
auto i = importedOperators.begin(), end = importedOperators.end();
auto start = i;
for (++i; i != end; ++i) {
if (i->first->conflictsWith(start->first)) {
if (loc.isValid()) {
ASTContext &C = SF.getASTContext();
C.Diags.diagnose(loc, diag::ambiguous_operator_decls);
start->first->diagnose(diag::found_this_operator_decl);
i->first->diagnose(diag::found_this_operator_decl);
}
return end;
}
}
return start;
}
template<>
typename ImportedOperatorsMap<PrecedenceGroupDecl>::iterator
checkOperatorConflicts(const SourceFile &SF, SourceLoc loc,
ImportedOperatorsMap<PrecedenceGroupDecl> &importedGroups) {
if (importedGroups.size() == 1)
return importedGroups.begin();
// Any sort of ambiguity is an error.
if (loc.isValid()) {
ASTContext &C = SF.getASTContext();
C.Diags.diagnose(loc, diag::ambiguous_precedence_groups);
for (auto &entry : importedGroups) {
entry.first->diagnose(diag::found_this_precedence_group);
}
}
return importedGroups.end();
}
// Returns None on error, Optional(nullptr) if no operator decl found, or
// Optional(decl) if decl was found.
template <typename OP_DECL>
static Optional<OP_DECL *>
lookupOperatorDeclForName(const FileUnit &File, SourceLoc Loc,
Identifier Name, bool includePrivate,
bool isCascading) {
auto &eval = File.getASTContext().evaluator;
auto desc = OperatorLookupDescriptor::forFile(const_cast<FileUnit *>(&File),
Name, isCascading,
/*diagLoc*/ SourceLoc());
switch (File.getKind()) {
case FileUnitKind::Builtin:
// The Builtin module declares no operators.
return nullptr;
case FileUnitKind::Synthesized:
// Synthesized files currently declare no operators.
return nullptr;
case FileUnitKind::Source:
break;
case FileUnitKind::SerializedAST:
case FileUnitKind::ClangModule:
case FileUnitKind::DWARFModule:
return OperatorLookup<OP_DECL>::lookup(eval, desc);
}
auto &SF = cast<SourceFile>(File);
assert(SF.ASTStage >= SourceFile::ImportsResolved);
// Check if the decl exists on the file.
if (auto *op = OperatorLookup<OP_DECL>::lookup(eval, desc))
return op;
// Look for imported operator decls.
// Record whether they come from re-exported modules.
// FIXME: We ought to prefer operators elsewhere in this module before we
// check imports.
auto ownModule = SF.getParentModule();
ImportedOperatorsMap<OP_DECL> importedOperators;
for (auto &imported : SourceFile::Impl::getImportsForSourceFile(SF)) {
// Protect against source files that contrive to import their own modules.
if (imported.module.importedModule == ownModule)
continue;
bool isExported =
imported.importOptions.contains(SourceFile::ImportFlags::Exported);
if (!includePrivate && !isExported)
continue;
Optional<OP_DECL *> maybeOp = lookupOperatorDeclForName<OP_DECL>(
imported.module.importedModule, Loc, Name, isCascading);
if (!maybeOp)
return None;
if (OP_DECL *op = *maybeOp)
importedOperators[op] |= isExported;
}
llvm::PointerIntPair<OP_DECL *, 1, /*isPrivate*/ bool> result = {nullptr,
true};
if (!importedOperators.empty()) {
auto start = checkOperatorConflicts(SF, Loc, importedOperators);
if (start == importedOperators.end())
return None;
result = { start->first, start->second };
}
if (includePrivate || result.getInt())
return result.getPointer();
return nullptr;
}
template<typename OP_DECL>
static Optional<OP_DECL *>
lookupOperatorDeclForName(ModuleDecl *M, SourceLoc Loc, Identifier Name,
bool isCascading) {
OP_DECL *result = nullptr;
for (const FileUnit *File : M->getFiles()) {
auto next = lookupOperatorDeclForName<OP_DECL>(*File, Loc, Name, false,
isCascading);
if (!next.hasValue())
return next;
// FIXME: Diagnose ambiguity.
if (*next && result)
return None;
if (*next)
result = *next;
}
return result;
}
template <typename OperatorType>
OperatorType *LookupOperatorRequest<OperatorType>::evaluate(
Evaluator &evaluator, OperatorLookupDescriptor desc) const {
auto *file = desc.fileOrModule.get<FileUnit *>();
auto result =
lookupOperatorDeclForName<OperatorType>(*file, desc.diagLoc, desc.name,
/*includePrivate*/ true,
desc.isCascading);
if (!result.hasValue())
return nullptr;
if (!result.getValue()) {
result = lookupOperatorDeclForName<OperatorType>(file->getParentModule(),
desc.diagLoc, desc.name,
desc.isCascading);
}
return result.hasValue() ? result.getValue() : nullptr;
}
#define LOOKUP_OPERATOR(Kind) \
Kind##Decl *ModuleDecl::lookup##Kind(Identifier name, SourceLoc loc) { \
auto result = lookupOperatorDeclForName<Kind##Decl>( \
this, loc, name, /*isCascading*/ false); \
return result ? *result : nullptr; \
} \
template Kind##Decl *LookupOperatorRequest<Kind##Decl>::evaluate( \
Evaluator &e, OperatorLookupDescriptor) const;
LOOKUP_OPERATOR(PrefixOperator)
LOOKUP_OPERATOR(InfixOperator)
LOOKUP_OPERATOR(PostfixOperator)
LOOKUP_OPERATOR(PrecedenceGroup)
#undef LOOKUP_OPERATOR
void DirectOperatorLookupRequest::writeDependencySink(
evaluator::DependencyCollector &reqTracker,
TinyPtrVector<OperatorDecl *> ops) const {