Skip to content

Commit

Permalink
Add ExternDecl and ExternType for extern classes. (carbon-language#3893)
Browse files Browse the repository at this point in the history
This expands `extern` handling for most cases.
  • Loading branch information
jonmeow authored and CJ Johnson committed May 23, 2024
1 parent 8087237 commit 531dc7c
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 307 deletions.
9 changes: 8 additions & 1 deletion toolchain/check/class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Carbon::Check {

auto MergeClassRedecl(Context& context, SemIRLoc new_loc,
SemIR::Class& new_class, bool /*new_is_import*/,
SemIR::Class& new_class, bool new_is_import,
bool new_is_definition, bool new_is_extern,
SemIR::ClassId prev_class_id, bool prev_is_extern,
SemIR::ImportIRInstId prev_import_ir_inst_id) -> bool {
Expand Down Expand Up @@ -51,6 +51,13 @@ auto MergeClassRedecl(Context& context, SemIRLoc new_loc,
prev_class.object_repr_id = new_class.object_repr_id;
}

if ((prev_import_ir_inst_id.is_valid() && !new_is_import) ||
(prev_is_extern && !new_is_extern)) {
prev_class.decl_id = new_class.decl_id;
ReplacePrevInstForMerge(
context, prev_class.enclosing_scope_id, prev_class.name_id,
new_is_import ? new_loc.inst_id : new_class.decl_id);
}
return true;
}

Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,7 @@ class TypeCompleter {
case SemIR::ClassInit::Kind:
case SemIR::Converted::Kind:
case SemIR::Deref::Kind:
case SemIR::ExternDecl::Kind:
case SemIR::FacetTypeAccess::Kind:
case SemIR::FloatLiteral::Kind:
case SemIR::FieldDecl::Kind:
Expand Down Expand Up @@ -1056,6 +1057,7 @@ class TypeCompleter {

case SemIR::AssociatedEntityType::Kind:
case SemIR::BindSymbolicName::Kind:
case SemIR::ExternType::Kind:
case SemIR::FloatType::Kind:
case SemIR::IntType::Kind:
case SemIR::PointerType::Kind:
Expand Down
10 changes: 10 additions & 0 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,15 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
return RebuildIfFieldsAreConstant(context, inst,
&SemIR::BoundMethod::object_id,
&SemIR::BoundMethod::function_id);
case CARBON_KIND(SemIR::ExternDecl extern_decl): {
// Return an extern form of the declaration's constant value.
auto non_extern_type_id = context.GetTypeIdForTypeConstant(
context.constant_values().Get(extern_decl.decl_id));
return MakeConstantResult(
context,
SemIR::ExternType{SemIR::TypeId::TypeType, non_extern_type_id},
Phase::Template);
}
case SemIR::InterfaceWitness::Kind:
return RebuildIfFieldsAreConstant(context, inst,
&SemIR::InterfaceWitness::elements_id);
Expand Down Expand Up @@ -975,6 +984,7 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
}

case SemIR::ClassType::Kind:
case SemIR::ExternType::Kind:
case SemIR::InterfaceType::Kind:
CARBON_FATAL() << inst.kind()
<< " is only created during corresponding Decl handling.";
Expand Down
84 changes: 62 additions & 22 deletions toolchain/check/handle_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "toolchain/base/kind_switch.h"
#include "toolchain/check/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/merge.h"
#include "toolchain/check/modifiers.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"

namespace Carbon::Check {
Expand Down Expand Up @@ -35,6 +38,51 @@ auto HandleClassIntroducer(Context& context, Parse::ClassIntroducerId node_id)
return true;
}

// Adds the name to name lookup. If there's a conflict, tries to merge. May
// update class_decl and class_info when merging.
static auto MergeOrAddName(Context& context, Parse::AnyClassDeclId node_id,
const DeclNameStack::NameContext& name_context,
SemIR::InstId class_decl_id,
SemIR::ClassDecl& class_decl,
SemIR::Class& class_info, bool is_definition,
bool is_extern) -> void {
auto prev_id =
context.decl_name_stack().LookupOrAddName(name_context, class_decl_id);
if (prev_id.is_valid()) {
auto prev_inst_for_merge =
ResolvePrevInstForMerge(context, node_id, prev_id);

auto prev_class_id = SemIR::ClassId::Invalid;
CARBON_KIND_SWITCH(prev_inst_for_merge.inst) {
case CARBON_KIND(SemIR::ClassDecl class_decl): {
prev_class_id = class_decl.class_id;
break;
}
case CARBON_KIND(SemIR::ClassType class_type): {
prev_class_id = class_type.class_id;
break;
}
default:
// This is a redeclaration of something other than a class.
context.DiagnoseDuplicateName(class_decl_id, prev_id);
break;
}

if (prev_class_id.is_valid()) {
if (MergeClassRedecl(context, node_id, class_info,
/*new_is_import=*/false, is_definition, is_extern,
prev_class_id, prev_inst_for_merge.is_extern,
prev_inst_for_merge.import_ir_inst_id)) {
// When merging, use the existing entity rather than adding a new one.
class_decl.class_id = prev_class_id;
}
} else {
// This is a redeclaration of something other than a class.
context.DiagnoseDuplicateName(class_decl_id, prev_id);
}
}
}

static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
bool is_definition)
-> std::tuple<SemIR::ClassId, SemIR::InstId> {
Expand Down Expand Up @@ -88,30 +136,16 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
.decl_id = class_decl_id,
.inheritance_kind = inheritance_kind};

// Check whether this is a redeclaration.
auto prev_id =
context.decl_name_stack().LookupOrAddName(name_context, class_decl_id);
if (prev_id.is_valid()) {
auto prev_inst_for_merge =
ResolvePrevInstForMerge(context, node_id, prev_id);

if (auto prev_class_decl =
prev_inst_for_merge.inst.TryAs<SemIR::ClassDecl>()) {
// TODO: Fix prev_is_extern.
if (MergeClassRedecl(context, node_id, class_info,
/*new_is_import=*/false, is_definition, is_extern,
prev_class_decl->class_id,
/*prev_is_extern=*/false,
prev_inst_for_merge.import_ir_inst_id)) {
// When merging, use the existing entity rather than adding a new one.
class_decl.class_id = prev_class_decl->class_id;
}
} else {
// This is a redeclaration of something other than a class.
context.DiagnoseDuplicateName(class_decl_id, prev_id);
}
auto extern_decl_id = SemIR::InstId::Invalid;
if (is_extern) {
extern_decl_id = context.AddPlaceholderInst(
{node_id, SemIR::ExternDecl{SemIR::TypeId::TypeType, class_decl_id}});
}

MergeOrAddName(context, node_id, name_context,
extern_decl_id.is_valid() ? extern_decl_id : class_decl_id,
class_decl, class_info, is_definition, is_extern);

// Create a new class if this isn't a valid redeclaration.
bool is_new_class = !class_decl.class_id.is_valid();
if (is_new_class) {
Expand All @@ -130,6 +164,12 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
class_info.self_type_id = context.GetTypeIdForTypeInst(class_decl_id);
}

if (is_extern) {
context.ReplaceInstBeforeConstantUse(
extern_decl_id,
SemIR::ExternDecl{SemIR::TypeId::TypeType, class_decl_id});
}

return {class_decl.class_id, class_decl_id};
}

Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ static auto BuildFunctionDecl(Context& context,
auto function_decl = SemIR::FunctionDecl{
context.GetBuiltinType(SemIR::BuiltinKind::FunctionType),
SemIR::FunctionId::Invalid, decl_block_id};
// TODO: Should Function replace is_extern with ExternDecl, similar to
// ClassDecl?
auto function_info = SemIR::Function{
.name_id = name_context.name_id_for_new_inst(),
.enclosing_scope_id = name_context.enclosing_scope_id_for_new_inst(),
Expand Down
5 changes: 5 additions & 0 deletions toolchain/check/import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ static auto GetImportName(const SemIR::File& import_sem_ir,
return {class_info.name_id, class_info.enclosing_scope_id};
}

case CARBON_KIND(SemIR::ExternDecl extern_decl): {
return GetImportName(import_sem_ir,
import_sem_ir.insts().Get(extern_decl.decl_id));
}

case CARBON_KIND(SemIR::FunctionDecl function_decl): {
const auto& function =
import_sem_ir.functions().Get(function_decl.function_id);
Expand Down
16 changes: 16 additions & 0 deletions toolchain/check/import_ref.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ class ImportRefResolver {
case CARBON_KIND(SemIR::ConstType inst): {
return TryResolveTypedInst(inst);
}
case CARBON_KIND(SemIR::ExternDecl inst): {
return TryResolveTypedInst(inst);
}
case CARBON_KIND(SemIR::FieldDecl inst): {
return TryResolveTypedInst(inst, inst_id);
}
Expand Down Expand Up @@ -669,6 +672,19 @@ class ImportRefResolver {
SemIR::ConstType{SemIR::TypeId::TypeType, inner_type_id})};
}

auto TryResolveTypedInst(SemIR::ExternDecl inst) -> ResolveResult {
auto initial_work = work_stack_.size();
CARBON_CHECK(inst.type_id == SemIR::TypeId::TypeType);
auto decl_const_id = GetLocalConstantId(inst.decl_id);
if (HasNewWork(initial_work)) {
return ResolveResult::Retry();
}
auto extern_id = context_.AddInstInNoBlock(SemIR::LocIdAndInst::Untyped(
AddImportIRInst(inst.decl_id),
SemIR::ExternDecl{SemIR::TypeId::TypeType, decl_const_id.inst_id()}));
return {context_.constant_values().Get(extern_id)};
}

auto TryResolveTypedInst(SemIR::FieldDecl inst, SemIR::InstId import_inst_id)
-> ResolveResult {
auto initial_work = work_stack_.size();
Expand Down
76 changes: 49 additions & 27 deletions toolchain/check/merge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,30 +127,44 @@ auto CheckIsAllowedRedecl(Context& context, Lex::TokenKind decl_kind,

auto ResolvePrevInstForMerge(Context& context, Parse::NodeId node_id,
SemIR::InstId prev_inst_id) -> InstForMerge {
auto prev_inst = context.insts().Get(prev_inst_id);
auto import_ref = prev_inst.TryAs<SemIR::AnyImportRef>();
// If not imported, use the instruction directly.
if (!import_ref) {
return {.inst = prev_inst,
.import_ir_inst_id = SemIR::ImportIRInstId::Invalid};
}
InstForMerge result = {.inst = context.insts().Get(prev_inst_id),
.import_ir_inst_id = SemIR::ImportIRInstId::Invalid,
.is_extern = false};

// If the import ref was previously used, print a diagnostic.
if (auto import_ref_used = prev_inst.TryAs<SemIR::ImportRefUsed>()) {
CARBON_DIAGNOSTIC(
RedeclOfUsedImport, Error,
"Redeclaration of imported entity that was previously used.");
CARBON_DIAGNOSTIC(UsedImportLoc, Note, "Import used here.");
context.emitter()
.Build(node_id, RedeclOfUsedImport)
.Note(import_ref_used->used_id, UsedImportLoc)
.Emit();
CARBON_KIND_SWITCH(result.inst) {
case CARBON_KIND(SemIR::ExternDecl extern_decl): {
result.is_extern = true;
result.inst = context.insts().Get(extern_decl.decl_id);
break;
}
case CARBON_KIND(SemIR::ImportRefUsed import_ref): {
CARBON_DIAGNOSTIC(
RedeclOfUsedImport, Error,
"Redeclaration of imported entity that was previously used.");
CARBON_DIAGNOSTIC(UsedImportLoc, Note, "Import used here.");
context.emitter()
.Build(node_id, RedeclOfUsedImport)
.Note(import_ref.used_id, UsedImportLoc)
.Emit();
[[fallthrough]];
}
case SemIR::ImportRefLoaded::Kind: {
// Follow the import ref.
auto import_ref = result.inst.As<SemIR::AnyImportRef>();
result.import_ir_inst_id = import_ref.import_ir_inst_id;
result.inst = context.insts().Get(
context.constant_values().Get(prev_inst_id).inst_id());
if (auto extern_type = result.inst.TryAs<SemIR::ExternType>()) {
result.inst =
context.types().GetAsInst(extern_type->non_extern_type_id);
}
break;
}
default:
break;
}

// Follow the import ref.
return {.inst = context.insts().Get(
context.constant_values().Get(prev_inst_id).inst_id()),
.import_ir_inst_id = import_ref->import_ir_inst_id};
return result;
}

// Returns the instruction to consider when merging the given inst_id. Returns
Expand Down Expand Up @@ -185,9 +199,18 @@ static auto ResolveMergeableInst(Context& context, SemIR::InstId inst_id)
if (!const_id.is_constant()) {
return std::nullopt;
}
return {
{.inst = context.insts().Get(const_id.inst_id()),
.import_ir_inst_id = inst.As<SemIR::AnyImportRef>().import_ir_inst_id}};

InstForMerge result = {
.inst = context.insts().Get(const_id.inst_id()),
.import_ir_inst_id = inst.As<SemIR::AnyImportRef>().import_ir_inst_id,
.is_extern = false};

if (auto extern_type = result.inst.TryAs<SemIR::ExternType>()) {
result.is_extern = true;
result.inst = context.types().GetAsInst(extern_type->non_extern_type_id);
}

return result;
}

auto ReplacePrevInstForMerge(Context& context, SemIR::NameScopeId scope_id,
Expand Down Expand Up @@ -240,11 +263,10 @@ auto MergeImportRef(Context& context, SemIR::InstId new_inst_id,
}

auto new_class = context.classes().Get(new_type.class_id);
// TODO: Fix new_is_extern and prev_is_extern.
MergeClassRedecl(context, new_inst_id, new_class,
/*new_is_import=*/true, new_class.is_defined(),
/*new_is_extern=*/false, prev_type->class_id,
/*prev_is_extern=*/false, prev_inst->import_ir_inst_id);
new_inst->is_extern, prev_type->class_id,
prev_inst->is_extern, prev_inst->import_ir_inst_id);
return;
}
default:
Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/merge.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ struct InstForMerge {
// The imported instruction, or invalid if not an import. This should
// typically only be used for the ImportIRId, but we only load it if needed.
SemIR::ImportIRInstId import_ir_inst_id;
// True if an `extern` declaration.
bool is_extern;
};

// Resolves prev_inst_id for merging (or name conflicts). This handles imports
Expand Down
Loading

0 comments on commit 531dc7c

Please sign in to comment.