Skip to content

Commit

Permalink
Initial support for binding patterns in SemIR (#4221)
Browse files Browse the repository at this point in the history
Introduces the `BindingPattern` and `SymbolicBindingPattern` insts, and
a separate stack of pattern blocks that they are emitted into. The
intent is to generate the corresponding pattern-matching insts (like
`BindName`) from them in a separate pass, but that is deferred to future
PRs.

See
[here](https://docs.google.com/document/d/1U_vQH17V893J9aF1LJXUnFYBNSs2MjKl4bJPaWCB2zo/edit?usp=sharing&resourcekey=0-w0xGYZ0An31Kpz-wvzSXwQ)
for the design this is based on, but note that during review we have
chosen to deviate from that design by putting the patterns in separate
blocks, and omitting the "forward references" from a `BindingPattern` to
its corresponding `BindName`. This in turn necessitates having separate
inst kinds for symbolic and non-symbolic binding patterns.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
  • Loading branch information
geoffromer and jonmeow authored Sep 25, 2024
1 parent 87678cc commit dc32aa2
Show file tree
Hide file tree
Showing 531 changed files with 10,270 additions and 8,119 deletions.
10 changes: 10 additions & 0 deletions toolchain/check/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,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_("pattern_block_stack_", sem_ir, vlog_stream),
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 @@ -82,6 +83,7 @@ auto Context::VerifyOnFinish() -> void {
// node_stack_ will still contain top-level entities.
scope_stack_.VerifyOnFinish();
inst_block_stack_.VerifyOnFinish();
pattern_block_stack_.VerifyOnFinish();
param_and_arg_refs_stack_.VerifyOnFinish();
}

Expand Down Expand Up @@ -177,6 +179,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 = AddInstInNoBlock(loc_id_and_inst);
pattern_block_stack_.AddInstId(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 Expand Up @@ -1285,6 +1294,7 @@ auto Context::PrintForStackDump(llvm::raw_ostream& output) const -> void {
SemIR::Formatter formatter(*tokens_, *parse_tree_, *sem_ir_);
node_stack_.PrintForStackDump(formatter, Indent, output);
inst_block_stack_.PrintForStackDump(formatter, Indent, output);
pattern_block_stack_.PrintForStackDump(formatter, Indent, output);
param_and_arg_refs_stack_.PrintForStackDump(formatter, Indent, output);
args_type_info_stack_.PrintForStackDump(formatter, Indent, output);
}
Expand Down
16 changes: 16 additions & 0 deletions toolchain/check/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,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 @@ -405,6 +417,7 @@ class Context {
auto node_stack() -> NodeStack& { return node_stack_; }

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

auto param_and_arg_refs_stack() -> ParamAndArgRefsStack& {
return param_and_arg_refs_stack_;
Expand Down Expand Up @@ -547,6 +560,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.
InstBlockStack pattern_block_stack_;

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

Expand Down
1 change: 1 addition & 0 deletions toolchain/check/decl_name_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class DeclNameStack {
.generic_id = SemIR::GenericId::Invalid,
.first_param_node_id = name.first_param_node_id,
.last_param_node_id = name.last_param_node_id,
.pattern_block_id = name.pattern_block_id,
.implicit_param_refs_id = name.implicit_params_id,
.param_refs_id = name.params_id,
.is_extern = is_extern,
Expand Down
2 changes: 2 additions & 0 deletions toolchain/check/eval.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,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 @@ -1448,6 +1449,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
1 change: 1 addition & 0 deletions toolchain/check/global_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ auto GlobalInit::Finalize() -> void {
.generic_id = SemIR::GenericId::Invalid,
.first_param_node_id = Parse::NodeId::Invalid,
.last_param_node_id = Parse::NodeId::Invalid,
.pattern_block_id = SemIR::InstBlockId::Empty,
.implicit_param_refs_id = SemIR::InstBlockId::Invalid,
.param_refs_id = SemIR::InstBlockId::Empty,
.is_extern = false,
Expand Down
5 changes: 5 additions & 0 deletions toolchain/check/handle_alias.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ auto HandleParseNode(Context& context, Parse::AliasIntroducerId /*node_id*/)
-> bool {
context.decl_introducer_state_stack().Push<Lex::TokenKind::Alias>();
context.decl_name_stack().PushScopeAndStartName();

// Push a pattern block to handle parameters of the alias declaration.
// TODO: Disallow these in parse, instead of check, so we don't have to do
// this.
context.pattern_block_stack().Push();
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 @@ -167,6 +167,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
4 changes: 4 additions & 0 deletions toolchain/check/handle_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ auto HandleParseNode(Context& context, Parse::ClassIntroducerId node_id)
context.decl_name_stack().PushScopeAndStartName();
// This class is potentially generic.
StartGenericDecl(context);
// Push a pattern block for the signature (if any) of the first NameComponent.
// TODO: Instead use a separate parse node kind for an identifier that's
// followed by a pattern, and push a pattern block when handling it.
context.pattern_block_stack().Push();
return true;
}

Expand Down
3 changes: 3 additions & 0 deletions toolchain/check/handle_export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ 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;
}

Expand Down
5 changes: 3 additions & 2 deletions toolchain/check/handle_function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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 @@ -213,8 +215,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 @@ -260,6 +260,7 @@ static auto BuildFunctionDecl(Context& context,
}

// Add the function declaration.
auto decl_block_id = context.inst_block_stack().Pop();
auto function_decl = SemIR::FunctionDecl{
SemIR::TypeId::Invalid, SemIR::FunctionId::Invalid, decl_block_id};
auto decl_id =
Expand Down
8 changes: 8 additions & 0 deletions toolchain/check/handle_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ 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 of the `forall` (if any).
// TODO: Instead use a separate parse node kinds for `impl` and `impl forall`,
// and only push a pattern block in `forall` case.
context.pattern_block_stack().Push();

return true;
}

Expand Down Expand Up @@ -213,6 +219,8 @@ 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);
context.impls().Get(impl_id).pattern_block_id =
context.pattern_block_stack().Pop();
SemIR::ImplDecl impl_decl = {.impl_id = impl_id,
.decl_block_id = decl_block_id};
auto impl_decl_id = context.AddInst(node_id, impl_decl);
Expand Down
4 changes: 4 additions & 0 deletions toolchain/check/handle_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ 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 (if any) of the first NameComponent.
// TODO: Instead use a separate parse node kind for an identifier that's
// followed by a pattern, and push a pattern block when handling it.
context.pattern_block_stack().Push();
return true;
}

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.
context.pattern_block_stack().PopAndDiscard();

// 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_name.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ auto HandleParseNode(Context& context, Parse::SelfValueNameExprId node_id)
auto HandleParseNode(Context& context, Parse::NameQualifierId /*node_id*/)
-> bool {
context.decl_name_stack().ApplyNameQualifier(PopNameComponent(context));
// Push a pattern block for the signature (if any) of the next NameComponent.
// TODO: Instead use a separate parse node kind for an identifier that's
// followed by a pattern, and push a pattern block when handling it.
context.pattern_block_stack().Push();
return true;
}

Expand Down
5 changes: 5 additions & 0 deletions toolchain/check/handle_namespace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ 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();

// Push a pattern block to handle parameters of the namespace declaration.
// TODO: Disallow these in parse, instead of check, so we don't have to do
// this.
context.pattern_block_stack().Push();
return true;
}

Expand Down
1 change: 1 addition & 0 deletions toolchain/check/import_ref.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ class ImportRefResolver {
.generic_id = MakeIncompleteGeneric(decl_id, import_base.generic_id),
.first_param_node_id = Parse::NodeId::Invalid,
.last_param_node_id = Parse::NodeId::Invalid,
.pattern_block_id = SemIR::InstBlockId::Invalid,
.implicit_param_refs_id = import_base.implicit_param_refs_id.is_valid()
? SemIR::InstBlockId::Empty
: SemIR::InstBlockId::Invalid,
Expand Down
1 change: 1 addition & 0 deletions toolchain/check/name_component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ auto PopNameComponent(Context& context) -> NameComponent {
implicit_params_id.value_or(SemIR::InstBlockId::Invalid),
.params_loc_id = params_loc_id,
.params_id = params_id.value_or(SemIR::InstBlockId::Invalid),
.pattern_block_id = context.pattern_block_stack().Pop(),
};
}

Expand Down
9 changes: 6 additions & 3 deletions toolchain/check/name_component.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ struct NameComponent {
// The explicit parameter list.
Parse::NodeId params_loc_id;
SemIR::InstBlockId params_id;

// The pattern block.
SemIR::InstBlockId pattern_block_id;
};

// Pop a name component from the node stack.
// Pop a name component from the node stack and pattern block stack.
auto PopNameComponent(Context& context) -> NameComponent;

// Pop the name of a declaration from the node stack, and diagnose if it has
// parameters.
// Pop the name of a declaration from the node stack and pattern block stack,
// and diagnose if it has parameters.
auto PopNameComponentWithoutParams(Context& context, Lex::TokenKind introducer)
-> NameComponent;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ let d: c = {};
// CHECK:STDOUT: .c = %c
// CHECK:STDOUT: .d = @__global_init.%d
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [template = constants.%C]
// CHECK:STDOUT: %a: type = bind_alias a, %C.decl [template = constants.%C]
// CHECK:STDOUT: %a.ref: type = name_ref a, %a [template = constants.%C]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ var d: D* = &c;
// CHECK:STDOUT: .C = %C.decl
// CHECK:STDOUT: .D = %D
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [template = constants.%C]
// CHECK:STDOUT: %D: type = bind_alias D, %C.decl [template = constants.%C]
// CHECK:STDOUT: }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ let c_var: c = d;
// CHECK:STDOUT: .d = %d
// CHECK:STDOUT: .c_var = @__global_init.%c_var
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {}
// CHECK:STDOUT: %D.decl: type = class_decl @D [template = constants.%D] {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: %D.decl: type = class_decl @D [template = constants.%D] {} {}
// CHECK:STDOUT: %C.ref: type = name_ref C, %C.decl [template = constants.%C]
// CHECK:STDOUT: %c: type = bind_alias c, %C.decl [template = constants.%C]
// CHECK:STDOUT: %D.ref: type = name_ref D, %D.decl [template = constants.%D]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ fn F() -> {} {
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %NS: <namespace> = namespace [template] {}
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {
// CHECK:STDOUT: %.loc13_12.1: %.1 = struct_literal ()
// CHECK:STDOUT: %.loc13_12.2: type = converted %.loc13_12.1, constants.%.1 [template = constants.%.1]
// CHECK:STDOUT: @F.%return: ref %.1 = var <return slot>
// CHECK:STDOUT: %return: ref %.1 = var <return slot>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ extern alias C = Class;
// CHECK:STDOUT: .B = %B
// CHECK:STDOUT: .C = %C
// CHECK:STDOUT: }
// CHECK:STDOUT: %Class.decl: type = class_decl @Class [template = constants.%Class] {}
// CHECK:STDOUT: %Class.decl: type = class_decl @Class [template = constants.%Class] {} {}
// CHECK:STDOUT: %Class.ref.loc38: type = name_ref Class, %Class.decl [template = constants.%Class]
// CHECK:STDOUT: %A: type = bind_alias A, %Class.decl [template = constants.%Class]
// CHECK:STDOUT: %Class.ref.loc44: type = name_ref Class, %Class.decl [template = constants.%Class]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ alias b = C;
// CHECK:STDOUT: .a = %a.loc13
// CHECK:STDOUT: .b = %b
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: %C.ref.loc13: type = name_ref C, %C.decl [template = constants.%C]
// CHECK:STDOUT: %a.loc13: type = bind_alias a, %C.decl [template = constants.%C]
// CHECK:STDOUT: %C.ref.loc21: type = name_ref C, %C.decl [template = constants.%C]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn F() {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {}
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
Expand Down
6 changes: 3 additions & 3 deletions toolchain/check/testdata/alias/no_prelude/fail_params.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ alias A(T:! type) = T*;
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .A = %A
// CHECK:STDOUT: }
// CHECK:STDOUT: %T.loc18_9.1: type = param T, runtime_param<invalid>
// CHECK:STDOUT: %T.loc18_9.2: type = bind_symbolic_name T 0, %T.loc18_9.1 [symbolic = constants.%T]
// CHECK:STDOUT: %T.ref: type = name_ref T, %T.loc18_9.2 [symbolic = constants.%T]
// CHECK:STDOUT: %T.param: type = param T, runtime_param<invalid>
// CHECK:STDOUT: %T: type = bind_symbolic_name T 0, %T.param [symbolic = constants.%T]
// CHECK:STDOUT: %T.ref: type = name_ref T, %T [symbolic = constants.%T]
// CHECK:STDOUT: %.loc18: type = ptr_type %T [symbolic = constants.%.1]
// CHECK:STDOUT: %A: <error> = bind_alias A, <error> [template = <error>]
// CHECK:STDOUT: }
Expand Down
2 changes: 1 addition & 1 deletion toolchain/check/testdata/alias/no_prelude/import.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ var c: () = a_alias_alias;
// CHECK:STDOUT: .c_alias = %c_alias
// CHECK:STDOUT: .a = %a
// CHECK:STDOUT: }
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {}
// CHECK:STDOUT: %C.decl: type = class_decl @C [template = constants.%C] {} {}
// CHECK:STDOUT: %C.ref.loc6: type = name_ref C, %C.decl [template = constants.%C]
// CHECK:STDOUT: %c_alias: type = bind_alias c_alias, %C.decl [template = constants.%C]
// CHECK:STDOUT: %C.ref.loc8: type = name_ref C, %C.decl [template = constants.%C]
Expand Down
Loading

0 comments on commit dc32aa2

Please sign in to comment.