diff --git a/toolchain/check/class.cpp b/toolchain/check/class.cpp index 6b5756072562..1d215dddbeef 100644 --- a/toolchain/check/class.cpp +++ b/toolchain/check/class.cpp @@ -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 diff --git a/toolchain/check/class.h b/toolchain/check/class.h index e2e12e38ee15..156a60b2472f 100644 --- a/toolchain/check/class.h +++ b/toolchain/check/class.h @@ -23,6 +23,12 @@ auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id, llvm::ArrayRef vtable_contents, llvm::ArrayRef 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_ diff --git a/toolchain/check/decl_introducer_state.h b/toolchain/check/decl_introducer_state.h index 53f56d0f4099..4920b8ed35bb 100644 --- a/toolchain/check/decl_introducer_state.h +++ b/toolchain/check/decl_introducer_state.h @@ -31,7 +31,7 @@ struct DeclIntroducerState { Parse::NodeId ordered_modifier_node_ids[static_cast(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(); diff --git a/toolchain/check/full_pattern_stack.h b/toolchain/check/full_pattern_stack.h index b1f0108c7ff1..28f8be1a48ad 100644 --- a/toolchain/check/full_pattern_stack.h +++ b/toolchain/check/full_pattern_stack.h @@ -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); diff --git a/toolchain/check/global_init.cpp b/toolchain/check/global_init.cpp index d154e2477c1b..561330520441 100644 --- a/toolchain/check/global_init.cpp +++ b/toolchain/check/global_init.cpp @@ -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 diff --git a/toolchain/check/global_init.h b/toolchain/check/global_init.h index ec51af9042d3..4863708f7a6f 100644 --- a/toolchain/check/global_init.h +++ b/toolchain/check/global_init.h @@ -42,6 +42,12 @@ class GlobalInit { llvm::SmallVector 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_ diff --git a/toolchain/check/handle_binding_pattern.cpp b/toolchain/check/handle_binding_pattern.cpp index 113daa3b28df..2240be5dd58c 100644 --- a/toolchain/check/handle_binding_pattern.cpp +++ b/toolchain/check/handle_binding_pattern.cpp @@ -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"); } diff --git a/toolchain/check/handle_let_and_var.cpp b/toolchain/check/handle_let_and_var.cpp index 55cc5016fcdd..ca44a7eec349 100644 --- a/toolchain/check/handle_let_and_var.cpp +++ b/toolchain/check/handle_let_and_var.cpp @@ -70,7 +70,7 @@ static auto HandleIntroducer(Context& context, Parse::NodeId node_id) -> bool { context.pattern_block_stack().Push(); if (context.scope_stack().TryGetCurrentScopeAs() && 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( + 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 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(); + 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(context, node_id); + context.full_pattern_stack().PopFullPattern(); + context.decl_introducer_state_stack().Pop(); 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(); LimitModifiersOnDecl( context, decl_info.introducer, @@ -397,15 +407,18 @@ auto HandleParseNode(Context& context, Parse::VariableDeclId node_id) -> bool { HandleDecl(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()) { - 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(); + return true; } diff --git a/toolchain/check/handle_modifier.cpp b/toolchain/check/handle_modifier.cpp index faa0f9c38405..5512fa4b48bf 100644 --- a/toolchain/check/handle_modifier.cpp +++ b/toolchain/check/handle_modifier.cpp @@ -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."); } diff --git a/toolchain/check/handle_pattern_list.cpp b/toolchain/check/handle_pattern_list.cpp index 2c0e228164bb..a88b427f7ccd 100644 --- a/toolchain/check/handle_pattern_list.cpp +++ b/toolchain/check/handle_pattern_list.cpp @@ -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 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), diff --git a/toolchain/check/keyword_modifier_set.h b/toolchain/check/keyword_modifier_set.h index c83f4bb9d89a..e922e58f8ffd 100644 --- a/toolchain/check/keyword_modifier_set.h +++ b/toolchain/check/keyword_modifier_set.h @@ -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 diff --git a/toolchain/check/modifiers.cpp b/toolchain/check/modifiers.cpp index f50dc717be98..ce993e6aa8a3 100644 --- a/toolchain/check/modifiers.cpp +++ b/toolchain/check/modifiers.cpp @@ -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; } diff --git a/toolchain/check/pattern.cpp b/toolchain/check/pattern.cpp index 928072147d63..acfbb7a93731 100644 --- a/toolchain/check/pattern.cpp +++ b/toolchain/check/pattern.cpp @@ -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(); auto name_id = context.entity_names().Get(pattern.entity_name_id).name_id; diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 4d39b2135214..8d984b31e8d0 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -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; diff --git a/toolchain/check/testdata/class/field/static.carbon b/toolchain/check/testdata/class/field/static.carbon new file mode 100644 index 000000000000..58da0788f47b --- /dev/null +++ b/toolchain/check/testdata/class/field/static.carbon @@ -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: = 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: = 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 %int_123.fff, %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a [concrete] +// CHECK:STDOUT: %Core.IntLiteral.as.ImplicitAs.impl.Convert.specific_fn: = 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 %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: = 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 %int_456.010, %Core.IntLiteral.as.ImplicitAs.impl.Convert.f1a [concrete] +// CHECK:STDOUT: %bound_method.4c0: = 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: = 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 %int_456, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.ee2] +// CHECK:STDOUT: %specific_fn: = 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 %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: +// 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 %int_123, %impl.elem0 [concrete = constants.%Core.IntLiteral.as.ImplicitAs.impl.Convert.bound.e93] +// CHECK:STDOUT: %specific_fn: = 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 %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: +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/lex/token_kind.def b/toolchain/lex/token_kind.def index 5247f745cd05..a2f55d3fda84 100644 --- a/toolchain/lex/token_kind.def +++ b/toolchain/lex/token_kind.def @@ -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") diff --git a/toolchain/lower/file_context.cpp b/toolchain/lower/file_context.cpp index a7108213b3d9..af4f13493898 100644 --- a/toolchain/lower/file_context.cpp +++ b/toolchain/lower/file_context.cpp @@ -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(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(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(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(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()`) diff --git a/toolchain/lower/file_context.h b/toolchain/lower/file_context.h index 521551fa288f..e565d595b4c6 100644 --- a/toolchain/lower/file_context.h +++ b/toolchain/lower/file_context.h @@ -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) diff --git a/toolchain/lower/testdata/class/static.carbon b/toolchain/lower/testdata/class/static.carbon new file mode 100644 index 000000000000..ec977c3f5942 --- /dev/null +++ b/toolchain/lower/testdata/class/static.carbon @@ -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) diff --git a/toolchain/parse/node_kind.def b/toolchain/parse/node_kind.def index 5d363cb44bfe..30ff46c06411 100644 --- a/toolchain/parse/node_kind.def +++ b/toolchain/parse/node_kind.def @@ -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) diff --git a/toolchain/parse/testdata/class/var.carbon b/toolchain/parse/testdata/class/var.carbon index 87bdade3849a..117598a9795b 100644 --- a/toolchain/parse/testdata/class/var.carbon +++ b/toolchain/parse/testdata/class/var.carbon @@ -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: ]