From: Sebastian Redl Date: Thu, 14 Jul 2011 19:08:10 +0000 (+0000) Subject: For C++11, do more checking of initializer lists up-front, enabling some subset of... X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ac420c5053d6aa41d59f782caad9e46e5baaf2c2;p=clang For C++11, do more checking of initializer lists up-front, enabling some subset of the final functionality. C just leaves the function early. C++98 runs through the same code path, but has no changed functionality either. This is a first baby step towards supporting generalized initializer lists. This also removes an aggregate test case that was just plain wrong, assuming that non-aggregates couldn't be initialized with initializer lists in C++11 mode. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@135177 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp index 835d837e4c..d2122a45ae 100644 --- a/lib/Sema/SemaInit.cpp +++ b/lib/Sema/SemaInit.cpp @@ -7,9 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This file implements semantic analysis for initializers. The main entry -// point is Sema::CheckInitList(), but all of the work is performed -// within the InitListChecker class. +// This file implements semantic analysis for initializers. // //===----------------------------------------------------------------------===// @@ -712,7 +710,7 @@ void InitListChecker::CheckSubElementType(const InitializedEntity &Entity, } else if (SemaRef.getLangOptions().CPlusPlus) { // C++ [dcl.init.aggr]p12: // All implicit type conversions (clause 4) are considered when - // initializing the aggregate member with an ini- tializer from + // initializing the aggregate member with an initializer from // an initializer-list. If the initializer can initialize a // member, the member is initialized. [...] @@ -2384,52 +2382,10 @@ static void MaybeProduceObjCObject(Sema &S, } } -/// \brief Attempt list initialization (C++0x [dcl.init.list]) -static void TryListInitialization(Sema &S, - const InitializedEntity &Entity, - const InitializationKind &Kind, - InitListExpr *InitList, - InitializationSequence &Sequence) { - // FIXME: We only perform rudimentary checking of list - // initializations at this point, then assume that any list - // initialization of an array, aggregate, or scalar will be - // well-formed. When we actually "perform" list initialization, we'll - // do all of the necessary checking. C++0x initializer lists will - // force us to perform more checking here. - - QualType DestType = Entity.getType(); - - // C++ [dcl.init]p13: - // If T is a scalar type, then a declaration of the form - // - // T x = { a }; - // - // is equivalent to - // - // T x = a; - if (DestType->isScalarType()) { - if (InitList->getNumInits() > 1 && S.getLangOptions().CPlusPlus) { - Sequence.SetFailed(InitializationSequence::FK_TooManyInitsForScalar); - return; - } - - // Assume scalar initialization from a single value works. - } else if (DestType->isAggregateType()) { - // Assume aggregate initialization works. - } else if (DestType->isVectorType()) { - // Assume vector initialization works. - } else if (DestType->isReferenceType()) { - // FIXME: C++0x defines behavior for this. - Sequence.SetFailed(InitializationSequence::FK_ReferenceBindingToInitList); - return; - } else if (DestType->isRecordType()) { - // FIXME: C++0x defines behavior for this - Sequence.SetFailed(InitializationSequence::FK_InitListBadDestinationType); - } - - // Add a general "list initialization" step. - Sequence.AddListInitializationStep(DestType); -} +static void SelectInitialization(Sema &S, const InitializedEntity &Entity, + const InitializationKind &Kind, + Expr **Args, unsigned NumArgs, + InitializationSequence &Sequence); /// \brief Try a reference initialization that involves calling a conversion /// function. @@ -3288,6 +3244,185 @@ static void checkIndirectCopyRestoreSource(Sema &S, Expr *src) { << src->getSourceRange(); } +static bool hasDefaultConstructor(Sema &S, CXXRecordDecl *decl) { + DeclContext::lookup_const_iterator Con, ConEnd; + for (llvm::tie(Con, ConEnd) = S.LookupConstructors(decl); + Con != ConEnd; ++Con) { + // FIXME: A constructor template can be a default constructor, but we don't + // handle this in other places as well. + if (isa(*Con)) + continue; + CXXConstructorDecl *Constructor = cast(*Con); + if (Constructor->isDefaultConstructor()) + return true; + } + return false; +} + +/// \brief Attempt list initialization (C++0x [dcl.init.list]) +static void TryListInitialization(Sema &S, + const InitializedEntity &Entity, + const InitializationKind &Kind, + InitListExpr *InitList, + InitializationSequence &Sequence) { + QualType DestType = Entity.getType(); + + // If we're not in C++ mode, defer everything to the init list checker. + if (!S.getLangOptions().CPlusPlus) { + Sequence.AddListInitializationStep(DestType); + return; + } + + // Early error return for some C++11 features when we're in 98 mode. + if (!S.getLangOptions().CPlusPlus0x) { + if (DestType->isReferenceType()) { + Sequence.SetFailed(InitializationSequence::FK_ReferenceBindingToInitList); + return; + } + if (DestType->isRecordType() && !DestType->isAggregateType()) { + Sequence.SetFailed(InitializationSequence::FK_InitListBadDestinationType); + return; + } + } + + // If we have a reference and not exactly one initializer (see below), unwrap + // the reference. + bool wasReference = false; + if (InitList->getNumInits() != 1) { + if (const ReferenceType *ref = DestType->getAs()) { + wasReference = true; + DestType = ref->getPointeeType(); + } + } + // Create an object that automatically adds a ref binding step on successful + // return. + class AddRefBinding { + InitializationSequence &Sequence; + bool Bind; + QualType RefType; + public: + AddRefBinding(InitializationSequence &sequence, bool bind, QualType refType) + : Sequence(sequence), Bind(bind), RefType(refType) {} + ~AddRefBinding() { + if (Bind && Sequence) { + Sequence.AddReferenceBindingStep(RefType, /*temporary*/true); + } + } + } addRefBinding(Sequence, wasReference, Entity.getType()); + + // C++11 [dcl.init.list]p3: + // List-initialization of an object or reference of type T is defined as + // follows: + // + // - If the initializer list has no elements and T is a class type with + // a default constructor, the object is value-initialized. + // + // See DR990. This case is handled specially because if we let it get to + // overload resolution, std::initializer_list constructors would be chosen + // over the default constructor. When there's more than one initlist ctor, + // this would actually be ambiguous and fail. + + const RecordType *recordType = DestType->getAs(); + CXXRecordDecl *recordDecl = recordType ? + dyn_cast(recordType->getDecl()) : 0; + if (recordDecl && InitList->getNumInits() == 0 && + hasDefaultConstructor(S, recordDecl)) { + TryValueInitialization(S, Entity, Kind, Sequence); + return; + } + + // - Otherwise, if T is an aggregate, aggregate initialization is + // performed. + // + // Aggregate initialization is the most complicated part. We delegate to + // an InitListChecker to build a representation of what's happening. + // We also treat vector types the same as aggregates. + if (DestType->isAggregateType() || DestType->isVectorType()) { + // FIXME: Deeper analysis necessary. + Sequence.AddListInitializationStep(DestType); + return; + } + + // - Otherwise, if T is a specialization of std::initializer_list, an + // initializer_list object is constructed as described below and used + // to initialize the object according to the rules for initialization + // of an object from a class of the same type. + // + // FIXME: Implement this case. + + // - Otherwise, if T is a class type, constructors are considered. The + // applicable constructors are enumerated and the best one is chosen + // through overload resolution. + if (recordDecl) { + // FIXME: initializer_list constructors are applicable. + TryConstructorInitialization(S, Entity, Kind, InitList->getInits(), + InitList->getNumInits(), DestType, Sequence); + return; + } + + // At this point, there is most likely a defect in the standard. The next + // bullet grabs all reference targets and creates temporaries from the init + // list. However, this means that code such as this doesn't work: + // int i; + // int &ri { i }; // error: non-const lvalue ref cannot bind to temporary. + // This is rather startling, since this code works: + // int &si ( i ); + // + // DR934 (CD2 status) tried to address the problem by making the bullet about + // references be only about references to class types, letting references to + // other things fall through. This means the above works, but this still + // doesn't: + // string s; + // string &rs { s }; // cannot bind to temporary + // string &ss ( s ); // fine + // And this works, but has different semantics: + // const string &cs { s }; // binds to temporary copy + // const string &ds ( s ); // binds directly to s + // Also, the wording change from that DR somehow got lost in the FDIS. + // + // DR1095 (FDIS status) again discovered the problem, but didn't actually + // fix it. + // + // GCC implements it this way. We swap the next two bullets instead, thus + // always letting a reference bind to the single element of an initializer + // list, and constructing a temporary only if the isn't exactly one element. + // So in our order, the next bullet is: + // + // - Otherwise, if the initializer list has a single element, the object + // or reference is initialized from that element; + if (InitList->getNumInits() == 1) { + SelectInitialization(S, Entity, Kind, InitList->getInits(), + InitList->getNumInits(), Sequence); + // Adjust the type of the whole init list to be the same as that of the + // single initializer. + InitList->setType(InitList->getInits()[0]->getType()); + return; + } + + // - Otherwise, if T is a reference type, a prvalue temporary of the type + // referenced by T is list-initialized, and the reference is bound to + // that temporary. + // + // We implement this by unwrapping references at the start of the function + // and adding a reference binding step at the bottom. + + // - Otherwise, if the initializer list has no elements, the object is + // value-initialized. + if (InitList->getNumInits() == 0) { + TryValueInitialization(S, Entity, Kind, Sequence); + return; + } + + // - Otherwise, the program is ill-formed. + // + // The only way to get here ought to be for scalar types with > 1 inits. + assert(DestType->isScalarType() && "Something strange is list-initialized."); + assert(InitList->getNumInits() > 1 && "Strange number of initializers."); + + Sequence.SetFailed(InitializationSequence::FK_TooManyInitsForScalar); + return; +} + /// \brief Determine whether we have compatible array types for the /// purposes of GNU by-copy array initialization. static bool hasCompatibleArrayTypes(ASTContext &Context, @@ -3360,7 +3495,6 @@ InitializationSequence::InitializationSequence(Sema &S, Expr **Args, unsigned NumArgs) : FailedCandidateSet(Kind.getLocation()) { - ASTContext &Context = S.Context; // C++0x [dcl.init]p16: // The semantics of initializers are as follows. The destination type is @@ -3368,9 +3502,8 @@ InitializationSequence::InitializationSequence(Sema &S, // type is the type of the initializer expression. The source type is not // defined when the initializer is a braced-init-list or when it is a // parenthesized list of expressions. - QualType DestType = Entity.getType(); - if (DestType->isDependentType() || + if (Entity.getType()->isDependentType() || Expr::hasAnyTypeDependentArguments(Args, NumArgs)) { SequenceKind = DependentSequence; return; @@ -3379,11 +3512,22 @@ InitializationSequence::InitializationSequence(Sema &S, // Almost everything is a normal sequence. setSequenceKind(NormalSequence); + SelectInitialization(S, Entity, Kind, Args, NumArgs, *this); +} + +static void SelectInitialization(Sema &S, const InitializedEntity &Entity, + const InitializationKind &Kind, + Expr **Args, unsigned NumArgs, + InitializationSequence &Sequence) { + ASTContext &Context = S.Context; + QualType DestType = Entity.getType(); + for (unsigned I = 0; I != NumArgs; ++I) if (Args[I]->getObjectKind() == OK_ObjCProperty) { ExprResult Result = S.ConvertPropertyForRValue(Args[I]); if (Result.isInvalid()) { - SetFailed(FK_ConversionFromPropertyFailed); + Sequence.SetFailed( + InitializationSequence::FK_ConversionFromPropertyFailed); return; } Args[I] = Result.take(); @@ -3400,7 +3544,7 @@ InitializationSequence::InitializationSequence(Sema &S, // - If the initializer is a braced-init-list, the object is // list-initialized (8.5.4). if (InitListExpr *InitList = dyn_cast_or_null(Initializer)) { - TryListInitialization(S, Entity, Kind, InitList, *this); + TryListInitialization(S, Entity, Kind, InitList, Sequence); return; } @@ -3412,22 +3556,22 @@ InitializationSequence::InitializationSequence(Sema &S, // by an object that can be converted into a T. // (Therefore, multiple arguments are not permitted.) if (NumArgs != 1) - SetFailed(FK_TooManyInitsForReference); + Sequence.SetFailed(InitializationSequence::FK_TooManyInitsForReference); else - TryReferenceInitialization(S, Entity, Kind, Args[0], *this); + TryReferenceInitialization(S, Entity, Kind, Args[0], Sequence); return; } // - If the initializer is (), the object is value-initialized. if (Kind.getKind() == InitializationKind::IK_Value || (Kind.getKind() == InitializationKind::IK_Direct && NumArgs == 0)) { - TryValueInitialization(S, Entity, Kind, *this); + TryValueInitialization(S, Entity, Kind, Sequence); return; } // Handle default initialization. if (Kind.getKind() == InitializationKind::IK_Default) { - TryDefaultInitialization(S, Entity, Kind, *this); + TryDefaultInitialization(S, Entity, Kind, Sequence); return; } @@ -3438,7 +3582,7 @@ InitializationSequence::InitializationSequence(Sema &S, // ill-formed. if (const ArrayType *DestAT = Context.getAsArrayType(DestType)) { if (Initializer && IsStringInit(Initializer, DestAT, Context)) { - TryStringLiteralInitialization(S, Entity, Kind, Initializer, *this); + TryStringLiteralInitialization(S, Entity, Kind, Initializer, Sequence); return; } @@ -3451,16 +3595,17 @@ InitializationSequence::InitializationSequence(Sema &S, const ArrayType *SourceAT = Context.getAsArrayType(Initializer->getType()); if (!hasCompatibleArrayTypes(S.Context, DestAT, SourceAT)) - SetFailed(FK_ArrayTypeMismatch); + Sequence.SetFailed(InitializationSequence::FK_ArrayTypeMismatch); else if (Initializer->HasSideEffects(S.Context)) - SetFailed(FK_NonConstantArrayInit); + Sequence.SetFailed(InitializationSequence::FK_NonConstantArrayInit); else { - AddArrayInitStep(DestType); + Sequence.AddArrayInitStep(DestType); } } else if (DestAT->getElementType()->isAnyCharacterType()) - SetFailed(FK_ArrayNeedsInitListOrStringLiteral); + Sequence.SetFailed( + InitializationSequence::FK_ArrayNeedsInitListOrStringLiteral); else - SetFailed(FK_ArrayNeedsInitList); + Sequence.SetFailed(InitializationSequence::FK_ArrayNeedsInitList); return; } @@ -3475,13 +3620,13 @@ InitializationSequence::InitializationSequence(Sema &S, if (!S.getLangOptions().CPlusPlus) { // If allowed, check whether this is an Objective-C writeback conversion. if (allowObjCWritebackConversion && - tryObjCWritebackConversion(S, *this, Entity, Initializer)) { + tryObjCWritebackConversion(S, Sequence, Entity, Initializer)) { return; } // Handle initialization in C - AddCAssignmentStep(DestType); - MaybeProduceObjCObject(S, *this, Entity); + Sequence.AddCAssignmentStep(DestType); + MaybeProduceObjCObject(S, Sequence, Entity); return; } @@ -3498,7 +3643,7 @@ InitializationSequence::InitializationSequence(Sema &S, (Context.hasSameUnqualifiedType(SourceType, DestType) || S.IsDerivedFrom(SourceType, DestType)))) TryConstructorInitialization(S, Entity, Kind, Args, NumArgs, - Entity.getType(), *this); + Entity.getType(), Sequence); // - Otherwise (i.e., for the remaining copy-initialization cases), // user-defined conversion sequences that can convert from the source // type to the destination type or (when a conversion function is @@ -3506,12 +3651,12 @@ InitializationSequence::InitializationSequence(Sema &S, // 13.3.1.4, and the best one is chosen through overload resolution // (13.3). else - TryUserDefinedConversion(S, Entity, Kind, Initializer, *this); + TryUserDefinedConversion(S, Entity, Kind, Initializer, Sequence); return; } if (NumArgs > 1) { - SetFailed(FK_TooManyInitsForScalar); + Sequence.SetFailed(InitializationSequence::FK_TooManyInitsForScalar); return; } assert(NumArgs == 1 && "Zero-argument case handled above"); @@ -3519,8 +3664,8 @@ InitializationSequence::InitializationSequence(Sema &S, // - Otherwise, if the source type is a (possibly cv-qualified) class // type, conversion functions are considered. if (!SourceType.isNull() && SourceType->isRecordType()) { - TryUserDefinedConversion(S, Entity, Kind, Initializer, *this); - MaybeProduceObjCObject(S, *this, Entity); + TryUserDefinedConversion(S, Entity, Kind, Initializer, Sequence); + MaybeProduceObjCObject(S, Sequence, Entity); return; } @@ -3556,22 +3701,22 @@ InitializationSequence::InitializationSequence(Sema &S, LvalueICS.Standard.setAsIdentityConversion(); LvalueICS.Standard.setAllToTypes(ICS.Standard.getToType(0)); LvalueICS.Standard.First = ICS.Standard.First; - AddConversionSequenceStep(LvalueICS, ICS.Standard.getToType(0)); + Sequence.AddConversionSequenceStep(LvalueICS, ICS.Standard.getToType(0)); } - - AddPassByIndirectCopyRestoreStep(Entity.getType(), ShouldCopy); + + Sequence.AddPassByIndirectCopyRestoreStep(Entity.getType(), ShouldCopy); } else if (ICS.isBad()) { DeclAccessPair dap; if (Initializer->getType() == Context.OverloadTy && !S.ResolveAddressOfOverloadedFunction(Initializer , DestType, false, dap)) - SetFailed(InitializationSequence::FK_AddressOfOverloadFailed); + Sequence.SetFailed(InitializationSequence::FK_AddressOfOverloadFailed); else - SetFailed(InitializationSequence::FK_ConversionFailed); + Sequence.SetFailed(InitializationSequence::FK_ConversionFailed); } else { - AddConversionSequenceStep(ICS, Entity.getType()); + Sequence.AddConversionSequenceStep(ICS, Entity.getType()); - MaybeProduceObjCObject(S, *this, Entity); + MaybeProduceObjCObject(S, Sequence, Entity); } } diff --git a/test/CXX/dcl.decl/dcl.init/dcl.init.aggr/p1-0x.cpp b/test/CXX/dcl.decl/dcl.init/dcl.init.aggr/p1-0x.cpp deleted file mode 100644 index 9b92340fa4..0000000000 --- a/test/CXX/dcl.decl/dcl.init/dcl.init.aggr/p1-0x.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -std=c++0x %s - -// An aggregate is an array or a class... -struct Aggr { -private: - static const int n; - void f(); -protected: - struct Inner { int m; }; -public: - bool &br; -}; -bool b; -Aggr ag = { b }; - -// with no user-provided constructors, ... -struct NonAggr1a { - NonAggr1a(int, int); - int k; -}; -// In C++03, this is {{non-aggregate type 'NonAggr1a'}}. -// In C++0x, 'user-provided' is only defined for special member functions, so -// this type is considered to be an aggregate. This is probably a langauge -// defect. -NonAggr1a na1a = { 42 }; - -struct NonAggr1b { - NonAggr1b(const NonAggr1b &); - int k; -}; -NonAggr1b na1b = { 42 }; // expected-error {{non-aggregate type 'NonAggr1b'}} - -// no brace-or-equal-initializers for non-static data members, ... -struct NonAggr2 { - int m = { 123 }; -}; -NonAggr2 na2 = { 42 }; // expected-error {{non-aggregate type 'NonAggr2'}} - -// no private... -struct NonAggr3 { -private: - int n; -}; -NonAggr3 na3 = { 42 }; // expected-error {{non-aggregate type 'NonAggr3'}} - -// or protected non-static data members, ... -struct NonAggr4 { -protected: - int n; -}; -NonAggr4 na4 = { 42 }; // expected-error {{non-aggregate type 'NonAggr4'}} - -// no base classes, ... -struct NonAggr5 : Aggr { -}; -NonAggr5 na5 = { b }; // expected-error {{non-aggregate type 'NonAggr5'}} - -// and no virtual functions. -struct NonAggr6 { - virtual void f(); - int n; -}; -NonAggr6 na6 = { 42 }; // expected-error {{non-aggregate type 'NonAggr6'}} diff --git a/test/SemaCXX/aggregate-initialization.cpp b/test/SemaCXX/aggregate-initialization.cpp index b9e69b00b7..d489691898 100644 --- a/test/SemaCXX/aggregate-initialization.cpp +++ b/test/SemaCXX/aggregate-initialization.cpp @@ -1,9 +1,7 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -std=c++0x %s +// RUN: %clang_cc1 -fsyntax-only -verify %s // Verify that we can't initialize non-aggregates with an initializer // list. -// FIXME: Note that due to a (likely) standard bug, this is technically an -// aggregate. struct NonAggr1 { NonAggr1(int) { } @@ -24,7 +22,7 @@ struct NonAggr4 { virtual void f(); }; -NonAggr1 na1 = { 17 }; +NonAggr1 na1 = { 17 }; // expected-error{{non-aggregate type 'NonAggr1' cannot be initialized with an initializer list}} NonAggr2 na2 = { 17 }; // expected-error{{non-aggregate type 'NonAggr2' cannot be initialized with an initializer list}} NonAggr3 na3 = { 17 }; // expected-error{{non-aggregate type 'NonAggr3' cannot be initialized with an initializer list}} NonAggr4 na4 = { 17 }; // expected-error{{non-aggregate type 'NonAggr4' cannot be initialized with an initializer list}} @@ -48,8 +46,9 @@ struct A { A(); A(int); ~A(); - - A(const A&) = delete; // expected-note 2 {{function has been explicitly marked deleted here}} + +private: + A(const A&) {} // expected-note 4 {{declared private here}} }; struct B { @@ -62,10 +61,10 @@ struct C { void f() { A as1[1] = { }; - A as2[1] = { 1 }; // expected-error {{copying array element of type 'A' invokes deleted constructor}} + A as2[1] = { 1 }; // expected-error {{calling a private constructor of class 'A'}} expected-warning {{requires an accessible copy constructor}} B b1 = { }; - B b2 = { 1 }; // expected-error {{copying member subobject of type 'A' invokes deleted constructor}} + B b2 = { 1 }; // expected-error {{field of type 'A' has private copy constructor}} expected-warning {{requires an accessible copy constructor}} C c1 = { 1 }; } diff --git a/test/SemaCXX/generalized-initializers.cpp b/test/SemaCXX/generalized-initializers.cpp index 6e2bee7e30..738dc5cf2c 100644 --- a/test/SemaCXX/generalized-initializers.cpp +++ b/test/SemaCXX/generalized-initializers.cpp @@ -224,5 +224,39 @@ namespace aggregate { S s3{ 1, 2, 3, 4, 5, 6 }; // xpected-error S s4{ {1, 2}, {3, 4}, {5, 6}, { {7, 8} } }; // xpected-error S s5{ {1, 2}, {3, 4}, { {5}, {6} }, {7, 8} }; // xpected-error + // May still omit stuff, though. + S s6{ {1}, {}, { {}, {} } }; } } + +namespace references { + // From [dcl.init.list]p3 bullet 5: + struct S { + S(std::initializer_list); + S(const std::string&); + }; + void test() { + const S &r1 = { 1, 2, 3.0 }; // no-error (constructor #1) + const S &r2{ "Spinach" }; // no-error (constructor #2) + S &r3 = { 1, 2, 3 }; // xpected-error (binding to non-const) + const int &i1 = { 1 }; // no-error + const int &i2 = { 1.1 }; // xpected-error {{narrowing}} + const int (&iar)[2] = { 1, 2 }; // no-error + + // Edge case: the standard says this must create a temporary and thus + // fail to bind, but that's almost certainly a defect. + int i; + int &ri1{ i }; + int &ri2 = { i }; + S s{ "Spinach" }; + S &rs1{ s }; + S &rs2 = { s }; + } +} + +namespace incomplete { + // Just to make sure it doesn't crash. + struct S; + S s { 1, 2, 3 }; // expected-error {{incomplete}} + S t = { 1, 2, 3 }; // expected-error {{incomplete}} +}