Skip to content

Commit

Permalink
Build Generic objects for generic classes and interfaces. (#4086)
Browse files Browse the repository at this point in the history
In `ClassType`s and `InterfaceType`s, track a `GenericInstanceId` for
the instance rather than just the argument list.

---------

Co-authored-by: Jon Ross-Perkins <[email protected]>
  • Loading branch information
zygoloid and jonmeow authored Jun 27, 2024
1 parent a0d7672 commit 19c5596
Show file tree
Hide file tree
Showing 78 changed files with 453 additions and 180 deletions.
2 changes: 2 additions & 0 deletions toolchain/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ cc_library(
"decl_name_stack.cpp",
"eval.cpp",
"function.cpp",
"generic.cpp",
"import_ref.cpp",
"inst_block_stack.cpp",
"merge.cpp",
Expand All @@ -36,6 +37,7 @@ cc_library(
"diagnostic_helpers.h",
"eval.h",
"function.h",
"generic.h",
"import_ref.h",
"inst_block_stack.h",
"keyword_modifier_set.h",
Expand Down
62 changes: 52 additions & 10 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@

namespace Carbon::Check {

static auto MakeGenericInstance(Context& context, SemIR::GenericId generic_id,
SemIR::InstBlockId args_id)
-> SemIR::GenericInstanceId {
auto instance_id = context.generic_instances().GetOrAdd(generic_id, args_id);
// TODO: Perform substitution into the generic declaration if needed.
return instance_id;
}

namespace {
// The evaluation phase for an expression, computed by evaluation. These are
// ordered so that the phase of an expression is the numerically highest phase
Expand Down Expand Up @@ -177,6 +185,27 @@ static auto GetConstantValue(Context& context, SemIR::TypeBlockId type_block_id,
return type_block_id;
}

// The constant value of a generic instance is the generic instance with the
// corresponding constant values for its arguments.
static auto GetConstantValue(Context& context,
SemIR::GenericInstanceId instance_id, Phase* phase)
-> SemIR::GenericInstanceId {
if (!instance_id.is_valid()) {
return SemIR::GenericInstanceId::Invalid;
}

const auto& instance = context.generic_instances().Get(instance_id);
auto args_id = GetConstantValue(context, instance.args_id, phase);
if (!args_id.is_valid()) {
return SemIR::GenericInstanceId::Invalid;
}

if (args_id == instance.args_id) {
return instance_id;
}
return MakeGenericInstance(context, instance.generic_id, args_id);
}

// Replaces the specified field of the given typed instruction with its constant
// value, if it has constant phase. Returns true on success, false if the value
// has runtime phase.
Expand Down Expand Up @@ -900,22 +929,32 @@ static auto MakeConstantForCall(Context& context, SemIRLoc loc,
auto type_inst =
context.types().GetAsInst(context.insts().Get(call.callee_id).type_id());
CARBON_KIND_SWITCH(type_inst) {
case CARBON_KIND(SemIR::GenericClassType generic_class):
case CARBON_KIND(SemIR::GenericClassType generic_class): {
auto instance_id = MakeGenericInstance(
context, context.classes().Get(generic_class.class_id).generic_id,
call.args_id);
return MakeConstantResult(
context,
SemIR::ClassType{.type_id = call.type_id,
.class_id = generic_class.class_id,
.args_id = call.args_id},
.instance_id = instance_id},
phase);
case CARBON_KIND(SemIR::GenericInterfaceType generic_interface):
}
case CARBON_KIND(SemIR::GenericInterfaceType generic_interface): {
auto instance_id = MakeGenericInstance(
context,
context.interfaces().Get(generic_interface.interface_id).generic_id,
call.args_id);
return MakeConstantResult(
context,
SemIR::InterfaceType{.type_id = call.type_id,
.interface_id = generic_interface.interface_id,
.args_id = call.args_id},
.instance_id = instance_id},
phase);
default:
}
default: {
return SemIR::ConstantId::NotConstant;
}
}
}

Expand Down Expand Up @@ -975,10 +1014,10 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
&SemIR::BoundMethod::function_id);
case SemIR::ClassType::Kind:
return RebuildIfFieldsAreConstant(context, inst,
&SemIR::ClassType::args_id);
&SemIR::ClassType::instance_id);
case SemIR::InterfaceType::Kind:
return RebuildIfFieldsAreConstant(context, inst,
&SemIR::InterfaceType::args_id);
&SemIR::InterfaceType::instance_id);
case SemIR::InterfaceWitness::Kind:
return RebuildIfFieldsAreConstant(context, inst,
&SemIR::InterfaceWitness::elements_id);
Expand Down Expand Up @@ -1065,7 +1104,8 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
return MakeConstantResult(
context,
SemIR::ClassType{.type_id = SemIR::TypeId::TypeType,
.class_id = class_decl.class_id},
.class_id = class_decl.class_id,
.instance_id = SemIR::GenericInstanceId::Invalid},
Phase::Template);
}
case CARBON_KIND(SemIR::InterfaceDecl interface_decl): {
Expand All @@ -1081,8 +1121,10 @@ auto TryEvalInst(Context& context, SemIR::InstId inst_id, SemIR::Inst inst)
// A non-generic interface declaration evaluates to the interface type.
return MakeConstantResult(
context,
SemIR::InterfaceType{.type_id = SemIR::TypeId::TypeType,
.interface_id = interface_decl.interface_id},
SemIR::InterfaceType{
.type_id = SemIR::TypeId::TypeType,
.interface_id = interface_decl.interface_id,
.instance_id = SemIR::GenericInstanceId::Invalid},
Phase::Template);
}

Expand Down
43 changes: 43 additions & 0 deletions toolchain/check/generic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include "toolchain/check/generic.h"

#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {

auto StartGenericDecl(Context& /*context*/) -> void {
// TODO: Start tracking the contents of this declaration.
}

auto StartGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Start tracking the contents of this definition.
}

auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
-> SemIR::GenericId {
if (context.scope_stack().compile_time_binding_stack().empty()) {
return SemIR::GenericId::Invalid;
}

auto bindings_id = context.inst_blocks().Add(
context.scope_stack().compile_time_binding_stack());
return context.generics().Add(
SemIR::Generic{.decl_id = decl_id, .bindings_id = bindings_id});
}

auto FinishGenericRedecl(Context& /*context*/, SemIR::InstId /*decl_id*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Compare contents of this declaration with the existing one on the
// generic.
}

auto FinishGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void {
// TODO: Track contents of this generic definition.
}

} // namespace Carbon::Check
37 changes: 37 additions & 0 deletions toolchain/check/generic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef CARBON_TOOLCHAIN_CHECK_GENERIC_H_
#define CARBON_TOOLCHAIN_CHECK_GENERIC_H_

#include "toolchain/check/context.h"
#include "toolchain/sem_ir/ids.h"

namespace Carbon::Check {

// Start processing a declaration or definition that might be a generic entity.
auto StartGenericDecl(Context& /*context*/) -> void;

// Start processing a declaration or definition that might be a generic entity.
auto StartGenericDefinition(Context& /*context*/,
SemIR::GenericId /*generic_id*/) -> void;

// Finish processing a potentially generic declaration and produce a
// corresponding generic object. Returns SemIR::GenericId::Invalid if this
// declaration is not actually generic.
auto FinishGenericDecl(Context& context, SemIR::InstId decl_id)
-> SemIR::GenericId;

// Merge a redeclaration of an entity that might be a generic into the original
// declaration.
auto FinishGenericRedecl(Context& context, SemIR::InstId decl_id,
SemIR::GenericId generic_id) -> void;

// Finish processing a potentially generic definition.
auto FinishGenericDefinition(Context& context, SemIR::GenericId generic_id)
-> void;

} // namespace Carbon::Check

#endif // CARBON_TOOLCHAIN_CHECK_GENERIC_H_
16 changes: 14 additions & 2 deletions toolchain/check/handle_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "toolchain/check/convert.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/merge.h"
#include "toolchain/check/modifiers.h"
Expand Down Expand Up @@ -38,6 +39,8 @@ auto HandleClassIntroducer(Context& context, Parse::ClassIntroducerId node_id)
// Optional modifiers and the name follow.
context.decl_introducer_state_stack().Push<Lex::TokenKind::Class>();
context.decl_name_stack().PushScopeAndStartName();
// This class is potentially generic.
StartGenericDecl(context);
return true;
}

Expand Down Expand Up @@ -221,6 +224,7 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
SemIR::Class class_info = {
.name_id = name_context.name_id_for_new_inst(),
.parent_scope_id = name_context.parent_scope_id_for_new_inst(),
.generic_id = SemIR::GenericId::Invalid,
.implicit_param_refs_id = name.implicit_params_id,
.param_refs_id = name.params_id,
// `.self_type_id` depends on the ClassType, so is set below.
Expand All @@ -238,10 +242,13 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
// TODO: If this is an invalid redeclaration of a non-class entity or there
// was an error in the qualifier, we will have lost track of the class name
// here. We should keep track of it even if the name is invalid.
class_info.generic_id = FinishGenericDecl(context, class_decl_id);
class_decl.class_id = context.classes().Add(class_info);
if (class_info.is_generic()) {
class_decl.type_id = context.GetGenericClassType(class_decl.class_id);
}
} else {
FinishGenericRedecl(context, class_decl_id, class_info.generic_id);
}

// Write the class ID into the ClassDecl.
Expand All @@ -253,11 +260,13 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
// declaration.
auto& class_info = context.classes().Get(class_decl.class_id);
if (class_info.is_generic()) {
// TODO: Pass in the generic arguments once we can represent them.
// TODO: Build generic arguments representing the parameters.
auto instance_id = SemIR::GenericInstanceId::Invalid;
class_info.self_type_id = context.GetTypeIdForTypeConstant(
TryEvalInst(context, SemIR::InstId::Invalid,
SemIR::ClassType{.type_id = SemIR::TypeId::TypeType,
.class_id = class_decl.class_id}));
.class_id = class_decl.class_id,
.instance_id = instance_id}));
} else {
class_info.self_type_id = context.GetTypeIdForTypeInst(class_decl_id);
}
Expand Down Expand Up @@ -291,6 +300,7 @@ auto HandleClassDefinitionStart(Context& context,

// Enter the class scope.
context.scope_stack().Push(class_decl_id, class_info.scope_id);
StartGenericDefinition(context, class_info.generic_id);

// Introduce `Self`.
context.name_scopes().AddRequiredName(
Expand Down Expand Up @@ -608,6 +618,8 @@ auto HandleClassDefinition(Context& context,
class_info.object_repr_id = context.GetStructType(fields_id);
}

FinishGenericDefinition(context, class_info.generic_id);

// The decl_name_stack and scopes are popped by `ProcessNodeIds`.
return true;
}
Expand Down
20 changes: 11 additions & 9 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "toolchain/check/decl_introducer_state.h"
#include "toolchain/check/decl_name_stack.h"
#include "toolchain/check/function.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/interface.h"
#include "toolchain/check/merge.h"
Expand All @@ -16,7 +17,6 @@
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/entry_point.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"

Expand All @@ -32,6 +32,8 @@ auto HandleFunctionIntroducer(Context& context,
// Optional modifiers and the name follow.
context.decl_introducer_state_stack().Push<Lex::TokenKind::Fn>();
context.decl_name_stack().PushScopeAndStartName();
// The function is potentially generic.
StartGenericDecl(context);
return true;
}

Expand Down Expand Up @@ -264,16 +266,10 @@ static auto BuildFunctionDecl(Context& context,

// Create a new function if this isn't a valid redeclaration.
if (!function_decl.function_id.is_valid()) {
// For a generic function, build the corresponding Generic entity.
if (!context.scope_stack().compile_time_binding_stack().empty()) {
function_info.generic_id = context.generics().Add(SemIR::Generic{
.decl_id = decl_id,
.bindings_id = context.inst_blocks().Add(
context.scope_stack().compile_time_binding_stack())});
}

function_info.generic_id = FinishGenericDecl(context, decl_id);
function_decl.function_id = context.functions().Add(function_info);
} else {
FinishGenericRedecl(context, decl_id, function_info.generic_id);
// TODO: Validate that the redeclaration doesn't set an access modifier.
}
function_decl.type_id = context.GetFunctionType(function_decl.function_id);
Expand Down Expand Up @@ -342,6 +338,7 @@ static auto HandleFunctionDefinitionAfterSignature(
context.return_scope_stack().push_back({.decl_id = decl_id});
context.inst_block_stack().Push();
context.scope_stack().Push(decl_id);
StartGenericDefinition(context, function.generic_id);
context.AddCurrentCodeBlockToFunction();

// Check the return type is complete.
Expand Down Expand Up @@ -426,6 +423,11 @@ auto HandleFunctionDefinition(Context& context,
context.inst_block_stack().Pop();
context.return_scope_stack().pop_back();
context.decl_name_stack().PopScope();

// If this is a generic function, collect information about the definition.
auto& function = context.functions().Get(function_id);
FinishGenericDefinition(context, function.generic_id);

return true;
}

Expand Down
Loading

0 comments on commit 19c5596

Please sign in to comment.