Pass imports to SemIR. (#3415)

This adds instructions so that we get printing. I may adjust the
instruction format a little further to add a type, but I think the basic
setup will remain.

Note this builds on #3414
This commit is contained in:
Jon Ross-Perkins
2023-11-28 19:55:38 +00:00
committed by GitHub
parent 35d15a390c
commit 239f8030a4
20 changed files with 228 additions and 38 deletions
+96 -35
View File
@@ -17,6 +17,27 @@
namespace Carbon::Check {
struct UnitInfo {
// A given import within the file, with its destination.
struct Import {
Parse::Tree::PackagingNames names;
UnitInfo* unit_info;
};
// A file's imports corresponding to a single package, for the map.
struct PackageImports {
// Use the constructor so that the SmallVector is only constructed
// as-needed.
explicit PackageImports(Parse::Node node) : node(node) {}
// The first `import` directive in the file, which declared the package's
// identifier (even if the import failed). Used for associating diagnostics
// not specific to a single import.
Parse::Node node;
// Whether there's an import that failed to load.
bool has_load_error = false;
// The list of valid imports.
llvm::SmallVector<Import> imports;
};
explicit UnitInfo(Unit& unit)
: unit(&unit),
translator(unit.tokens, unit.tokens->source().filename(),
@@ -31,8 +52,11 @@ struct UnitInfo {
ErrorTrackingDiagnosticConsumer err_tracker;
DiagnosticEmitter<Parse::Node> emitter;
// A list of outgoing imports.
llvm::SmallVector<std::pair<Parse::Node, UnitInfo*>> imports;
// A map of package names to outgoing imports. If the
// import's target isn't available, the unit will be nullptr to assist with
// name lookup. Invalid imports (for example, `import Main;`) aren't added
// because they won't add identifiers to name lookup.
llvm::DenseMap<IdentifierId, PackageImports> package_imports_map;
// The remaining number of imports which must be checked before this unit can
// be processed.
@@ -43,6 +67,42 @@ struct UnitInfo {
llvm::SmallVector<UnitInfo*> incoming_imports;
};
// Add imports to the root block.
static auto AddImports(Context& context, UnitInfo& unit_info) -> void {
for (auto& [package_id, package_imports] : unit_info.package_imports_map) {
llvm::SmallVector<const SemIR::File*> sem_irs;
for (auto import : package_imports.imports) {
sem_irs.push_back(&**import.unit_info->unit->sem_ir);
}
context.AddPackageImports(package_imports.node, package_id, sem_irs,
package_imports.has_load_error);
}
}
// Loops over all nodes in the tree. On some errors, this may return early,
// for example if an unrecoverable state is encountered.
static auto ProcessParseNodes(Context& context,
ErrorTrackingDiagnosticConsumer& err_tracker)
-> bool {
for (auto parse_node : context.parse_tree().postorder()) {
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (auto parse_kind = context.parse_tree().node_kind(parse_node)) {
#define CARBON_PARSE_NODE_KIND(Name) \
case Parse::NodeKind::Name: { \
if (!Check::Handle##Name(context, parse_node)) { \
CARBON_CHECK(err_tracker.seen_error()) \
<< "Handle" #Name " returned false without printing a diagnostic"; \
return false; \
} \
break; \
}
#include "toolchain/parse/node_kind.def"
}
}
return true;
}
// Produces and checks the IR for the provided Parse::Tree.
// TODO: Both valid and invalid imports should be recorded on the SemIR. Invalid
// imports should suppress errors where it makes sense.
@@ -54,10 +114,9 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
// For ease-of-access.
SemIR::File& sem_ir = **unit_info.unit->sem_ir;
const Parse::Tree& parse_tree = *unit_info.unit->parse_tree;
Check::Context context(*unit_info.unit->tokens, unit_info.emitter, parse_tree,
sem_ir, vlog_stream);
Context context(*unit_info.unit->tokens, unit_info.emitter,
*unit_info.unit->parse_tree, sem_ir, vlog_stream);
PrettyStackTraceFunction context_dumper(
[&](llvm::raw_ostream& output) { context.PrintForStackDump(output); });
@@ -65,24 +124,11 @@ static auto CheckParseTree(const SemIR::File& builtin_ir, UnitInfo& unit_info,
context.inst_block_stack().Push();
context.PushScope();
// Loops over all nodes in the tree. On some errors, this may return early,
// for example if an unrecoverable state is encountered.
for (auto parse_node : parse_tree.postorder()) {
// clang warns on unhandled enum values; clang-tidy is incorrect here.
// NOLINTNEXTLINE(bugprone-switch-missing-default-case)
switch (auto parse_kind = parse_tree.node_kind(parse_node)) {
#define CARBON_PARSE_NODE_KIND(Name) \
case Parse::NodeKind::Name: { \
if (!Check::Handle##Name(context, parse_node)) { \
CARBON_CHECK(unit_info.err_tracker.seen_error()) \
<< "Handle" #Name " returned false without printing a diagnostic"; \
sem_ir.set_has_errors(true); \
return; \
} \
break; \
}
#include "toolchain/parse/node_kind.def"
}
AddImports(context, unit_info);
if (!ProcessParseNodes(context, unit_info.err_tracker)) {
context.sem_ir().set_has_errors(true);
return;
}
// Pop information for the file-level scope.
@@ -228,11 +274,19 @@ static auto TrackImport(
return;
}
// Get the package imports.
auto package_imports_it =
unit_info.package_imports_map.try_emplace(import.package_id, import.node)
.first;
if (auto api = api_map.find(import_key); api != api_map.end()) {
unit_info.imports.push_back({import.node, api->second});
// Add references between the file and imported api.
package_imports_it->second.imports.push_back({import, api->second});
++unit_info.imports_remaining;
api->second->incoming_imports.push_back(&unit_info);
} else {
// The imported api is missing.
package_imports_it->second.has_load_error = true;
CARBON_DIAGNOSTIC(LibraryApiNotFound, Error,
"Corresponding API not found.");
CARBON_DIAGNOSTIC(ImportNotFound, Error, "Imported API not found.");
@@ -392,17 +446,24 @@ auto CheckParseTrees(const SemIR::File& builtin_ir,
// TODO: Better identify cycles, maybe try to untangle them.
for (auto& unit_info : unit_infos) {
if (unit_info.imports_remaining > 0) {
for (auto* import_it = unit_info.imports.begin();
import_it != unit_info.imports.end();) {
auto* import_unit = import_it->second->unit;
if (*import_unit->sem_ir) {
++import_it;
} else {
CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
"Import cannot be used due to a cycle. Cycle "
"must be fixed to import.");
unit_info.emitter.Emit(import_it->first, ImportCycleDetected);
import_it = unit_info.imports.erase(import_it);
for (auto& [package_id, package_imports] :
unit_info.package_imports_map) {
for (auto* import_it = package_imports.imports.begin();
import_it != package_imports.imports.end();) {
if (*import_it->unit_info->unit->sem_ir) {
// The import is checked, so continue.
++import_it;
} else {
// The import hasn't been checked, indicating a cycle.
CARBON_DIAGNOSTIC(ImportCycleDetected, Error,
"Import cannot be used due to a cycle. Cycle "
"must be fixed to import.");
unit_info.emitter.Emit(import_it->names.node,
ImportCycleDetected);
// Make this look the same as an import which wasn't found.
package_imports.has_load_error = true;
import_it = package_imports.imports.erase(import_it);
}
}
}
}