From de7f4b14f5a8b359eb410bcb38b1e582f51b88d8 Mon Sep 17 00:00:00 2001 From: Roberto Raggi Date: Wed, 16 Jul 2025 19:43:05 +0200 Subject: [PATCH] Disallow nested structs and enums in C mode Fixes #565 Signed-off-by: Roberto Raggi --- src/parser/cxx/parser.cc | 23 ++++++++++------ src/parser/cxx/parser.h | 2 ++ src/parser/cxx/scope.cc | 4 +++ src/parser/cxx/scope.h | 1 + src/parser/cxx/symbol_printer.cc | 5 +++- tests/unit_tests/sema/struct_c_01.c | 41 +++++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 tests/unit_tests/sema/struct_c_01.c diff --git a/src/parser/cxx/parser.cc b/src/parser/cxx/parser.cc index 069635b1..3ac09605 100644 --- a/src/parser/cxx/parser.cc +++ b/src/parser/cxx/parser.cc @@ -5206,11 +5206,6 @@ auto Parser::parse_elaborated_enum_specifier(SpecifierAST*& yyast, SourceLocation enumLoc; if (!match(TokenKind::T_ENUM, enumLoc)) return false; - auto globalScopeGuard = Binder::ScopeGuard{&binder_}; - if (is_parsing_c()) { - setScope(globalScope_); - } - NestedNameSpecifierAST* nestedNameSpecifier = nullptr; parse_optional_nested_name_specifier( nestedNameSpecifier, NestedNameSpecifierContext::kDeclarative); @@ -5251,8 +5246,9 @@ auto Parser::parse_elaborated_type_specifier(SpecifierAST*& yyast, if (!parse_class_key(classLoc)) return false; auto globalScopeGuard = Binder::ScopeGuard{&binder_}; + if (is_parsing_c()) { - setScope(globalScope_); + setScope(getCurrentNonClassScope()); } List* attributes = nullptr; @@ -6408,8 +6404,9 @@ auto Parser::parse_enum_specifier(SpecifierAST*& yyast, DeclSpecs& specs) if (!parse_enum_key(enumLoc, classLoc)) return false; auto globalScopeGuard = Binder::ScopeGuard{&binder_}; + if (is_parsing_c()) { - setScope(globalScope_); + setScope(getCurrentNonClassScope()); } List* attributes = nullptr; @@ -7700,8 +7697,9 @@ auto Parser::parse_class_specifier(ClassSpecifierAST*& yyast, DeclSpecs& specs) if (!parse_class_key(classLoc)) return false; auto globalScopeGuard = Binder::ScopeGuard{&binder_}; + if (is_parsing_c()) { - setScope(globalScope_); + setScope(getCurrentNonClassScope()); } List* attributeList = nullptr; @@ -9634,6 +9632,15 @@ void Parser::completePendingFunctionDefinitions() { } } +auto Parser::getCurrentNonClassScope() const -> Scope* { + for (auto current = scope(); current; current = current->parent()) { + if (current->isClassOrNamespaceScope()) continue; + return current; + } + + return globalScope_; +} + auto Parser::scope() const -> Scope* { return binder_.scope(); } void Parser::setScope(Scope* scope) { binder_.setScope(scope); } diff --git a/src/parser/cxx/parser.h b/src/parser/cxx/parser.h index 01a43d34..8c64b6d7 100644 --- a/src/parser/cxx/parser.h +++ b/src/parser/cxx/parser.h @@ -780,6 +780,8 @@ class Parser final { void completePendingFunctionDefinitions(); void completeFunctionDefinition(FunctionDefinitionAST* ast); + [[nodiscard]] auto getCurrentNonClassScope() const -> Scope*; + [[nodiscard]] auto scope() const -> Scope*; void setScope(Scope* scope); void setScope(ScopedSymbol* symbol); diff --git a/src/parser/cxx/scope.cc b/src/parser/cxx/scope.cc index c243dead..e22b6834 100644 --- a/src/parser/cxx/scope.cc +++ b/src/parser/cxx/scope.cc @@ -74,6 +74,10 @@ auto Scope::isTemplateParametersScope() const -> bool { return owner_ && owner_->isTemplateParameters(); } +auto Scope::isFunctionParametersScope() const -> bool { + return owner_ && owner_->isFunctionParameters(); +} + auto Scope::enclosingNamespaceScope() const -> Scope* { for (auto scope = parent_; scope; scope = scope->parent()) { if (scope->isNamespaceScope()) { diff --git a/src/parser/cxx/scope.h b/src/parser/cxx/scope.h index 91715052..bb65462a 100644 --- a/src/parser/cxx/scope.h +++ b/src/parser/cxx/scope.h @@ -46,6 +46,7 @@ class Scope { [[nodiscard]] auto isBlockScope() const -> bool; [[nodiscard]] auto isEnumScope() const -> bool; [[nodiscard]] auto isTemplateParametersScope() const -> bool; + [[nodiscard]] auto isFunctionParametersScope() const -> bool; [[nodiscard]] auto enclosingNamespaceScope() const -> Scope*; [[nodiscard]] auto enclosingNonTemplateParametersScope() const -> Scope*; diff --git a/src/parser/cxx/symbol_printer.cc b/src/parser/cxx/symbol_printer.cc index a9eda6a1..ae438057 100644 --- a/src/parser/cxx/symbol_printer.cc +++ b/src/parser/cxx/symbol_printer.cc @@ -70,7 +70,10 @@ struct DumpSymbols { void operator()(NamespaceSymbol* symbol) { indent(); - out << std::format("namespace {}\n", to_string(symbol->name())); + out << "namespace"; + if (symbol->name()) + out << std::format(" {}", to_string(symbol->name())); + out << "\n"; dumpScope(symbol->scope()); } diff --git a/tests/unit_tests/sema/struct_c_01.c b/tests/unit_tests/sema/struct_c_01.c new file mode 100644 index 00000000..8118599b --- /dev/null +++ b/tests/unit_tests/sema/struct_c_01.c @@ -0,0 +1,41 @@ +// RUN: %cxx -fcheck -dump-symbols -xc %s | %filecheck %s --match-full-lines + +enum E { + v, +}; + +struct A { + enum E e; + int x; +}; + +int main() { + enum K { + w, + }; + + struct B { + enum K k; + enum E e; + int y; + }; + + struct M; +} + +// clang-format off +// CHECK:namespace +// CHECK-NEXT: enum E : int +// CHECK-NEXT: enumerator E v +// CHECK-NEXT: class A +// CHECK-NEXT: field E e +// CHECK-NEXT: field int x +// CHECK-NEXT: function int main() +// CHECK-NEXT: block +// CHECK-NEXT: enum K : int +// CHECK-NEXT: enumerator K w +// CHECK-NEXT: class B +// CHECK-NEXT: field K k +// CHECK-NEXT: field E e +// CHECK-NEXT: field int y +// CHECK-NEXT: class M