// Part of the Carbon Language project, under the Apache License v2.0 with LLVM // Exceptions. See /LICENSE for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include "toolchain/check/modifiers.h" #include "toolchain/check/decl_state.h" namespace Carbon::Check { static auto ReportNotAllowed(Context& context, Parse::NodeId modifier_node, Lex::TokenKind decl_kind, llvm::StringRef context_string, Parse::NodeId context_node) -> void { CARBON_DIAGNOSTIC(ModifierNotAllowedOn, Error, "`{0}` not allowed on `{1}` declaration{2}.", Lex::TokenKind, Lex::TokenKind, std::string); auto diag = context.emitter().Build(modifier_node, ModifierNotAllowedOn, context.token_kind(modifier_node), decl_kind, context_string.str()); if (context_node.is_valid()) { CARBON_DIAGNOSTIC(ModifierNotInContext, Note, "Containing definition here."); diag.Note(context_node, ModifierNotInContext); } diag.Emit(); } // Returns the KeywordModifierSet corresponding to the ModifierOrder entry. static auto ModifierOrderAsSet(ModifierOrder order) -> KeywordModifierSet { switch (order) { case ModifierOrder::Access: return KeywordModifierSet::Access; case ModifierOrder::Extern: return KeywordModifierSet::Extern; case ModifierOrder::Decl: return KeywordModifierSet::Decl; } } auto ForbidModifiersOnDecl(Context& context, KeywordModifierSet forbidden, Lex::TokenKind decl_kind, llvm::StringRef context_string, Parse::NodeId context_node) -> void { auto& s = context.decl_state_stack().innermost(); auto not_allowed = s.modifier_set & forbidden; if (!not_allowed) { return; } for (auto order_index = 0; order_index <= static_cast(ModifierOrder::Last); ++order_index) { auto order = static_cast(order_index); if (!!(not_allowed & ModifierOrderAsSet(order))) { ReportNotAllowed(context, s.modifier_node_id(order), decl_kind, context_string, context_node); s.set_modifier_node_id(order, Parse::NodeId::Invalid); } } s.modifier_set &= ~forbidden; } // Returns the instruction that owns the given scope, or Invalid if the scope is // not associated with an instruction. static auto GetScopeInstId(Context& context, SemIR::NameScopeId scope_id) -> SemIR::InstId { if (!scope_id.is_valid()) { return SemIR::InstId::Invalid; } return context.name_scopes().Get(scope_id).inst_id; } // Returns the instruction that owns the given scope, or Invalid if the scope is // not associated with an instruction. static auto GetScopeInst(Context& context, SemIR::NameScopeId scope_id) -> std::optional { auto inst_id = GetScopeInstId(context, scope_id); if (!inst_id.is_valid()) { return std::nullopt; } return context.insts().Get(inst_id); } auto CheckAccessModifiersOnDecl(Context& context, Lex::TokenKind decl_kind, SemIR::NameScopeId target_scope_id) -> void { auto target = GetScopeInst(context, target_scope_id); if (target && target->Is()) { // TODO: This assumes that namespaces can only be declared at file scope. If // we add support for non-file-scope namespaces, we will need to check the // parents of the target scope to determine whether we're at file scope. ForbidModifiersOnDecl( context, KeywordModifierSet::Protected, decl_kind, " at file scope, `protected` is only allowed on class members"); return; } if (target && target->Is()) { // Both `private` and `protected` allowed in a class definition. return; } // Otherwise neither `private` nor `protected` allowed. ForbidModifiersOnDecl(context, KeywordModifierSet::Protected, decl_kind, ", `protected` is only allowed on class members"); ForbidModifiersOnDecl( context, KeywordModifierSet::Private, decl_kind, ", `private` is only allowed on class members and at file scope"); } auto CheckMethodModifiersOnFunction(Context& context, SemIR::NameScopeId target_scope_id) -> void { const Lex::TokenKind decl_kind = Lex::TokenKind::Fn; auto target_id = GetScopeInstId(context, target_scope_id); if (target_id.is_valid()) { if (auto class_decl = context.insts().TryGetAs(target_id)) { auto inheritance_kind = context.classes().Get(class_decl->class_id).inheritance_kind; if (inheritance_kind == SemIR::Class::Final) { ForbidModifiersOnDecl(context, KeywordModifierSet::Virtual, decl_kind, " in a non-abstract non-base `class` definition", context.insts().GetNodeId(target_id)); } if (inheritance_kind != SemIR::Class::Abstract) { ForbidModifiersOnDecl(context, KeywordModifierSet::Abstract, decl_kind, " in a non-abstract `class` definition", context.insts().GetNodeId(target_id)); } return; } } ForbidModifiersOnDecl(context, KeywordModifierSet::Method, decl_kind, " outside of a class"); } auto ForbidExternModifierOnDefinition(Context& context, Lex::TokenKind decl_kind) -> void { ForbidModifiersOnDecl(context, KeywordModifierSet::Extern, decl_kind, " that provides a definition"); } auto RequireDefaultFinalOnlyInInterfaces(Context& context, Lex::TokenKind decl_kind, SemIR::NameScopeId target_scope_id) -> void { auto target = GetScopeInst(context, target_scope_id); if (target && target->Is()) { // Both `default` and `final` allowed in an interface definition. return; } ForbidModifiersOnDecl(context, KeywordModifierSet::Interface, decl_kind, " outside of an interface"); } } // namespace Carbon::Check