Implement static var class fields (#7215)

This adds the `static` token to the lexer and parses it as a modifier.

In check, `FullPatternStack::Kind::FieldDecl` is now used for both
static and non-static vars. Static vars get treated basically the same
as `NameBindingDecl`s.

Global initialization is used for static var initializers. To make the
necessary stack information available to `pattern_match.cpp`, the
`full_pattern_stack` and `decl_introducer_state_stack` are now popped
later in `handle_let_and_var.cpp`.

In lowering, each class's body is checked for `VarStorage` insts and
lowered the same as global vars.
This commit is contained in:
Nicholas Bishop
2026-05-18 20:09:17 +00:00
committed by GitHub
parent 31db4a0931
commit 773ecdfac6
21 changed files with 380 additions and 69 deletions
+14
View File
@@ -357,4 +357,18 @@ auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id,
class_info.complete_type_witness_id = complete_type_witness_id;
}
auto InNonStaticFieldDecl(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
!context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
auto InStaticClassScopeVar(Context& context) -> bool {
return context.full_pattern_stack().IsCurrentKindClassScopeVarDecl() &&
context.decl_introducer_state_stack()
.innermost()
.modifier_set.HasAnyOf(KeywordModifierSet::Static);
}
} // namespace Carbon::Check
+6
View File
@@ -23,6 +23,12 @@ auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id,
llvm::ArrayRef<SemIR::InstId> vtable_contents,
llvm::ArrayRef<SemIR::InstId> body) -> void;
// Whether a non-static field decl is currently being checked.
auto InNonStaticFieldDecl(Context& context) -> bool;
// Whether a static class var decl is currently being checked.
auto InStaticClassScopeVar(Context& context) -> bool;
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_CLASS_H_
+1 -1
View File
@@ -31,7 +31,7 @@ struct DeclIntroducerState {
Parse::NodeId
ordered_modifier_node_ids[static_cast<int8_t>(ModifierOrder::Last) + 1] =
{Parse::NodeId::None, Parse::NodeId::None, Parse::NodeId::None,
Parse::NodeId::None, Parse::NodeId::None};
Parse::NodeId::None, Parse::NodeId::None, Parse::NodeId::None};
// Invariant: contains just the modifiers represented by `saw_*_modifier`.
KeywordModifierSet modifier_set = KeywordModifierSet();
+10 -11
View File
@@ -40,8 +40,8 @@ class FullPatternStack {
// A name-binding declaration, such as a `let` or `var` statement.
NameBindingDecl,
// A non-static `var` field declaration inside a class.
FieldDecl,
// A `var` field declaration inside a class.
ClassScopeVarDecl,
// The implicit parameter list of a function or impl declaration.
ImplicitParamList,
@@ -65,10 +65,9 @@ class FullPatternStack {
// The kind of the current full-pattern.
auto CurrentKind() const -> Kind { return kind_stack_.back(); }
// Whether the kind of the current full-pattern is a non-static class
// `var` decl.
auto IsCurrentKindFieldDecl() -> bool {
return !empty() && CurrentKind() == Kind::FieldDecl;
// Whether the kind of the current full-pattern is a class `var` decl.
auto IsCurrentKindClassScopeVarDecl() -> bool {
return !empty() && CurrentKind() == Kind::ClassScopeVarDecl;
}
// Marks the start of a new full-pattern for a parameterized entity
@@ -85,10 +84,9 @@ class FullPatternStack {
bind_name_stack_.PushArray();
}
// Marks the start of a new full-pattern for a non-staitc `var` field
// declaration.
auto PushFieldDecl() -> void {
kind_stack_.push_back(Kind::FieldDecl);
// Marks the start of a new full-pattern for a class `var` declaration.
auto PushClassScopeVarDecl() -> void {
kind_stack_.push_back(Kind::ClassScopeVarDecl);
bind_name_stack_.PushArray();
}
@@ -126,7 +124,8 @@ class FullPatternStack {
// Marks the start of the initializer for the current name binding decl.
auto StartPatternInitializer() -> void {
CARBON_CHECK(kind_stack_.back() == Kind::NameBindingDecl);
CARBON_CHECK(kind_stack_.back() == Kind::ClassScopeVarDecl ||
kind_stack_.back() == Kind::NameBindingDecl);
for (auto& [name_id, inst_id] : bind_name_stack_.PeekArray()) {
CARBON_CHECK(inst_id == SemIR::InstId::InitTombstone);
auto& lookup_result = lookup_->Get(name_id);
+6
View File
@@ -4,6 +4,7 @@
#include "toolchain/check/global_init.h"
#include "toolchain/check/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/inst.h"
@@ -57,4 +58,9 @@ auto GlobalInit::Finalize() -> void {
.body_block_ids = {SemIR::InstBlockId::GlobalInit}}}));
}
auto UseGlobalInit(Context& context) -> bool {
return context.scope_stack().PeekIndex() == ScopeIndex::Package ||
InStaticClassScopeVar(context);
}
} // namespace Carbon::Check
+6
View File
@@ -42,6 +42,12 @@ class GlobalInit {
llvm::SmallVector<SemIR::InstId> block_;
};
// Whether to use global initialization for the initializer of a declaration.
//
// This returns true for a declaration in package scope, or for a static
// class field.
auto UseGlobalInit(Context& context) -> bool;
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_GLOBAL_INIT_H_
+1 -1
View File
@@ -340,7 +340,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
}
case FullPatternStack::Kind::NameBindingDecl:
case FullPatternStack::Kind::FieldDecl: {
case FullPatternStack::Kind::ClassScopeVarDecl: {
if (node_kind == Parse::NodeKind::FormBindingPattern) {
return context.TODO(node_id, "support local form bindings");
}
+36 -23
View File
@@ -70,7 +70,7 @@ static auto HandleIntroducer(Context& context, Parse::NodeId node_id) -> bool {
context.pattern_block_stack().Push();
if (context.scope_stack().TryGetCurrentScopeAs<SemIR::ClassDecl>() &&
Kind == Lex::TokenKind::Var) {
context.full_pattern_stack().PushFieldDecl();
context.full_pattern_stack().PushClassScopeVarDecl();
} else {
context.full_pattern_stack().PushNameBindingDecl();
}
@@ -120,10 +120,18 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
context, node_id,
{.type_id = type_id, .subpattern_id = subpattern_id});
break;
case FullPatternStack::Kind::FieldDecl:
// For class fields, a `FieldDecl` has already been created; do
// not create a var pattern.
return true;
case FullPatternStack::Kind::ClassScopeVarDecl:
if (InStaticClassScopeVar(context)) {
// Handle static class fields the same as NameBindingDecls.
pattern_id = AddInst<SemIR::VarPattern>(
context, node_id,
{.type_id = type_id, .subpattern_id = subpattern_id});
break;
} else {
// For non-static class fields, a `FieldDecl` has already been
// created; do not create a var pattern.
return true;
}
case FullPatternStack::Kind::NotInEitherParamList:
CARBON_FATAL("Unreachable");
}
@@ -146,9 +154,9 @@ static auto EndFullPattern(Context& context) -> void {
}
auto pattern_block_id = context.pattern_block_stack().Pop();
// For class fields, a `FieldDecl` has been created; skip creating a
// name binding and var storage.
if (context.full_pattern_stack().IsCurrentKindFieldDecl()) {
// For non-static class fields, a `FieldDecl` has been created; skip
// creating a name binding and var storage.
if (InNonStaticFieldDecl(context)) {
return;
}
@@ -163,7 +171,7 @@ static auto EndFullPattern(Context& context) -> void {
}
static auto StartPatternInitializer(Context& context) -> bool {
if (context.scope_stack().PeekIndex() == ScopeIndex::Package) {
if (UseGlobalInit(context)) {
context.global_init().Resume();
}
context.full_pattern_stack().StartPatternInitializer();
@@ -171,7 +179,7 @@ static auto StartPatternInitializer(Context& context) -> bool {
}
static auto EndPatternInitializer(Context& context) -> void {
if (context.scope_stack().PeekIndex() == ScopeIndex::Package) {
if (UseGlobalInit(context)) {
context.global_init().Suspend();
}
context.full_pattern_stack().EndPatternInitializer();
@@ -202,7 +210,7 @@ auto HandleParseNode(Context& context,
auto HandleParseNode(Context& context, Parse::VariableInitializerId node_id)
-> bool {
if (context.full_pattern_stack().IsCurrentKindFieldDecl()) {
if (InNonStaticFieldDecl(context)) {
context.TODO(node_id, "Field initializer");
return false;
}
@@ -259,7 +267,7 @@ template <const Lex::TokenKind& IntroducerTokenKind,
const Parse::NodeKind& InitializerNodeKind>
static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
DeclInfo decl_info = DeclInfo();
bool is_field_decl = context.full_pattern_stack().IsCurrentKindFieldDecl();
bool in_non_static_field_decl = InNonStaticFieldDecl(context);
// Handle the optional initializer.
if (context.node_stack().PeekNextIs(InitializerNodeKind)) {
@@ -281,7 +289,7 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
// A non-class variable declaration without an explicit initializer
// is initialized by calling `(T as Core.DefaultOrUnformed).Op()`.
if (!is_field_decl) {
if (!in_non_static_field_decl) {
if constexpr (IntroducerNodeKind == Parse::NodeKind::VariableIntroducer) {
StartPatternInitializer(context);
decl_info.init_id = MakeDefaultInit(context, node_id,
@@ -290,9 +298,8 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
}
}
}
context.full_pattern_stack().PopFullPattern();
if (!is_field_decl) {
if (!in_non_static_field_decl) {
decl_info.pattern_id = context.node_stack().PopPattern();
}
@@ -305,8 +312,7 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
context.name_scopes()
.GetInstIfValid(context.scope_stack().PeekNameScopeId())
.second;
decl_info.introducer =
context.decl_introducer_state_stack().Pop<IntroducerTokenKind>();
decl_info.introducer = context.decl_introducer_state_stack().innermost();
CheckAccessModifiersOnDecl(context, decl_info.introducer, parent_scope_inst);
return decl_info;
@@ -316,6 +322,8 @@ auto HandleParseNode(Context& context, Parse::LetDeclId node_id) -> bool {
auto decl_info =
HandleDecl<Lex::TokenKind::Let, Parse::NodeKind::LetIntroducer,
Parse::NodeKind::LetInitializer>(context, node_id);
context.full_pattern_stack().PopFullPattern();
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Let>();
LimitModifiersOnDecl(
context, decl_info.introducer,
@@ -345,6 +353,8 @@ auto HandleParseNode(Context& context, Parse::AssociatedConstantDeclId node_id)
Parse::NodeKind::AssociatedConstantIntroducer,
Parse::NodeKind::AssociatedConstantInitializer>(
context, node_id);
context.full_pattern_stack().PopFullPattern();
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Let>();
LimitModifiersOnDecl(
context, decl_info.introducer,
@@ -397,15 +407,18 @@ auto HandleParseNode(Context& context, Parse::VariableDeclId node_id) -> bool {
HandleDecl<Lex::TokenKind::Var, Parse::NodeKind::VariableIntroducer,
Parse::NodeKind::VariableInitializer>(context, node_id);
LimitModifiersOnDecl(
context, decl_info.introducer,
KeywordModifierSet::Access | KeywordModifierSet::Returned);
LimitModifiersOnDecl(context, decl_info.introducer,
KeywordModifierSet::Access |
KeywordModifierSet::Returned |
KeywordModifierSet::Static);
if (context.scope_stack().TryGetCurrentScopeAs<SemIR::ClassDecl>()) {
return true;
if (!InNonStaticFieldDecl(context)) {
LocalPatternMatch(context, decl_info.pattern_id, decl_info.init_id);
}
LocalPatternMatch(context, decl_info.pattern_id, decl_info.init_id);
context.full_pattern_stack().PopFullPattern();
context.decl_introducer_state_stack().Pop<Lex::TokenKind::Var>();
return true;
}
+5 -1
View File
@@ -47,7 +47,8 @@ static auto HandleModifier(Context& context, Parse::NodeId node_id,
if (keyword.HasAnyOf(KeywordModifierSet::Access)) {
order = ModifierOrder::Access;
later_modifiers = KeywordModifierSet::Extern | KeywordModifierSet::Decl |
KeywordModifierSet::Evaluation;
KeywordModifierSet::Evaluation |
KeywordModifierSet::Static;
} else if (keyword.HasAnyOf(KeywordModifierSet::Extern)) {
order = ModifierOrder::Extern;
later_modifiers = KeywordModifierSet::Decl | KeywordModifierSet::Evaluation;
@@ -60,6 +61,9 @@ static auto HandleModifier(Context& context, Parse::NodeId node_id,
} else if (keyword.HasAnyOf(KeywordModifierSet::Evaluation)) {
order = ModifierOrder::Evaluation;
later_modifiers = KeywordModifierSet::None;
} else if (keyword.HasAnyOf(KeywordModifierSet::Static)) {
order = ModifierOrder::Static;
later_modifiers = KeywordModifierSet::None;
} else {
CARBON_FATAL("Unexpected modifier keyword.");
}
+2 -1
View File
@@ -2,6 +2,7 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/class.h"
#include "toolchain/check/context.h"
#include "toolchain/check/handle.h"
#include "toolchain/check/inst.h"
@@ -96,7 +97,7 @@ auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool {
llvm::SmallVector<SemIR::InstId> type_inst_ids;
type_inst_ids.reserve(inst_block.size());
for (auto inst : inst_block) {
if (context.full_pattern_stack().IsCurrentKindFieldDecl()) {
if (InNonStaticFieldDecl(context)) {
CARBON_DIAGNOSTIC(FieldWithTuplePattern, Error,
"found tuple pattern in class `var` decl");
context.emitter().Emit(LocIdForDiagnostics::TokenOnly(node_id),
+9 -5
View File
@@ -19,6 +19,7 @@ enum class ModifierOrder : int8_t {
Extern,
Extend,
Decl,
Static,
Evaluation,
Last = Evaluation
};
@@ -49,6 +50,9 @@ enum class ModifierOrder : int8_t {
X(Returned) \
X(Virtual) \
\
/* Static is standalone. */ \
X(Static) \
\
/* Eval and MustEval are mutually exclusive. */ \
X(Eval) \
X(MustEval)
@@ -158,11 +162,11 @@ static_assert(
.HasAnyOf(KeywordModifierSet::Evaluation),
"Order-related sets must not overlap");
#define CARBON_KEYWORD_MODIFIER_SET_IN_GROUP(Modifier) \
static_assert((KeywordModifierSet::Access | KeywordModifierSet::Extern | \
KeywordModifierSet::Extend | KeywordModifierSet::Decl | \
KeywordModifierSet::Evaluation) \
.HasAnyOf(KeywordModifierSet::Modifier), \
#define CARBON_KEYWORD_MODIFIER_SET_IN_GROUP(Modifier) \
static_assert((KeywordModifierSet::Access | KeywordModifierSet::Extern | \
KeywordModifierSet::Extend | KeywordModifierSet::Decl | \
KeywordModifierSet::Evaluation | KeywordModifierSet::Static) \
.HasAnyOf(KeywordModifierSet::Modifier), \
"Modifier missing from all modifier sets: " #Modifier);
CARBON_KEYWORD_MODIFIER_SET(CARBON_KEYWORD_MODIFIER_SET_IN_GROUP)
#undef CARBON_KEYWORD_MODIFIER_SET_IN_GROUP
+2
View File
@@ -61,6 +61,8 @@ static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet {
return KeywordModifierSet::Extend;
case ModifierOrder::Decl:
return KeywordModifierSet::Decl;
case ModifierOrder::Static:
return KeywordModifierSet::Static;
case ModifierOrder::Evaluation:
return KeywordModifierSet::Evaluation;
}
+2 -2
View File
@@ -136,8 +136,8 @@ auto AddBindingPattern(Context& context, SemIR::LocId name_loc,
}
auto type_id = SemIR::ExtractScrutineeType(context.sem_ir(), pattern.type_id);
// Handle `var` decls in a class by creating a `FieldDecl`.
if (context.full_pattern_stack().IsCurrentKindFieldDecl()) {
// Handle non-static `var` decls in a class by creating a `FieldDecl`.
if (InNonStaticFieldDecl(context)) {
auto class_decl =
context.scope_stack().TryGetCurrentScopeAs<SemIR::ClassDecl>();
auto name_id = context.entity_names().Get(pattern.entity_name_id).name_id;
+2 -2
View File
@@ -605,7 +605,7 @@ auto MatchContext::DoVarPreWorkImpl(State state,
// TODO: Find a more efficient way to put these insts in the global_init
// block (or drop the distinction between the global_init block and the
// file scope?)
if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) {
if (UseGlobalInit(context_)) {
context_.global_init().Resume();
}
@@ -625,7 +625,7 @@ auto MatchContext::DoVarPreWorkImpl(State state,
{.lhs_id = storage_id, .rhs_id = init_id});
}
if (context_.scope_stack().PeekIndex() == ScopeIndex::Package) {
if (UseGlobalInit(context_)) {
context_.global_init().Suspend();
}
return storage_id;
+164
View File
@@ -0,0 +1,164 @@
// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/class/field/static.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/class/field/static.carbon
// --- static_field.carbon
library "[[@TEST_NAME]]";
//@dump-sem-ir-begin
class C {
static var static_field: i32;
static var static_field_with_init: i32 = 123;
}
fn F() {
C.static_field = 456;
}
//@dump-sem-ir-end
// --- private_static_field.carbon
library "[[@TEST_NAME]]";
class C {
private static var static_field: i32;
fn F() {
Self.static_field = 123;
}
}
// --- fail_private_static_field_invalid_access.carbon
library "[[@TEST_NAME]]";
class C {
private static var static_field: i32;
}
fn F() {
// CHECK:STDERR: fail_private_static_field_invalid_access.carbon:[[@LINE+7]]:3: error: cannot access private member `static_field` of type `C` [ClassInvalidMemberAccess]
// CHECK:STDERR: C.static_field = 123;
// CHECK:STDERR: ^~~~~~~~~~~~~~
// CHECK:STDERR: fail_private_static_field_invalid_access.carbon:[[@LINE-7]]:22: note: declared here [ClassMemberDeclaration]
// CHECK:STDERR: private static var static_field: i32;
// CHECK:STDERR: ^~~~~~~~~~~~~~~~~
// CHECK:STDERR:
C.static_field = 123;
}
// CHECK:STDOUT: --- static_field.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %C: type = class_type @C [concrete]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
// CHECK:STDOUT: %pattern_type.7ce: type = pattern_type %i32 [concrete]
// CHECK:STDOUT: %DefaultOrUnformed.type: type = facet_type <@DefaultOrUnformed> [concrete]
// CHECK:STDOUT: %T.67d: type = symbolic_binding T, 0 [symbolic]
// CHECK:STDOUT: %T.as.DefaultOrUnformed.impl.Op.type.462: type = fn_type @T.as.DefaultOrUnformed.impl.Op, @T.as.DefaultOrUnformed.impl(%T.67d) [symbolic]
// CHECK:STDOUT: %T.as.DefaultOrUnformed.impl.Op.b80: %T.as.DefaultOrUnformed.impl.Op.type.462 = struct_value () [symbolic]
// CHECK:STDOUT: %DefaultOrUnformed.impl_witness.89f: <witness> = impl_witness imports.%DefaultOrUnformed.impl_witness_table.725, @T.as.DefaultOrUnformed.impl(%i32) [concrete]
// CHECK:STDOUT: %DefaultOrUnformed.facet: %DefaultOrUnformed.type = facet_value %i32, (%DefaultOrUnformed.impl_witness.89f) [concrete]
// CHECK:STDOUT: %int_123.fff: Core.IntLiteral = int_value 123 [concrete]
// CHECK:STDOUT: %ImplicitAs.type.e8c: type = facet_type <@ImplicitAs, @ImplicitAs(%i32)> [concrete]
// CHECK:STDOUT: %To: Core.IntLiteral = symbolic_binding To, 0 [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f67: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%To) [symbolic]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.8fd: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f67 = struct_value () [symbolic]
// CHECK:STDOUT: %ImplicitAs.impl_witness.ac5: <witness> = impl_witness imports.%ImplicitAs.impl_witness_table.82d, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.a56: type = fn_type @Core.IntLiteral.as.ImplicitAs.impl.Convert, @Core.IntLiteral.as.ImplicitAs.impl(%int_32) [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a: %Core.IntLiteral.as.ImplicitAs.impl.Convert.type.a56 = struct_value () [concrete]
// CHECK:STDOUT: %ImplicitAs.facet: %ImplicitAs.type.e8c = facet_value Core.IntLiteral, (%ImplicitAs.impl_witness.ac5) [concrete]
// CHECK:STDOUT: %ImplicitAs.WithSelf.Convert.type.b91: type = fn_type @ImplicitAs.WithSelf.Convert, @ImplicitAs.WithSelf(%i32, %ImplicitAs.facet) [concrete]
// CHECK:STDOUT: %.9db: type = fn_type_with_self_type %ImplicitAs.WithSelf.Convert.type.b91, %ImplicitAs.facet [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.e93: <bound method> = bound_method %int_123.fff, %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: <specific function> = specific_function %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a, @Core.IntLiteral.as.ImplicitAs.impl.Convert(%int_32) [concrete]
// CHECK:STDOUT: %bound_method.0ac: <bound method> = bound_method %int_123.fff, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
// CHECK:STDOUT: %int_123.f7f: %i32 = int_value 123 [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
// CHECK:STDOUT: %int_456.010: Core.IntLiteral = int_value 456 [concrete]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.ee2: <bound method> = bound_method %int_456.010, %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a [concrete]
// CHECK:STDOUT: %bound_method.4c0: <bound method> = bound_method %int_456.010, %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn [concrete]
// CHECK:STDOUT: %int_456.d17: %i32 = int_value 456 [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core.import_ref.dba: @T.as.DefaultOrUnformed.impl.%T.as.DefaultOrUnformed.impl.Op.type (%T.as.DefaultOrUnformed.impl.Op.type.462) = import_ref Core//prelude/parts/default, loc{{\d+_\d+}}, loaded [symbolic = @T.as.DefaultOrUnformed.impl.%T.as.DefaultOrUnformed.impl.Op (constants.%T.as.DefaultOrUnformed.impl.Op.b80)]
// CHECK:STDOUT: %DefaultOrUnformed.impl_witness_table.725 = impl_witness_table (%Core.import_ref.dba), @T.as.DefaultOrUnformed.impl [concrete]
// CHECK:STDOUT: %Core.import_ref.70a: @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert.type (%Core.IntLiteral.as.ImplicitAs.impl.Convert.type.f67) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Core.IntLiteral.as.ImplicitAs.impl.%Core.IntLiteral.as.ImplicitAs.impl.Convert (constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.8fd)]
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.82d = impl_witness_table (%Core.import_ref.70a), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: %C.decl: type = class_decl @C [concrete = constants.%C] {} {}
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] {} {}
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: class @C {
// CHECK:STDOUT: name_binding_decl {
// CHECK:STDOUT: %static_field.patt: %pattern_type.7ce = ref_binding_pattern static_field [concrete]
// CHECK:STDOUT: %static_field.var_patt: %pattern_type.7ce = var_pattern %static_field.patt [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT: %static_field.var: ref %i32 = var %static_field.var_patt [concrete]
// CHECK:STDOUT: %i32.loc5: type = type_literal constants.%i32 [concrete = constants.%i32]
// CHECK:STDOUT: %static_field: ref %i32 = ref_binding static_field, %static_field.var [concrete = %static_field.var]
// CHECK:STDOUT: name_binding_decl {
// CHECK:STDOUT: %static_field_with_init.patt: %pattern_type.7ce = ref_binding_pattern static_field_with_init [concrete]
// CHECK:STDOUT: %static_field_with_init.var_patt: %pattern_type.7ce = var_pattern %static_field_with_init.patt [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT: %static_field_with_init.var: ref %i32 = var %static_field_with_init.var_patt [concrete]
// CHECK:STDOUT: %i32.loc6: type = type_literal constants.%i32 [concrete = constants.%i32]
// CHECK:STDOUT: %static_field_with_init: ref %i32 = ref_binding static_field_with_init, %static_field_with_init.var [concrete = %static_field_with_init.var]
// CHECK:STDOUT: %complete_type: <witness> = complete_type_witness constants.%empty_struct_type [concrete = constants.%complete_type.357]
// CHECK:STDOUT: complete_type_witness = %complete_type
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = constants.%C
// CHECK:STDOUT: .static_field = %static_field
// CHECK:STDOUT: .static_field_with_init = %static_field_with_init
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %C.ref: type = name_ref C, file.%C.decl [concrete = constants.%C]
// CHECK:STDOUT: %static_field.ref: ref %i32 = name_ref static_field, @C.%static_field [concrete = @C.%static_field.var]
// CHECK:STDOUT: %int_456: Core.IntLiteral = int_value 456 [concrete = constants.%int_456.010]
// CHECK:STDOUT: %impl.elem0: %.9db = impl_witness_access constants.%ImplicitAs.impl_witness.ac5, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a]
// CHECK:STDOUT: %bound_method.loc10_18.1: <bound method> = bound_method %int_456, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.ee2]
// CHECK:STDOUT: %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
// CHECK:STDOUT: %bound_method.loc10_18.2: <bound method> = bound_method %int_456, %specific_fn [concrete = constants.%bound_method.4c0]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc10_18.2(%int_456) [concrete = constants.%int_456.d17]
// CHECK:STDOUT: %.loc10: init %i32 = converted %int_456, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_456.d17]
// CHECK:STDOUT: assign %static_field.ref, %.loc10
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @__global_init() {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %DefaultOrUnformed.facet: %DefaultOrUnformed.type = facet_value constants.%i32, (constants.%DefaultOrUnformed.impl_witness.89f) [concrete = constants.%DefaultOrUnformed.facet]
// CHECK:STDOUT: %.loc5_31.1: %DefaultOrUnformed.type = converted constants.%i32, %DefaultOrUnformed.facet [concrete = constants.%DefaultOrUnformed.facet]
// CHECK:STDOUT: %as_type: type = facet_access_type %.loc5_31.1 [concrete = constants.%i32]
// CHECK:STDOUT: %.loc5_31.2: type = converted %.loc5_31.1, %as_type [concrete = constants.%i32]
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: %T.as.DefaultOrUnformed.impl.Op.call: init %i32 = call %T.as.DefaultOrUnformed.impl.Op.specific_fn()
// CHECK:STDOUT: assign @C.%static_field.var, %T.as.DefaultOrUnformed.impl.Op.call
// CHECK:STDOUT: %int_123: Core.IntLiteral = int_value 123 [concrete = constants.%int_123.fff]
// CHECK:STDOUT: %impl.elem0: %.9db = impl_witness_access constants.%ImplicitAs.impl_witness.ac5, element0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a]
// CHECK:STDOUT: %bound_method.loc6_10.1: <bound method> = bound_method %int_123, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.e93]
// CHECK:STDOUT: %specific_fn: <specific function> = specific_function %impl.elem0, @Core.IntLiteral.as.ImplicitAs.impl.Convert(constants.%int_32) [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn]
// CHECK:STDOUT: %bound_method.loc6_10.2: <bound method> = bound_method %int_123, %specific_fn [concrete = constants.%bound_method.0ac]
// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.call: init %i32 = call %bound_method.loc6_10.2(%int_123) [concrete = constants.%int_123.f7f]
// CHECK:STDOUT: %.loc6: init %i32 = converted %int_123, %Core.IntLiteral.as.ImplicitAs.impl.Convert.call [concrete = constants.%int_123.f7f]
// CHECK:STDOUT: assign @C.%static_field_with_init.var, %.loc6
// CHECK:STDOUT: <elided>
// CHECK:STDOUT: }
// CHECK:STDOUT:
+1
View File
@@ -215,6 +215,7 @@ CARBON_KEYWORD_TOKEN(Return, "return")
CARBON_KEYWORD_TOKEN(Returned, "returned")
CARBON_KEYWORD_TOKEN(SelfTypeIdentifier, "Self")
CARBON_KEYWORD_TOKEN(SelfValueIdentifier, "self")
CARBON_KEYWORD_TOKEN(Static, "static")
// TODO: Although we provide a `str` type literal, it's not standardized.
CARBON_KEYWORD_TOKEN(Str, "str")
CARBON_KEYWORD_TOKEN(Template, "template")
+33 -21
View File
@@ -115,28 +115,13 @@ auto FileContext::LowerDefinitions() -> void {
// Lower global variable definitions.
// TODO: Storing both a `constants_` array and a separate `global_variables_`
// map is redundant.
for (auto inst_id :
sem_ir().inst_blocks().Get(sem_ir().top_inst_block_id())) {
// Only `VarStorage` indicates a global variable declaration in the
// top instruction block.
if (auto var = sem_ir().insts().TryGetAs<SemIR::VarStorage>(inst_id)) {
// Get the global variable declaration. We created this when lowering the
// constant unless the variable is unnamed, in which case we need to
// create it now.
llvm::GlobalVariable* llvm_var = nullptr;
if (auto const_id = sem_ir().constant_values().Get(inst_id);
const_id.is_constant()) {
llvm_var = cast<llvm::GlobalVariable>(GetConstant(const_id, inst_id));
} else {
// We should never be emitting a definition for a C++ global variable.
llvm_var = BuildNonCppGlobalVariableDecl(*var);
}
LowerGlobalVariables(sem_ir().top_inst_block_id());
// Convert the declaration of this variable into a definition by adding an
// initializer.
global_variables_.Insert(inst_id, llvm_var);
llvm_var->setInitializer(
llvm::Constant::getNullValue(llvm_var->getValueType()));
// Lower static class variable definitions.
for (auto class_info : sem_ir().classes().values()) {
auto inst_block_id = class_info.body_block_id;
if (inst_block_id.has_value()) {
LowerGlobalVariables(inst_block_id);
}
}
@@ -264,6 +249,33 @@ auto FileContext::GetOrCreateFunctionInfo(
return result;
}
auto FileContext::LowerGlobalVariables(SemIR::InstBlockId inst_block_id)
-> void {
for (auto inst_id : sem_ir().inst_blocks().Get(inst_block_id)) {
// Only `VarStorage` indicates a global variable declaration in the
// top instruction block.
if (auto var = sem_ir().insts().TryGetAs<SemIR::VarStorage>(inst_id)) {
// Get the global variable declaration. We created this when lowering the
// constant unless the variable is unnamed, in which case we need to
// create it now.
llvm::GlobalVariable* llvm_var = nullptr;
if (auto const_id = sem_ir().constant_values().Get(inst_id);
const_id.is_constant()) {
llvm_var = cast<llvm::GlobalVariable>(GetConstant(const_id, inst_id));
} else {
// We should never be emitting a definition for a C++ global variable.
llvm_var = BuildNonCppGlobalVariableDecl(*var);
}
// Convert the declaration of this variable into a definition by adding an
// initializer.
global_variables_.Insert(inst_id, llvm_var);
llvm_var->setInitializer(
llvm::Constant::getNullValue(llvm_var->getValueType()));
}
}
}
auto FileContext::HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl)
-> llvm::Function* {
// Create the LLVM function (`CodeGenModule::GetOrCreateLLVMFunction()`)
+3
View File
@@ -190,6 +190,9 @@ class FileContext {
SemIR::SpecificId specific_id = SemIR::SpecificId::None) -> void;
private:
// Lower global variables defined in `inst_block_id`.
auto LowerGlobalVariables(SemIR::InstBlockId inst_block_id) -> void;
// Notes that a C++ function has been referenced for the first time, so we
// should ask Clang to generate a definition for it if possible.
auto HandleReferencedCppFunction(clang::FunctionDecl* cpp_decl)
+67
View File
@@ -0,0 +1,67 @@
// 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-FILE: toolchain/testing/testdata/min_prelude/int.carbon
//
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/class/static.carbon
// TIP: To dump output, run:
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/class/static.carbon
// --- static.carbon
library "[[@TEST_NAME]]";
class C {
static var static_field: i32;
static var static_field_with_init: i32 = 123;
}
fn F() {
C.static_field = 456;
C.static_field_with_init = 789;
}
// CHECK:STDOUT: ; ModuleID = 'static.carbon'
// CHECK:STDOUT: source_filename = "static.carbon"
// CHECK:STDOUT:
// CHECK:STDOUT: @_Cstatic_field.C.Main = global i32 0
// CHECK:STDOUT: @_Cstatic_field_with_init.C.Main = global i32 0
// CHECK:STDOUT: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @_C__global_init.Main, ptr null }]
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define void @_CF.Main() #0 !dbg !4 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: store i32 456, ptr @_Cstatic_field.C.Main, align 4, !dbg !7
// CHECK:STDOUT: store i32 789, ptr @_Cstatic_field_with_init.C.Main, align 4, !dbg !8
// CHECK:STDOUT: ret void, !dbg !9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: ; Function Attrs: nounwind
// CHECK:STDOUT: define internal void @_C__global_init.Main() #0 !dbg !10 {
// CHECK:STDOUT: entry:
// CHECK:STDOUT: store i32 poison, ptr @_Cstatic_field.C.Main, align 4, !dbg !11
// CHECK:STDOUT: store i32 123, ptr @_Cstatic_field_with_init.C.Main, align 4, !dbg !12
// CHECK:STDOUT: ret void, !dbg !13
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: attributes #0 = { nounwind }
// CHECK:STDOUT:
// CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
// CHECK:STDOUT: !llvm.dbg.cu = !{!2}
// CHECK:STDOUT:
// CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
// CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
// CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
// CHECK:STDOUT: !3 = !DIFile(filename: "static.carbon", directory: "")
// CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 8, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
// CHECK:STDOUT: !6 = !{null}
// CHECK:STDOUT: !7 = !DILocation(line: 9, column: 3, scope: !4)
// CHECK:STDOUT: !8 = !DILocation(line: 10, column: 3, scope: !4)
// CHECK:STDOUT: !9 = !DILocation(line: 8, column: 1, scope: !4)
// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "__global_init", linkageName: "_C__global_init.Main", scope: null, file: !3, type: !5, spFlags: DISPFlagDefinition, unit: !2)
// CHECK:STDOUT: !11 = !DILocation(line: 4, column: 10, scope: !10)
// CHECK:STDOUT: !12 = !DILocation(line: 5, column: 10, scope: !10)
// CHECK:STDOUT: !13 = !DILocation(line: 0, scope: !10)
+1
View File
@@ -360,6 +360,7 @@ CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(MustEval)
CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Override)
CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Private)
CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Protected)
CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Static)
CARBON_PARSE_NODE_KIND_TOKEN_MODIFIER(Virtual)
CARBON_PARSE_NODE_KIND(ClassIntroducer)
+9 -1
View File
@@ -11,6 +11,7 @@
class Foo {
var x: i32;
var y: i32 = 0;
static var z: i32;
}
@@ -34,6 +35,13 @@ class Foo {
// CHECK:STDOUT: {kind: 'VariableInitializer', text: '='},
// CHECK:STDOUT: {kind: 'IntLiteral', text: '0'},
// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 8},
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 18},
// CHECK:STDOUT: {kind: 'VariableIntroducer', text: 'var'},
// CHECK:STDOUT: {kind: 'StaticModifier', text: 'static'},
// CHECK:STDOUT: {kind: 'IdentifierNameNotBeforeSignature', text: 'z'},
// CHECK:STDOUT: {kind: 'IntTypeLiteral', text: 'i32'},
// CHECK:STDOUT: {kind: 'VarBindingPattern', text: ':', subtree_size: 3},
// CHECK:STDOUT: {kind: 'VariablePattern', text: 'var', subtree_size: 4},
// CHECK:STDOUT: {kind: 'VariableDecl', text: ';', subtree_size: 7},
// CHECK:STDOUT: {kind: 'ClassDefinition', text: '}', subtree_size: 25},
// CHECK:STDOUT: {kind: 'FileEnd', text: ''},
// CHECK:STDOUT: ]