Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for binding patterns in SemIR #4221

Merged
merged 21 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions toolchain/check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ cc_library(
"merge.cpp",
"modifiers.cpp",
"name_component.cpp",
"pattern_block_stack.cpp",
"return.cpp",
"subst.cpp",
],
Expand All @@ -51,6 +52,7 @@ cc_library(
"modifiers.h",
"name_component.h",
"param_and_arg_refs_stack.h",
"pattern_block_stack.h",
"pending_block.h",
"return.h",
"subst.h",
Expand Down
8 changes: 8 additions & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Context::Context(const Lex::TokenizedBuffer& tokens, DiagnosticEmitter& emitter,
vlog_stream_(vlog_stream),
node_stack_(parse_tree, vlog_stream),
inst_block_stack_("inst_block_stack_", sem_ir, vlog_stream),
pattern_block_stack_(this),
param_and_arg_refs_stack_(sem_ir, vlog_stream, node_stack_),
args_type_info_stack_("args_type_info_stack_", sem_ir, vlog_stream),
decl_name_stack_(this),
Expand Down Expand Up @@ -147,6 +148,13 @@ auto Context::AddPlaceholderInst(SemIR::LocIdAndInst loc_id_and_inst)
return inst_id;
}

auto Context::AddPatternInst(SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId {
auto inst_id = sem_ir().insts().AddInNoBlock(loc_id_and_inst);
pattern_block_stack_.AddInst(inst_id);
return inst_id;
}

auto Context::AddConstant(SemIR::Inst inst, bool is_symbolic)
-> SemIR::ConstantId {
auto const_id = constants().GetOrAdd(inst, is_symbolic);
Expand Down
19 changes: 19 additions & 0 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "toolchain/check/inst_block_stack.h"
#include "toolchain/check/node_stack.h"
#include "toolchain/check/param_and_arg_refs_stack.h"
#include "toolchain/check/pattern_block_stack.h"
#include "toolchain/check/scope_stack.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/parse/tree.h"
Expand Down Expand Up @@ -112,6 +113,18 @@ class Context {
auto AddPlaceholderInstInNoBlock(SemIR::LocIdAndInst loc_id_and_inst)
-> SemIR::InstId;

// Adds an instruction to the current pattern block, returning the produced
// ID.
auto AddPatternInst(SemIR::LocIdAndInst loc_id_and_inst) -> SemIR::InstId;

// Convenience for AddPatternInst with typed nodes.
template <typename InstT>
requires(SemIR::Internal::HasNodeId<InstT>)
auto AddPatternInst(decltype(InstT::Kind)::TypedNodeId node_id, InstT inst)
-> SemIR::InstId {
return AddPatternInst(SemIR::LocIdAndInst(node_id, inst));
}

// Adds an instruction to the constants block, returning the produced ID.
auto AddConstant(SemIR::Inst inst, bool is_symbolic) -> SemIR::ConstantId;

Expand Down Expand Up @@ -392,6 +405,9 @@ class Context {
auto node_stack() -> NodeStack& { return node_stack_; }

auto inst_block_stack() -> InstBlockStack& { return inst_block_stack_; }
auto pattern_block_stack() -> PatternBlockStack& {
return pattern_block_stack_;
}

auto param_and_arg_refs_stack() -> ParamAndArgRefsStack& {
return param_and_arg_refs_stack_;
Expand Down Expand Up @@ -529,6 +545,9 @@ class Context {
// The stack of instruction blocks being used for general IR generation.
InstBlockStack inst_block_stack_;

// The stack of instruction blocks that contain pattern instructions.
PatternBlockStack pattern_block_stack_;

// The stack of instruction blocks being used for param and arg ref blocks.
ParamAndArgRefsStack param_and_arg_refs_stack_;

Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,7 @@ auto TryEvalInstInContext(EvalContext& eval_context, SemIR::InstId inst_id,
case SemIR::AddrPattern::Kind:
case SemIR::Assign::Kind:
case SemIR::BindName::Kind:
case SemIR::BindingPattern::Kind:
case SemIR::BlockArg::Kind:
case SemIR::Branch::Kind:
case SemIR::BranchIf::Kind:
Expand All @@ -1478,6 +1479,7 @@ auto TryEvalInstInContext(EvalContext& eval_context, SemIR::InstId inst_id,
case SemIR::ReturnExpr::Kind:
case SemIR::Return::Kind:
case SemIR::StructLiteral::Kind:
case SemIR::SymbolicBindingPattern::Kind:
case SemIR::TupleLiteral::Kind:
case SemIR::VarStorage::Kind:
break;
Expand Down
8 changes: 6 additions & 2 deletions toolchain/check/handle_alias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ auto HandleParseNode(Context& context, Parse::AliasIntroducerId /*node_id*/)
-> bool {
context.decl_introducer_state_stack().Push<Lex::TokenKind::Alias>();
context.decl_name_stack().PushScopeAndStartName();
context.pattern_block_stack().Push();
return true;
}

auto HandleParseNode(Context& /*context*/,
Parse::AliasInitializerId /*node_id*/) -> bool {
auto HandleParseNode(Context& context, Parse::AliasInitializerId /*node_id*/)
-> bool {
// TODO: when/if parameterized aliases are supported, this must be
// attached to the `BindAlias` inst.
(void)context.pattern_block_stack().Pop();
return true;
}

Expand Down
14 changes: 14 additions & 0 deletions toolchain/check/handle_binding_pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
// TODO: Bindings should come into scope immediately in other contexts
// too.
context.AddNameToLookup(name_id, bind_id);
auto entity_name_id =
context.insts().GetAs<SemIR::AnyBindName>(bind_id).entity_name_id;
if (is_generic) {
context.AddPatternInst<SemIR::SymbolicBindingPattern>(
name_node,
{.type_id = cast_type_id, .entity_name_id = entity_name_id});
} else {
context.AddPatternInst<SemIR::BindingPattern>(
name_node,
{.type_id = cast_type_id, .entity_name_id = entity_name_id});
}
// TODO: use the pattern insts to generate the pattern-match insts
// at the end of the full pattern, instead of eagerly generating them
// here.
break;
}

Expand Down
7 changes: 6 additions & 1 deletion toolchain/check/handle_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ auto HandleParseNode(Context& context, Parse::ClassIntroducerId node_id)
context.decl_name_stack().PushScopeAndStartName();
// This class is potentially generic.
StartGenericDecl(context);
// Start a new pattern block for the signature.
context.pattern_block_stack().Push();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, but something else to consider with storage might be whether we want the parameters and implicit parameters to each have their own pattern block. A single block is fine for now, I'm not asking for a change in this PR, but it might be something that affects the implementation more later.

return true;
}

Expand Down Expand Up @@ -208,12 +210,15 @@ static auto BuildClassDecl(Context& context, Parse::AnyClassDeclId node_id,
? SemIR::Class::Base
: SemIR::Class::Final;

SemIR::InstBlockId pattern_block_id = context.pattern_block_stack().Pop();
auto decl_block_id = context.inst_block_stack().Pop();
SemIR::DeclId decl_id = context.sem_ir().decls().Add(
{.pattern_block_id = pattern_block_id, .decl_block_id = decl_block_id});

// Add the class declaration.
auto class_decl = SemIR::ClassDecl{.type_id = SemIR::TypeId::TypeType,
.class_id = SemIR::ClassId::Invalid,
.decl_block_id = decl_block_id};
.decl_id = decl_id};
auto class_decl_id =
context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, class_decl));

Expand Down
7 changes: 7 additions & 0 deletions toolchain/check/handle_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ auto HandleParseNode(Context& context, Parse::ExportIntroducerId /*node_id*/)
context.decl_introducer_state_stack().Push<Lex::TokenKind::Export>();
// TODO: Probably need to update DeclNameStack to restrict to only namespaces.
context.decl_name_stack().PushScopeAndStartName();
// The parser supports patterns after `export`, so we need a pattern block
// to handle them.
context.pattern_block_stack().Push();
return true;
}

auto HandleParseNode(Context& context, Parse::ExportDeclId node_id) -> bool {
// Discard the pattern block, because patterns after `export` are disallowed,
// and diagnosed elsewhere.
(void)context.pattern_block_stack().Pop();

auto name_context = context.decl_name_stack().FinishName(
PopNameComponentWithoutParams(context, Lex::TokenKind::Export));
context.decl_name_stack().PopScope();
Expand Down
36 changes: 22 additions & 14 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ auto HandleParseNode(Context& context, Parse::FunctionIntroducerId node_id)
context.decl_name_stack().PushScopeAndStartName();
// The function is potentially generic.
StartGenericDecl(context);
// Start a new pattern block for the signature.
context.pattern_block_stack().Push();
return true;
}

Expand Down Expand Up @@ -171,8 +173,6 @@ static auto BuildFunctionDecl(Context& context,
Parse::AnyFunctionDeclId node_id,
bool is_definition)
-> std::pair<SemIR::FunctionId, SemIR::InstId> {
auto decl_block_id = context.inst_block_stack().Pop();

auto return_storage_id = SemIR::InstId::Invalid;
if (auto [return_node, maybe_return_storage_id] =
context.node_stack().PopWithNodeIdIf<Parse::NodeKind::ReturnType>();
Expand Down Expand Up @@ -209,48 +209,56 @@ static auto BuildFunctionDecl(Context& context,
}

// Add the function declaration.
auto function_decl = SemIR::FunctionDecl{
SemIR::TypeId::Invalid, SemIR::FunctionId::Invalid, decl_block_id};
auto decl_id =
auto pattern_block_id = context.pattern_block_stack().Pop();
auto decl_block_id = context.inst_block_stack().Pop();
SemIR::DeclId decl_id = context.sem_ir().decls().Add({
.pattern_block_id = pattern_block_id,
.decl_block_id = decl_block_id,
});
SemIR::FunctionDecl function_decl = {
.type_id = SemIR::TypeId::Invalid,
.function_id = SemIR::FunctionId::Invalid,
.decl_id = decl_id};
auto decl_inst_id =
context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, function_decl));

// Build the function entity. This will be merged into an existing function if
// there is one, or otherwise added to the function store.
auto function_info = SemIR::Function{
{name_context.MakeEntityWithParamsBase(name, decl_id, is_extern)},
{name_context.MakeEntityWithParamsBase(name, decl_inst_id, is_extern)},
{.return_storage_id = return_storage_id}};
if (is_definition) {
function_info.definition_id = decl_id;
function_info.definition_id = decl_inst_id;
}

TryMergeRedecl(context, node_id, name_context.prev_inst_id(), function_decl,
function_info, is_definition);

// Create a new function if this isn't a valid redeclaration.
if (!function_decl.function_id.is_valid()) {
function_info.generic_id = FinishGenericDecl(context, decl_id);
function_info.generic_id = FinishGenericDecl(context, decl_inst_id);
function_decl.function_id = context.functions().Add(function_info);
} else {
FinishGenericRedecl(context, decl_id, function_info.generic_id);
FinishGenericRedecl(context, decl_inst_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, context.scope_stack().PeekSpecificId());

// Write the function ID into the FunctionDecl.
context.ReplaceInstBeforeConstantUse(decl_id, function_decl);
context.ReplaceInstBeforeConstantUse(decl_inst_id, function_decl);

// Check if we need to add this to name lookup, now that the function decl is
// done.
if (!name_context.prev_inst_id().is_valid()) {
// At interface scope, a function declaration introduces an associated
// function.
auto lookup_result_id = decl_id;
auto lookup_result_id = decl_inst_id;
if (parent_scope_inst && !name_context.has_qualifiers) {
if (auto interface_scope =
parent_scope_inst->TryAs<SemIR::InterfaceDecl>()) {
lookup_result_id = BuildAssociatedEntity(
context, interface_scope->interface_id, decl_id);
context, interface_scope->interface_id, decl_inst_id);
}
}

Expand All @@ -276,10 +284,10 @@ static auto BuildFunctionDecl(Context& context,
}

if (!is_definition && context.IsImplFile() && !is_extern) {
context.definitions_required().push_back(decl_id);
context.definitions_required().push_back(decl_inst_id);
}

return {function_decl.function_id, decl_id};
return {function_decl.function_id, decl_inst_id};
}

auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool {
Expand Down
10 changes: 8 additions & 2 deletions toolchain/check/handle_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ auto HandleParseNode(Context& context, Parse::ImplIntroducerId node_id)
// consistent to imagine that it does. This also gives us a scope for implicit
// parameters.
context.decl_name_stack().PushScopeAndStartName();

// Push a pattern block for the signature.
context.pattern_block_stack().Push();

return true;
}

Expand Down Expand Up @@ -184,9 +188,12 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
context.node_stack().PopExprWithNodeId();
auto [self_type_node, self_type_id] =
context.node_stack().PopWithNodeId<Parse::NodeCategory::ImplAs>();
SemIR::InstBlockId pattern_block_id = context.pattern_block_stack().Pop();
auto [params_node, params_id] =
context.node_stack().PopWithNodeIdIf<Parse::NodeKind::ImplForall>();
auto decl_block_id = context.inst_block_stack().Pop();
SemIR::DeclId decl_id = context.sem_ir().decls().Add(
{.pattern_block_id = pattern_block_id, .decl_block_id = decl_block_id});
context.node_stack().PopForSoloNodeId<Parse::NodeKind::ImplIntroducer>();

// Convert the constraint expression to a type.
Expand Down Expand Up @@ -214,8 +221,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
// TODO: Does lookup in an impl file need to look for a prior impl declaration
// in the api file?
auto impl_id = context.impls().LookupOrAdd(self_type_id, constraint_type_id);
SemIR::ImplDecl impl_decl = {.impl_id = impl_id,
.decl_block_id = decl_block_id};
SemIR::ImplDecl impl_decl = {.impl_id = impl_id, .decl_id = decl_id};
auto impl_decl_id = context.AddInst(node_id, impl_decl);

// For an `extend impl` declaration, mark the impl as extending this `impl`.
Expand Down
7 changes: 6 additions & 1 deletion toolchain/check/handle_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ auto HandleParseNode(Context& context, Parse::InterfaceIntroducerId node_id)
context.decl_name_stack().PushScopeAndStartName();
// This interface is potentially generic.
StartGenericDecl(context);
// Push a pattern block for the signature.
context.pattern_block_stack().Push();
return true;
}

Expand All @@ -45,11 +47,14 @@ static auto BuildInterfaceDecl(Context& context,
CheckAccessModifiersOnDecl(context, introducer, parent_scope_inst);
LimitModifiersOnDecl(context, introducer, KeywordModifierSet::Access);

SemIR::InstBlockId pattern_block_id = context.pattern_block_stack().Pop();
auto decl_block_id = context.inst_block_stack().Pop();
SemIR::DeclId decl_id = context.sem_ir().decls().Add(
{.pattern_block_id = pattern_block_id, .decl_block_id = decl_block_id});

// Add the interface declaration.
auto interface_decl = SemIR::InterfaceDecl{
SemIR::TypeId::TypeType, SemIR::InterfaceId::Invalid, decl_block_id};
SemIR::TypeId::TypeType, SemIR::InterfaceId::Invalid, decl_id};
auto interface_decl_id =
context.AddPlaceholderInst(SemIR::LocIdAndInst(node_id, interface_decl));

Expand Down
8 changes: 7 additions & 1 deletion toolchain/check/handle_let_and_var.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ namespace Carbon::Check {
template <Lex::TokenKind::RawEnumType Kind>
static auto HandleIntroducer(Context& context, Parse::NodeId node_id) -> bool {
context.decl_introducer_state_stack().Push<Kind>();
// Push a bracketing node to establish the pattern context.
// Push a bracketing node and pattern block to establish the pattern context.
context.node_stack().Push(node_id);
context.pattern_block_stack().Push();
return true;
}

Expand Down Expand Up @@ -145,6 +146,11 @@ template <const Lex::TokenKind& IntroducerTokenKind,
static auto HandleDecl(Context& context, NodeT node_id)
-> std::optional<DeclInfo> {
std::optional<DeclInfo> decl_info = DeclInfo();

// TODO: update binding-pattern handling to use the pattern block even in
// a let/var context, and then consume it here.
(void)context.pattern_block_stack().Pop();

// Handle the optional initializer.
if (context.node_stack().PeekNextIs<InitializerNodeKind>()) {
decl_info->init_id = context.node_stack().PopExpr();
Expand Down
4 changes: 4 additions & 0 deletions toolchain/check/handle_namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ auto HandleParseNode(Context& context, Parse::NamespaceStartId /*node_id*/)
// Optional modifiers and the name follow.
context.decl_introducer_state_stack().Push<Lex::TokenKind::Namespace>();
context.decl_name_stack().PushScopeAndStartName();
context.pattern_block_stack().Push();
return true;
}

auto HandleParseNode(Context& context, Parse::NamespaceId node_id) -> bool {
// TODO: consume this when/if parameterized namespaces are supported.
(void)context.pattern_block_stack().Pop();

auto name_context = context.decl_name_stack().FinishName(
PopNameComponentWithoutParams(context, Lex::TokenKind::Namespace));

Expand Down
Loading