mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user