Author: Nathan Sidwell Date: 2021-09-09T08:30:04-07:00 New Revision: bb3f5f5d788dd9375ab260f77612fab4a707a1ac
URL: https://github.com/llvm/llvm-project/commit/bb3f5f5d788dd9375ab260f77612fab4a707a1ac DIFF: https://github.com/llvm/llvm-project/commit/bb3f5f5d788dd9375ab260f77612fab4a707a1ac.diff LOG: [clang] Array list initialization (pre-p0388) Extends handling of list initialization of bounded array parameters. This adds the missing checks on converting each initializer for both std::initializer_list and arrays. And extends CompareImplicitConversionSequence to compares array size, for two conversions to array type. As noted in this patch, there's a defect in the std concerning the partial orderability of conversion sequences. DR2492 has a suggested direction that will be simple to add once it (hopefully) is accepted. Differential Revision: https://reviews.llvm.org/D103088 Added: clang/test/SemaCXX/overload-ary-bind.cpp Modified: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Overload.h clang/lib/Sema/SemaOverload.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f8e89549f0503..824d9bf469360 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4483,7 +4483,8 @@ def note_ovl_candidate_bad_conv_incomplete : Note< "; remove &}7">; def note_ovl_candidate_bad_list_argument : Note< "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " - "cannot convert initializer list argument to %4">; + "%select{cannot convert initializer list|too few initializers in list" + "|too many initializers in list}7 argument to %4">; def note_ovl_candidate_bad_overload : Note< "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " "no overload of %4 matching %3 for %ordinal5 argument">; diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h index 82661cb3d12ac..f16595c3319cd 100644 --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -469,7 +469,9 @@ class Sema; unrelated_class, bad_qualifiers, lvalue_ref_to_rvalue, - rvalue_ref_to_lvalue + rvalue_ref_to_lvalue, + too_few_initializers, + too_many_initializers, }; // This can be null, e.g. for implicit object arguments. @@ -533,11 +535,14 @@ class Sema; }; /// ConversionKind - The kind of implicit conversion sequence. - unsigned ConversionKind : 30; + unsigned ConversionKind; - /// Whether the target is really a std::initializer_list, and the - /// sequence only represents the worst element conversion. - unsigned StdInitializerListElement : 1; + /// When initializing an array or std::initializer_list from an + /// initializer-list, this is the array or std::initializer_list type being + /// initialized. The remainder of the conversion sequence, including ToType, + /// describe the worst conversion of an initializer to an element of the + /// array or std::initializer_list. (Note, 'worst' is not well defined.) + QualType InitializerListContainerType; void setKind(Kind K) { destruct(); @@ -568,13 +573,13 @@ class Sema; }; ImplicitConversionSequence() - : ConversionKind(Uninitialized), StdInitializerListElement(false) { + : ConversionKind(Uninitialized), InitializerListContainerType() { Standard.setAsIdentityConversion(); } ImplicitConversionSequence(const ImplicitConversionSequence &Other) : ConversionKind(Other.ConversionKind), - StdInitializerListElement(Other.StdInitializerListElement) { + InitializerListContainerType(Other.InitializerListContainerType) { switch (ConversionKind) { case Uninitialized: break; case StandardConversion: Standard = Other.Standard; break; @@ -670,14 +675,18 @@ class Sema; Standard.setAllToTypes(T); } - /// Whether the target is really a std::initializer_list, and the - /// sequence only represents the worst element conversion. - bool isStdInitializerListElement() const { - return StdInitializerListElement; + // True iff this is a conversion sequence from an initializer list to an + // array or std::initializer. + bool hasInitializerListContainerType() const { + return !InitializerListContainerType.isNull(); } - - void setStdInitializerListElement(bool V = true) { - StdInitializerListElement = V; + void setInitializerListContainerType(QualType T) { + InitializerListContainerType = T; + } + QualType getInitializerListContainerType() const { + assert(hasInitializerListContainerType() && + "not initializer list container"); + return InitializerListContainerType; } /// Form an "implicit" conversion sequence from nullptr_t to bool, for a diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 07f50400989e8..4b1b61a390276 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -541,8 +541,8 @@ void UserDefinedConversionSequence::dump() const { /// error. Useful for debugging overloading issues. void ImplicitConversionSequence::dump() const { raw_ostream &OS = llvm::errs(); - if (isStdInitializerListElement()) - OS << "Worst std::initializer_list element conversion: "; + if (hasInitializerListContainerType()) + OS << "Worst list element conversion: "; switch (ConversionKind) { case StandardConversion: OS << "Standard conversion: "; @@ -3777,7 +3777,9 @@ CompareImplicitConversionSequences(Sema &S, SourceLocation Loc, if (S.getLangOpts().CPlusPlus11 && !S.getLangOpts().WritableStrings && hasDeprecatedStringLiteralToCharPtrConversion(ICS1) != - hasDeprecatedStringLiteralToCharPtrConversion(ICS2)) + hasDeprecatedStringLiteralToCharPtrConversion(ICS2) && + // Ill-formedness must not diff er + ICS1.isBad() == ICS2.isBad()) return hasDeprecatedStringLiteralToCharPtrConversion(ICS1) ? ImplicitConversionSequence::Worse : ImplicitConversionSequence::Better; @@ -3803,16 +3805,35 @@ CompareImplicitConversionSequences(Sema &S, SourceLocation Loc, // list-initialization sequence L2 if: // - L1 converts to std::initializer_list<X> for some X and L2 does not, or, // if not that, - // - L1 converts to type "array of N1 T", L2 converts to type "array of N2 T", - // and N1 is smaller than N2., + // — L1 and L2 convert to arrays of the same element type, and either the + // number of elements n_1 initialized by L1 is less than the number of + // elements n_2 initialized by L2, or (unimplemented:C++20) n_1 = n_2 and L2 + // converts to an array of unknown bound and L1 does not, // even if one of the other rules in this paragraph would otherwise apply. if (!ICS1.isBad()) { - if (ICS1.isStdInitializerListElement() && - !ICS2.isStdInitializerListElement()) - return ImplicitConversionSequence::Better; - if (!ICS1.isStdInitializerListElement() && - ICS2.isStdInitializerListElement()) - return ImplicitConversionSequence::Worse; + bool StdInit1 = false, StdInit2 = false; + if (ICS1.hasInitializerListContainerType()) + StdInit1 = S.isStdInitializerList(ICS1.getInitializerListContainerType(), + nullptr); + if (ICS2.hasInitializerListContainerType()) + StdInit2 = S.isStdInitializerList(ICS2.getInitializerListContainerType(), + nullptr); + if (StdInit1 != StdInit2) + return StdInit1 ? ImplicitConversionSequence::Better + : ImplicitConversionSequence::Worse; + + if (ICS1.hasInitializerListContainerType() && + ICS2.hasInitializerListContainerType()) + if (auto *CAT1 = S.Context.getAsConstantArrayType( + ICS1.getInitializerListContainerType())) + if (auto *CAT2 = S.Context.getAsConstantArrayType( + ICS2.getInitializerListContainerType())) + if (S.Context.hasSameUnqualifiedType(CAT1->getElementType(), + CAT2->getElementType()) && + CAT1->getSize() != CAT2->getSize()) + return CAT1->getSize().ult(CAT2->getSize()) + ? ImplicitConversionSequence::Better + : ImplicitConversionSequence::Worse; } if (ICS1.isStandard()) @@ -5066,43 +5087,77 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType, // default-constructible, and if all the elements of the initializer list // can be implicitly converted to X, the implicit conversion sequence is // the worst conversion necessary to convert an element of the list to X. - // - // FIXME: We're missing a lot of these checks. - bool toStdInitializerList = false; - QualType X; - if (ToType->isArrayType()) - X = S.Context.getAsArrayType(ToType)->getElementType(); - else - toStdInitializerList = S.isStdInitializerList(ToType, &X); - if (!X.isNull()) { - for (unsigned i = 0, e = From->getNumInits(); i < e; ++i) { + QualType InitTy = ToType; + ArrayType const *AT = S.Context.getAsArrayType(ToType); + if (AT || S.isStdInitializerList(ToType, &InitTy)) { + unsigned e = From->getNumInits(); + ImplicitConversionSequence DfltElt; + DfltElt.setBad(BadConversionSequence::no_conversion, QualType(), + QualType()); + if (AT) { + // Result has been initialized above as a BadConversionSequence + InitTy = AT->getElementType(); + if (ConstantArrayType const *CT = dyn_cast<ConstantArrayType>(AT)) { + if (CT->getSize().ult(e)) { + // Too many inits, fatally bad + Result.setBad(BadConversionSequence::too_many_initializers, From, + ToType); + Result.setInitializerListContainerType(ToType); + return Result; + } + if (CT->getSize().ugt(e)) { + // Need an init from empty {}, is there one? + InitListExpr EmptyList(S.Context, From->getEndLoc(), None, + From->getEndLoc()); + EmptyList.setType(S.Context.VoidTy); + DfltElt = TryListConversion( + S, &EmptyList, InitTy, SuppressUserConversions, + InOverloadResolution, AllowObjCWritebackConversion); + if (DfltElt.isBad()) { + // No {} init, fatally bad + Result.setBad(BadConversionSequence::too_few_initializers, From, + ToType); + Result.setInitializerListContainerType(ToType); + return Result; + } + } + } + } + + Result.setStandard(); + Result.Standard.setAsIdentityConversion(); + Result.Standard.setFromType(InitTy); + Result.Standard.setAllToTypes(InitTy); + for (unsigned i = 0; i < e; ++i) { Expr *Init = From->getInit(i); - ImplicitConversionSequence ICS = - TryCopyInitialization(S, Init, X, SuppressUserConversions, - InOverloadResolution, - AllowObjCWritebackConversion); - // If a single element isn't convertible, fail. - if (ICS.isBad()) { + ImplicitConversionSequence ICS = TryCopyInitialization( + S, Init, InitTy, SuppressUserConversions, InOverloadResolution, + AllowObjCWritebackConversion); + + // Keep the worse conversion seen so far. + // FIXME: Sequences are not totally ordered, so 'worse' can be + // ambiguous. CWG has been informed. + if (CompareImplicitConversionSequences(S, From->getBeginLoc(), ICS, + Result) == + ImplicitConversionSequence::Worse) { Result = ICS; - break; + // Bail as soon as we find something unconvertible. + if (Result.isBad()) { + Result.setInitializerListContainerType(ToType); + return Result; + } } - // Otherwise, look for the worst conversion. - if (Result.isBad() || CompareImplicitConversionSequences( - S, From->getBeginLoc(), ICS, Result) == - ImplicitConversionSequence::Worse) - Result = ICS; } - // For an empty list, we won't have computed any conversion sequence. - // Introduce the identity conversion sequence. - if (From->getNumInits() == 0) { - Result.setStandard(); - Result.Standard.setAsIdentityConversion(); - Result.Standard.setFromType(ToType); - Result.Standard.setAllToTypes(ToType); - } + // If we needed any implicit {} initialization, compare that now. + // over.ics.list/6 indicates we should compare that conversion. Again CWG + // has been informed that this might not be the best thing. + if (!DfltElt.isBad() && CompareImplicitConversionSequences( + S, From->getEndLoc(), DfltElt, Result) == + ImplicitConversionSequence::Worse) + Result = DfltElt; - Result.setStdInitializerListElement(toStdInitializerList); + Result.setInitializerListContainerType(ToType); return Result; } @@ -5481,6 +5536,10 @@ Sema::PerformObjectArgumentInitialization(Expr *From, case BadConversionSequence::no_conversion: case BadConversionSequence::unrelated_class: break; + + case BadConversionSequence::too_few_initializers: + case BadConversionSequence::too_many_initializers: + llvm_unreachable("Lists are not objects"); } return Diag(From->getBeginLoc(), diag::err_member_function_call_bad_type) @@ -10528,7 +10587,11 @@ static void DiagnoseBadConversion(Sema &S, OverloadCandidate *Cand, S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_list_argument) << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy - << ToTy << (unsigned)isObjectArgument << I + 1; + << ToTy << (unsigned)isObjectArgument << I + 1 + << (Conv.Bad.Kind == BadConversionSequence::too_few_initializers ? 1 + : Conv.Bad.Kind == BadConversionSequence::too_many_initializers + ? 2 + : 0); MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } diff --git a/clang/test/SemaCXX/overload-ary-bind.cpp b/clang/test/SemaCXX/overload-ary-bind.cpp new file mode 100644 index 0000000000000..824ec50e87bcd --- /dev/null +++ b/clang/test/SemaCXX/overload-ary-bind.cpp @@ -0,0 +1,97 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s +// RUN: %clang_cc1 -std=c++17 -verify %s + +namespace One { +char (&b(int(&&)[1]))[1]; // #1 expected-note{{too many initializers}} +char (&b(int(&&)[2]))[2]; // #2 expected-note{{too many initializers}} + +void f() { + static_assert(sizeof(b({1})) == 1); // #1 + static_assert(sizeof(b({1, 2})) == 2); // #2 + + b({1, 2, 3}); // expected-error{{no matching function}} +} +} // namespace One + +namespace Two { +struct Bob { + Bob(int = 1); +}; + +char (&b(Bob(&&)[1]))[1]; // #1 +char (&b(Bob(&&)[2]))[2]; // #2 + +void f() { + static_assert(sizeof(b({})) == 1); // #1 + static_assert(sizeof(b({Bob()})) == 1); // #1 + static_assert(sizeof(b({2, Bob()})) == 2); // #2 +} +} // namespace Two + +namespace Three { +struct Kevin { + Kevin(int); +}; + +char (&b(Kevin(&&)[2]))[2]; // #2 expected-note{{too few initializers}} + +void f() { + b({2}); // #1 expected-error{{no matching function}} +} +} // namespace Three + +namespace Four { +char (&b(int(&&)[1], float))[1]; // #1 expected-note{{candidate}} +char (&b(int(&&)[1], double))[2]; // #2 expected-note{{candidate}} + +char (&c(float, int(&&)[1]))[1]; // #1 expected-note{{candidate}} +char (&c(double, int(&&)[1]))[2]; // #2 expected-note{{candidate}} + +void f() { + b({1}, 0); // expected-error{{is ambiguous}} + c(0, {1}); // expected-error{{is ambiguous}} +} +} // namespace Four + +typedef decltype(sizeof(char)) size_t; +namespace std { +// sufficient initializer list +template <class _E> +class initializer_list { + const _E *__begin_; + size_t __size_; + + constexpr initializer_list(const _E *__b, size_t __s) + : __begin_(__b), + __size_(__s) {} + +public: + typedef _E value_type; + typedef const _E &reference; + typedef const _E &const_reference; + typedef size_t size_type; + + typedef const _E *iterator; + typedef const _E *const_iterator; + + constexpr initializer_list() : __begin_(nullptr), __size_(0) {} + + constexpr size_t size() const { return __size_; } + constexpr const _E *begin() const { return __begin_; } + constexpr const _E *end() const { return __begin_ + __size_; } +}; +} // namespace std + +namespace Five { +struct ugly { + ugly(char *); + ugly(int); +}; +char (&f(std::initializer_list<char *>))[1]; // #1 +char (&f(std::initializer_list<ugly>))[2]; // #2 +void g() { + // Pick #2 as #1 not viable (3->char * fails). + static_assert(sizeof(f({"hello", 3})) == 2); // expected-warning{{not allow}} +} + +} // namespace Five _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits