From e37ac4ff1620ed2d7026f52baccbfa022d79ced1 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Sun, 13 Apr 2008 21:30:24 +0000 Subject: [PATCH] This patch adds very basic support for parsing and type-checking class inheritance in C++. It'll parse the base-specifier list, e.g., class D : public B1, virtual public B2 { }; and do some of the simpler semantic checks (B1 and B2 are classes; they aren't unions or incomplete types, etc). git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@49623 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/DiagnosticKinds.def | 16 ++ include/clang/Parse/AccessSpecifier.h | 30 +++ include/clang/Parse/Action.h | 9 + include/clang/Parse/Parser.h | 10 +- lib/Parse/ParseDecl.cpp | 40 +--- lib/Parse/ParseDeclCXX.cpp | 233 +++++++++++++++++++++++- lib/Sema/Sema.h | 9 + lib/Sema/SemaDeclCXX.cpp | 49 +++++ test/Sema/decl-invalid.c | 2 +- test/Sema/inherit.cpp | 25 +++ 10 files changed, 382 insertions(+), 41 deletions(-) create mode 100644 include/clang/Parse/AccessSpecifier.h create mode 100644 test/Sema/inherit.cpp diff --git a/include/clang/Basic/DiagnosticKinds.def b/include/clang/Basic/DiagnosticKinds.def index 0c953cad0f..8e2f69b222 100644 --- a/include/clang/Basic/DiagnosticKinds.def +++ b/include/clang/Basic/DiagnosticKinds.def @@ -910,6 +910,22 @@ DIAG(err_overload_no_match, ERROR, DIAG(err_overload_multiple_match, ERROR, "more than one matching function found in __builtin_overload") +// Classes. +DIAG(err_dup_virtual, ERROR, + "duplicate 'virtual' in base specifier") +DIAG(err_expected_class_name, ERROR, + "expected class name") +DIAG(err_anon_type_definition, ERROR, + "declaration of anonymous %0 must be a definition") +DIAG(err_base_clause_on_union, ERROR, + "unions cannot have base classes") +DIAG(err_base_must_be_class, ERROR, + "base specifier must name a class") +DIAG(err_union_as_base_class, ERROR, + "unions cannot be base classes") +DIAG(err_incomplete_base_class, ERROR, + "base class has incomplete type") + // CHECK: printf format string errors DIAG(warn_printf_not_string_constant, WARNING, "format string is not a string literal (potentially insecure)") diff --git a/include/clang/Parse/AccessSpecifier.h b/include/clang/Parse/AccessSpecifier.h new file mode 100644 index 0000000000..a35725ab3f --- /dev/null +++ b/include/clang/Parse/AccessSpecifier.h @@ -0,0 +1,30 @@ +//===--- AccessSpecifier.h - C++ Access Specifiers -*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines interfaces used for Declaration Specifiers and Declarators. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_PARSE_ACCESS_SPECIFIER_H +#define LLVM_CLANG_PARSE_ACCESS_SPECIFIER_H + +namespace clang { + +/// AccessSpecifier - A C++ access specifier (none, public, private, +/// protected). +enum AccessSpecifier { + AS_none, + AS_public, + AS_protected, + AS_private +}; + +} // end namespace clang + +#endif diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index c6282e5788..8d2c62bbb2 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -17,6 +17,7 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/TokenKinds.h" +#include "clang/Parse/AccessSpecifier.h" namespace clang { // Semantic. @@ -545,6 +546,14 @@ public: ExprTy *Op = 0) { return 0; } + + //===---------------------------- C++ Classes ---------------------------===// + /// ActOnBaseSpecifier - Parsed a base specifier + virtual void ActOnBaseSpecifier(DeclTy *classdecl, SourceRange SpecifierRange, + bool Virtual, AccessSpecifier Access, + DeclTy *basetype, SourceLocation BaseLoc) { + } + //===----------------------- Obj-C Declarations -------------------------===// // ActOnStartClassInterface - this action is called immediately after parsing diff --git a/include/clang/Parse/Parser.h b/include/clang/Parse/Parser.h index 13535805e2..9ebe91610d 100644 --- a/include/clang/Parse/Parser.h +++ b/include/clang/Parse/Parser.h @@ -451,7 +451,6 @@ private: bool ParseTag(DeclTy *&Decl, unsigned TagType, SourceLocation StartLoc); void ParseEnumSpecifier(DeclSpec &DS); void ParseEnumBody(SourceLocation StartLoc, DeclTy *TagDecl); - void ParseStructUnionSpecifier(DeclSpec &DS); void ParseStructUnionBody(SourceLocation StartLoc, unsigned TagType, DeclTy *TagDecl); void ParseStructDeclaration(DeclSpec &DS, @@ -482,6 +481,15 @@ private: DeclTy *ParseNamespace(unsigned Context); DeclTy *ParseLinkage(unsigned Context); + //===--------------------------------------------------------------------===// + // C++ 9: classes [class] and C structs/unions. + void ParseClassSpecifier(DeclSpec &DS); + + //===--------------------------------------------------------------------===// + // C++ 10: Derived classes [class.derived] + void ParseBaseClause(DeclTy *ClassDecl); + bool ParseBaseSpecifier(DeclTy *ClassDecl); + AccessSpecifier getAccessSpecifierIfPresent(); }; } // end namespace clang diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 8a0dcce31c..65f1124e63 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -519,7 +519,7 @@ void Parser::ParseDeclarationSpecifiers(DeclSpec &DS) { case tok::kw_class: case tok::kw_struct: case tok::kw_union: - ParseStructUnionSpecifier(DS); + ParseClassSpecifier(DS); continue; case tok::kw_enum: ParseEnumSpecifier(DS); @@ -608,42 +608,6 @@ bool Parser::ParseTag(DeclTy *&Decl, unsigned TagType, SourceLocation StartLoc){ return false; } - -/// ParseStructUnionSpecifier -/// struct-or-union-specifier: [C99 6.7.2.1] -/// struct-or-union identifier[opt] '{' struct-contents '}' -/// struct-or-union identifier -/// [GNU] struct-or-union attributes[opt] identifier[opt] '{' struct-contents -/// '}' attributes[opt] -/// [GNU] struct-or-union attributes[opt] identifier -/// struct-or-union: -/// 'struct' -/// 'union' -/// -void Parser::ParseStructUnionSpecifier(DeclSpec &DS) { - assert((Tok.is(tok::kw_class) || - Tok.is(tok::kw_struct) || - Tok.is(tok::kw_union)) && - "Not a class/struct/union specifier"); - DeclSpec::TST TagType = - Tok.is(tok::kw_class) ? DeclSpec::TST_class : - Tok.is(tok::kw_union) ? DeclSpec::TST_union : DeclSpec::TST_struct; - SourceLocation StartLoc = ConsumeToken(); - - // Parse the tag portion of this. - DeclTy *TagDecl; - if (ParseTag(TagDecl, TagType, StartLoc)) - return; - - // If there is a body, parse it and inform the actions module. - if (Tok.is(tok::l_brace)) - ParseStructUnionBody(StartLoc, TagType, TagDecl); - - const char *PrevSpec = 0; - if (DS.SetTypeSpecType(TagType, StartLoc, PrevSpec, TagDecl)) - Diag(StartLoc, diag::err_invalid_decl_spec_combination, PrevSpec); -} - /// ParseStructDeclaration - Parse a struct declaration without the terminating /// semicolon. /// @@ -736,7 +700,7 @@ void Parser::ParseStructUnionBody(SourceLocation RecordLoc, // Empty structs are an extension in C (C99 6.7.2.1p7), but are allowed in // C++. - if (Tok.is(tok::r_brace)) + if (Tok.is(tok::r_brace) && !getLang().CPlusPlus) Diag(Tok, diag::ext_empty_struct_union_enum, DeclSpec::getSpecifierName((DeclSpec::TST)TagType)); diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index 46dcb57481..606ff57287 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -11,9 +11,10 @@ // //===----------------------------------------------------------------------===// +#include "clang/Basic/Diagnostic.h" +#include "clang/Parse/DeclSpec.h" #include "clang/Parse/Parser.h" #include "clang/Parse/Scope.h" -#include "clang/Basic/Diagnostic.h" using namespace clang; /// ParseNamespace - We know that the current token is a namespace keyword. This @@ -117,3 +118,233 @@ Parser::DeclTy *Parser::ParseLinkage(unsigned Context) { return Actions.ActOnLinkageSpec(Loc, LBrace, RBrace, LangBufPtr, StrSize, D); } + +/// ParseClassSpecifier - Parse a C++ class-specifier [C++ class] or +/// elaborated-type-specifier [C++ dcl.type.elab]; we can't tell which +/// until we reach the start of a definition or see a token that +/// cannot start a definition. +/// +/// class-specifier: [C++ class] +/// class-head '{' member-specification[opt] '}' +/// class-head '{' member-specification[opt] '}' attributes[opt] +/// class-head: +/// class-key identifier[opt] base-clause[opt] +/// class-key nested-name-specifier identifier base-clause[opt] +/// class-key nested-name-specifier[opt] simple-template-id +/// base-clause[opt] +/// [GNU] class-key attributes[opt] identifier[opt] base-clause[opt] +/// [GNU] class-key attributes[opt] nested-name-specifier +/// identifier base-clause[opt] +/// [GNU] class-key attributes[opt] nested-name-specifier[opt] +/// simple-template-id base-clause[opt] +/// class-key: +/// 'class' +/// 'struct' +/// 'union' +/// +/// elaborated-type-specifier: [C++ dcl.type.elab] +/// class-key ::[opt] nested-name-specifier[opt] identifier +/// class-key ::[opt] nested-name-specifier[opt] 'template'[opt] +/// simple-template-id +/// +/// Note that the C++ class-specifier and elaborated-type-specifier, +/// together, subsume the C99 struct-or-union-specifier: +/// +/// struct-or-union-specifier: [C99 6.7.2.1] +/// struct-or-union identifier[opt] '{' struct-contents '}' +/// struct-or-union identifier +/// [GNU] struct-or-union attributes[opt] identifier[opt] '{' struct-contents +/// '}' attributes[opt] +/// [GNU] struct-or-union attributes[opt] identifier +/// struct-or-union: +/// 'struct' +/// 'union' +void Parser::ParseClassSpecifier(DeclSpec &DS) { + assert((Tok.is(tok::kw_class) || + Tok.is(tok::kw_struct) || + Tok.is(tok::kw_union)) && + "Not a class specifier"); + DeclSpec::TST TagType = + Tok.is(tok::kw_class) ? DeclSpec::TST_class : + Tok.is(tok::kw_struct) ? DeclSpec::TST_struct : + DeclSpec::TST_union; + + SourceLocation StartLoc = ConsumeToken(); + + AttributeList *Attr = 0; + // If attributes exist after tag, parse them. + if (Tok.is(tok::kw___attribute)) + Attr = ParseAttributes(); + + // FIXME: Parse the (optional) nested-name-specifier. + + // Parse the (optional) class name. + // FIXME: Alternatively, parse a simple-template-id. + IdentifierInfo *Name = 0; + SourceLocation NameLoc; + if (Tok.is(tok::identifier)) { + Name = Tok.getIdentifierInfo(); + NameLoc = ConsumeToken(); + } + + // There are three options here. If we have 'struct foo;', then + // this is a forward declaration. If we have 'struct foo {...' or + // 'struct fo :...' then this is a definition. Otherwise we have + // something like 'struct foo xyz', a reference. + Action::TagKind TK; + if (Tok.is(tok::l_brace) || (getLang().CPlusPlus && Tok.is(tok::colon))) + TK = Action::TK_Definition; + else if (Tok.is(tok::semi)) + TK = Action::TK_Declaration; + else + TK = Action::TK_Reference; + + if (!Name && TK != Action::TK_Definition) { + // We have a declaration or reference to an anonymous class. + Diag(StartLoc, diag::err_anon_type_definition, + DeclSpec::getSpecifierName(TagType)); + + // Skip the rest of this declarator, up until the comma or semicolon. + SkipUntil(tok::comma, true); + return; + } + + // Parse the tag portion of this. + DeclTy *TagDecl = Actions.ActOnTag(CurScope, TagType, TK, StartLoc, Name, + NameLoc, Attr); + + // Parse the optional base clause (C++ only). + if (getLang().CPlusPlus && Tok.is(tok::colon)) { + ParseBaseClause(TagDecl); + } + + // If there is a body, parse it and inform the actions module. + if (Tok.is(tok::l_brace)) + ParseStructUnionBody(StartLoc, TagType, TagDecl); + else if (TK == Action::TK_Definition) { + // FIXME: Complain that we have a base-specifier list but no + // definition. + Diag(Tok.getLocation(), diag::err_expected_lbrace); + } + + const char *PrevSpec = 0; + if (DS.SetTypeSpecType(TagType, StartLoc, PrevSpec, TagDecl)) + Diag(StartLoc, diag::err_invalid_decl_spec_combination, PrevSpec); +} + +/// ParseBaseClause - Parse the base-clause of a C++ class [C++ class.derived]. +/// +/// base-clause : [C++ class.derived] +/// ':' base-specifier-list +/// base-specifier-list: +/// base-specifier '...'[opt] +/// base-specifier-list ',' base-specifier '...'[opt] +void Parser::ParseBaseClause(DeclTy *ClassDecl) +{ + assert(Tok.is(tok::colon) && "Not a base clause"); + ConsumeToken(); + + while (true) { + // Parse a base-specifier. + if (ParseBaseSpecifier(ClassDecl)) { + // Skip the rest of this base specifier, up until the comma or + // opening brace. + SkipUntil(tok::comma, tok::l_brace); + } + + // If the next token is a comma, consume it and keep reading + // base-specifiers. + if (Tok.isNot(tok::comma)) break; + + // Consume the comma. + ConsumeToken(); + } +} + +/// ParseBaseSpecifier - Parse a C++ base-specifier. A base-specifier is +/// one entry in the base class list of a class specifier, for example: +/// class foo : public bar, virtual private baz { +/// 'public bar' and 'virtual private baz' are each base-specifiers. +/// +/// base-specifier: [C++ class.derived] +/// ::[opt] nested-name-specifier[opt] class-name +/// 'virtual' access-specifier[opt] ::[opt] nested-name-specifier[opt] +/// class-name +/// access-specifier 'virtual'[opt] ::[opt] nested-name-specifier[opt] +/// class-name +bool Parser::ParseBaseSpecifier(DeclTy *ClassDecl) +{ + bool IsVirtual = false; + SourceLocation StartLoc = Tok.getLocation(); + + // Parse the 'virtual' keyword. + if (Tok.is(tok::kw_virtual)) { + ConsumeToken(); + IsVirtual = true; + } + + // Parse an (optional) access specifier. + AccessSpecifier Access = getAccessSpecifierIfPresent(); + if (Access) + ConsumeToken(); + + // Parse the 'virtual' keyword (again!), in case it came after the + // access specifier. + if (Tok.is(tok::kw_virtual)) { + SourceLocation VirtualLoc = ConsumeToken(); + if (IsVirtual) { + // Complain about duplicate 'virtual' + Diag(VirtualLoc, diag::err_dup_virtual); + } + + IsVirtual = true; + } + + // FIXME: Parse optional '::' and optional nested-name-specifier. + + // Parse the class-name. + // FIXME: Alternatively, parse a simple-template-id. + if (Tok.isNot(tok::identifier)) { + Diag(Tok.getLocation(), diag::err_expected_class_name); + return true; + } + + // We have an identifier; check whether it is actually a type. + DeclTy *BaseType = Actions.isTypeName(*Tok.getIdentifierInfo(), CurScope); + if (!BaseType) { + Diag(Tok.getLocation(), diag::err_expected_class_name); + return true; + } + + // The location of the base class itself. + SourceLocation BaseLoc = Tok.getLocation(); + + // Find the complete source range for the base-specifier. + SourceRange Range(StartLoc, BaseLoc); + + // Consume the identifier token (finally!). + ConsumeToken(); + + // Notify semantic analysis that we have parsed a complete + // base-specifier. + Actions.ActOnBaseSpecifier(ClassDecl, Range, IsVirtual, Access, BaseType, + BaseLoc); + return false; +} + +/// getAccessSpecifierIfPresent - Determine whether the next token is +/// a C++ access-specifier. +/// +/// access-specifier: [C++ class.derived] +/// 'private' +/// 'protected' +/// 'public' +AccessSpecifier Parser::getAccessSpecifierIfPresent() +{ + switch (Tok.getKind()) { + default: return AS_none; + case tok::kw_private: return AS_private; + case tok::kw_protected: return AS_protected; + case tok::kw_public: return AS_public; + } +} diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 226ffa233d..7b305737c2 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -588,7 +588,16 @@ public: SourceLocation ProtoLoc, SourceLocation LParenLoc, SourceLocation RParenLoc); + + //===--------------------------------------------------------------------===// + // C++ Classes + // + /// ActOnBaseSpecifier - Parsed a base specifier + virtual void ActOnBaseSpecifier(DeclTy *classdecl, SourceRange SpecifierRange, + bool Virtual, AccessSpecifier Access, + DeclTy *basetype, SourceLocation BaseLoc); + // Objective-C declarations. virtual DeclTy *ActOnStartClassInterface( SourceLocation AtInterafceLoc, diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 89440c0cf4..7202d2f2f1 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -13,6 +13,7 @@ #include "Sema.h" #include "clang/Basic/LangOptions.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/Type.h" @@ -234,3 +235,51 @@ void Sema::CheckCXXDefaultArguments(FunctionDecl *FD) { } } } + +/// ActOnBaseSpecifier - Parsed a base specifier. A base specifier is +/// one entry in the base class list of a class specifier, for +/// example: +/// class foo : public bar, virtual private baz { +/// 'public bar' and 'virtual private baz' are each base-specifiers. +void Sema::ActOnBaseSpecifier(DeclTy *classdecl, SourceRange SpecifierRange, + bool Virtual, AccessSpecifier Access, + DeclTy *basetype, SourceLocation BaseLoc) { + RecordDecl *Decl = (RecordDecl*)classdecl; + QualType BaseType = Context.getTypeDeclType((TypeDecl*)basetype); + + // Base specifiers must be record types. + if (!BaseType->isRecordType()) { + Diag(BaseLoc, diag::err_base_must_be_class, SpecifierRange); + return; + } + + // C++ [class.union]p1: + // A union shall not be used as a base class. + if (BaseType->isUnionType()) { + Diag(BaseLoc, diag::err_union_as_base_class, SpecifierRange); + return; + } + + // C++ [class.union]p1: + // A union shall not have base classes. + if (Decl->getKind() == Decl::Union) { + Diag(Decl->getLocation(), diag::err_base_clause_on_union, + SpecifierRange); + Decl->setInvalidDecl(); + return; + } + + // C++ [class.derived]p2: + // The class-name in a base-specifier shall not be an incompletely + // defined class. + if (BaseType->isIncompleteType()) { + Diag(BaseLoc, diag::err_incomplete_base_class, SpecifierRange); + return; + } + + // FIXME: C++ [class.mi]p3: + // A class shall not be specified as a direct base class of a + // derived class more than once. + + // FIXME: Attach base class to the record. +} diff --git a/test/Sema/decl-invalid.c b/test/Sema/decl-invalid.c index 281e8a8d5c..b92146260e 100644 --- a/test/Sema/decl-invalid.c +++ b/test/Sema/decl-invalid.c @@ -1,6 +1,6 @@ // RUN: clang %s -fsyntax-only -verify -typedef union __mbstate_t; // expected-error: {{expected identifier or}} +typedef union __mbstate_t; // expected-error: {{declaration of anonymous union must be a definition}} // PR2017 diff --git a/test/Sema/inherit.cpp b/test/Sema/inherit.cpp new file mode 100644 index 0000000000..f1f96f14e8 --- /dev/null +++ b/test/Sema/inherit.cpp @@ -0,0 +1,25 @@ +// RUN: clang -fsyntax-only -verify %s +class A { }; + +class B1 : A { }; + +class B2 : virtual A { }; + +class B3 : virtual virtual A { }; // expected-error{{duplicate 'virtual' in base specifier}} + +class C : public B1, private B2 { }; + + +class D; + +class E : public D { }; // expected-error{{base class has incomplete type}} + +typedef int I; + +class F : public I { }; // expected-error{{base specifier must name a class}} + +union U1 : public A { }; // expected-error{{unions cannot have base classes}} + +union U2 {}; + +class G : public U2 { }; // expected-error{{unions cannot be base classes}} -- 2.40.0