diff options
author | Mariya Podchishchaeva <mariya.podchishchaeva@intel.com> | 2024-03-06 11:46:35 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-06 09:46:35 +0100 |
commit | aced81c0a5bf30dda99fde2e28364426de4c18d3 (patch) | |
tree | bb0340c4fabca6fe5253bafeba3931366dc04065 /clang/lib/Sema | |
parent | 6cdf596c52f028ea7d150e0696f967fbff443ccf (diff) | |
download | llvm-aced81c0a5bf30dda99fde2e28364426de4c18d3.zip llvm-aced81c0a5bf30dda99fde2e28364426de4c18d3.tar.gz llvm-aced81c0a5bf30dda99fde2e28364426de4c18d3.tar.bz2 |
[C23] Implement N3018: The constexpr specifier for object definitions (#73099)
The implementation mostly reuses C++ code paths where possible,
including narrowing check in order to provide diagnostic messages in
case initializer for constexpr variable is not exactly representable in
target type.
The following won't work due to lack of support for other features:
- Diagnosing of underspecified declarations involving constexpr
- Constexpr attached to compound literals
Also due to lack of support for char8_t some of examples with utf-8
strings don't work properly.
Fixes https://github.com/llvm/llvm-project/issues/64742
Diffstat (limited to 'clang/lib/Sema')
-rw-r--r-- | clang/lib/Sema/DeclSpec.cpp | 14 | ||||
-rw-r--r-- | clang/lib/Sema/SemaDecl.cpp | 79 | ||||
-rw-r--r-- | clang/lib/Sema/SemaInit.cpp | 119 | ||||
-rw-r--r-- | clang/lib/Sema/SemaOverload.cpp | 65 |
4 files changed, 250 insertions, 27 deletions
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp index aede602..b79683b 100644 --- a/clang/lib/Sema/DeclSpec.cpp +++ b/clang/lib/Sema/DeclSpec.cpp @@ -1377,6 +1377,20 @@ void DeclSpec::Finish(Sema &S, const PrintingPolicy &Policy) { ThreadStorageClassSpec = TSCS_unspecified; ThreadStorageClassSpecLoc = SourceLocation(); } + if (S.getLangOpts().C23 && + getConstexprSpecifier() == ConstexprSpecKind::Constexpr) { + S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination) + << DeclSpec::getSpecifierName(getThreadStorageClassSpec()) + << SourceRange(getThreadStorageClassSpecLoc()); + } + } + + if (S.getLangOpts().C23 && + getConstexprSpecifier() == ConstexprSpecKind::Constexpr && + StorageClassSpec == SCS_extern) { + S.Diag(ConstexprLoc, diag::err_invalid_decl_spec_combination) + << DeclSpec::getSpecifierName(getStorageClassSpec()) + << SourceRange(getStorageClassSpecLoc()); } // If no type specifier was provided and we're parsing a language where diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 8fcaf6a..210e283 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -5147,6 +5147,8 @@ Decl *Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS, Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_tag) << GetDiagnosticTypeSpecifierID(DS) << static_cast<int>(DS.getConstexprSpecifier()); + else if (getLangOpts().C23) + Diag(DS.getConstexprSpecLoc(), diag::err_c23_constexpr_not_variable); else Diag(DS.getConstexprSpecLoc(), diag::err_constexpr_wrong_decl_kind) << static_cast<int>(DS.getConstexprSpecifier()); @@ -8649,6 +8651,38 @@ static bool checkForConflictWithNonVisibleExternC(Sema &S, const T *ND, return false; } +static bool CheckC23ConstexprVarType(Sema &SemaRef, SourceLocation VarLoc, + QualType T) { + QualType CanonT = SemaRef.Context.getCanonicalType(T); + // C23 6.7.1p5: An object declared with storage-class specifier constexpr or + // any of its members, even recursively, shall not have an atomic type, or a + // variably modified type, or a type that is volatile or restrict qualified. + if (CanonT->isVariablyModifiedType()) { + SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T; + return true; + } + + // Arrays are qualified by their element type, so get the base type (this + // works on non-arrays as well). + CanonT = SemaRef.Context.getBaseElementType(CanonT); + + if (CanonT->isAtomicType() || CanonT.isVolatileQualified() || + CanonT.isRestrictQualified()) { + SemaRef.Diag(VarLoc, diag::err_c23_constexpr_invalid_type) << T; + return true; + } + + if (CanonT->isRecordType()) { + const RecordDecl *RD = CanonT->getAsRecordDecl(); + if (llvm::any_of(RD->fields(), [&SemaRef, VarLoc](const FieldDecl *F) { + return CheckC23ConstexprVarType(SemaRef, VarLoc, F->getType()); + })) + return true; + } + + return false; +} + void Sema::CheckVariableDeclarationType(VarDecl *NewVD) { // If the decl is already known invalid, don't check it. if (NewVD->isInvalidDecl()) @@ -8899,6 +8933,12 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) { return; } + if (getLangOpts().C23 && NewVD->isConstexpr() && + CheckC23ConstexprVarType(*this, NewVD->getLocation(), T)) { + NewVD->setInvalidDecl(); + return; + } + if (NewVD->isConstexpr() && !T->isDependentType() && RequireLiteralType(NewVD->getLocation(), T, diag::err_constexpr_var_non_literal)) { @@ -9281,6 +9321,22 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, FunctionDecl *NewFD = nullptr; bool isInline = D.getDeclSpec().isInlineSpecified(); + ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier(); + if (ConstexprKind == ConstexprSpecKind::Constinit || + (SemaRef.getLangOpts().C23 && + ConstexprKind == ConstexprSpecKind::Constexpr)) { + + if (SemaRef.getLangOpts().C23) + SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(), + diag::err_c23_constexpr_not_variable); + else + SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(), + diag::err_constexpr_wrong_decl_kind) + << static_cast<int>(ConstexprKind); + ConstexprKind = ConstexprSpecKind::Unspecified; + D.getMutableDeclSpec().ClearConstexprSpec(); + } + if (!SemaRef.getLangOpts().CPlusPlus) { // Determine whether the function was written with a prototype. This is // true when: @@ -9314,15 +9370,6 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, } ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier(); - - ConstexprSpecKind ConstexprKind = D.getDeclSpec().getConstexprSpecifier(); - if (ConstexprKind == ConstexprSpecKind::Constinit) { - SemaRef.Diag(D.getDeclSpec().getConstexprSpecLoc(), - diag::err_constexpr_wrong_decl_kind) - << static_cast<int>(ConstexprKind); - ConstexprKind = ConstexprSpecKind::Unspecified; - D.getMutableDeclSpec().ClearConstexprSpec(); - } Expr *TrailingRequiresClause = D.getTrailingRequiresClause(); SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R); @@ -13909,7 +13956,9 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) { VDecl->setStorageClass(SC_Extern); // C99 6.7.8p4. All file scoped initializers need to be constant. - if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl()) + // Avoid duplicate diagnostics for constexpr variables. + if (!getLangOpts().CPlusPlus && !VDecl->isInvalidDecl() && + !VDecl->isConstexpr()) CheckForConstantInitializer(Init, DclT); } @@ -14520,9 +14569,13 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { QualType baseType = Context.getBaseElementType(type); bool HasConstInit = true; + if (getLangOpts().C23 && var->isConstexpr() && !Init) + Diag(var->getLocation(), diag::err_constexpr_var_requires_const_init) + << var; + // Check whether the initializer is sufficiently constant. - if (getLangOpts().CPlusPlus && !type->isDependentType() && Init && - !Init->isValueDependent() && + if ((getLangOpts().CPlusPlus || (getLangOpts().C23 && var->isConstexpr())) && + !type->isDependentType() && Init && !Init->isValueDependent() && (GlobalStorage || var->isConstexpr() || var->mightBeUsableInConstantExpressions(Context))) { // If this variable might have a constant initializer or might be usable in @@ -14530,7 +14583,7 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) { // do this lazily, because the result might depend on things that change // later, such as which constexpr functions happen to be defined. SmallVector<PartialDiagnosticAt, 8> Notes; - if (!getLangOpts().CPlusPlus11) { + if (!getLangOpts().CPlusPlus11 && !getLangOpts().C23) { // Prior to C++11, in contexts where a constant initializer is required, // the set of valid constant initializers is described by syntactic rules // in [expr.const]p2-6. diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 060fe35..011deed 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -190,13 +190,35 @@ static void updateGNUCompoundLiteralRValue(Expr *E) { } } +static bool initializingConstexprVariable(const InitializedEntity &Entity) { + Decl *D = Entity.getDecl(); + const InitializedEntity *Parent = &Entity; + + while (Parent) { + D = Parent->getDecl(); + Parent = Parent->getParent(); + } + + if (const auto *VD = dyn_cast_if_present<VarDecl>(D); VD && VD->isConstexpr()) + return true; + + return false; +} + +static void CheckC23ConstexprInitStringLiteral(const StringLiteral *SE, + Sema &SemaRef, QualType &TT); + static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT, - Sema &S) { + Sema &S, bool CheckC23ConstexprInit = false) { // Get the length of the string as parsed. auto *ConstantArrayTy = cast<ConstantArrayType>(Str->getType()->getAsArrayTypeUnsafe()); uint64_t StrLength = ConstantArrayTy->getSize().getZExtValue(); + if (CheckC23ConstexprInit) + if (const StringLiteral *SL = dyn_cast<StringLiteral>(Str->IgnoreParens())) + CheckC23ConstexprInitStringLiteral(SL, S, DeclT); + if (const IncompleteArrayType *IAT = dyn_cast<IncompleteArrayType>(AT)) { // C99 6.7.8p14. We have an array of character type with unknown size // being initialized to a string literal. @@ -1476,7 +1498,9 @@ void InitListChecker::CheckSubElementType(const InitializedEntity &Entity, if (IsStringInit(expr, arrayType, SemaRef.Context) == SIF_None) { // FIXME: Should we do this checking in verify-only mode? if (!VerifyOnly) - CheckStringInit(expr, ElemType, arrayType, SemaRef); + CheckStringInit(expr, ElemType, arrayType, SemaRef, + SemaRef.getLangOpts().C23 && + initializingConstexprVariable(Entity)); if (StructuredList) UpdateStructuredListElement(StructuredList, StructuredIndex, expr); ++Index; @@ -1941,7 +1965,9 @@ void InitListChecker::CheckArrayType(const InitializedEntity &Entity, // constant for each string. // FIXME: Should we do these checks in verify-only mode too? if (!VerifyOnly) - CheckStringInit(IList->getInit(Index), DeclType, arrayType, SemaRef); + CheckStringInit(IList->getInit(Index), DeclType, arrayType, SemaRef, + SemaRef.getLangOpts().C23 && + initializingConstexprVariable(Entity)); if (StructuredList) { UpdateStructuredListElement(StructuredList, StructuredIndex, IList->getInit(Index)); @@ -8377,6 +8403,9 @@ static void DiagnoseNarrowingInInitList(Sema &S, QualType EntityType, const Expr *PostInit); +static void CheckC23ConstexprInitConversion(Sema &S, QualType FromType, + QualType ToType, Expr *Init); + /// Provide warnings when std::move is used on construction. static void CheckMoveOnConstruction(Sema &S, const Expr *InitExpr, bool IsReturnStmt) { @@ -9203,6 +9232,23 @@ ExprResult InitializationSequence::Perform(Sema &S, return ExprError(); CurInit = CurInitExprRes; + if (S.getLangOpts().C23 && initializingConstexprVariable(Entity)) { + CheckC23ConstexprInitConversion(S, SourceType, Entity.getType(), + CurInit.get()); + + // C23 6.7.1p6: If an object or subobject declared with storage-class + // specifier constexpr has pointer, integer, or arithmetic type, any + // explicit initializer value for it shall be null, an integer + // constant expression, or an arithmetic constant expression, + // respectively. + Expr::EvalResult ER; + if (Entity.getType()->getAs<PointerType>() && + CurInit.get()->EvaluateAsRValue(ER, S.Context) && + !ER.Val.isNullPointer()) { + S.Diag(Kind.getLocation(), diag::err_c23_constexpr_pointer_not_null); + } + } + bool Complained; if (S.DiagnoseAssignmentResult(ConvTy, Kind.getLocation(), Step->Type, SourceType, @@ -9220,7 +9266,9 @@ ExprResult InitializationSequence::Perform(Sema &S, QualType Ty = Step->Type; bool UpdateType = ResultType && Entity.getType()->isIncompleteArrayType(); CheckStringInit(CurInit.get(), UpdateType ? *ResultType : Ty, - S.Context.getAsArrayType(Ty), S); + S.Context.getAsArrayType(Ty), S, + S.getLangOpts().C23 && + initializingConstexprVariable(Entity)); break; } @@ -10509,6 +10557,69 @@ static void DiagnoseNarrowingInInitList(Sema &S, S.getLocForEndOfToken(PostInit->getEndLoc()), ")"); } +static void CheckC23ConstexprInitConversion(Sema &S, QualType FromType, + QualType ToType, Expr *Init) { + assert(S.getLangOpts().C23); + ImplicitConversionSequence ICS = S.TryImplicitConversion( + Init->IgnoreParenImpCasts(), ToType, /*SuppressUserConversions*/ false, + Sema::AllowedExplicit::None, + /*InOverloadResolution*/ false, + /*CStyle*/ false, + /*AllowObjCWritebackConversion=*/false); + + if (!ICS.isStandard()) + return; + + APValue Value; + QualType PreNarrowingType; + // Reuse C++ narrowing check. + switch (ICS.Standard.getNarrowingKind( + S.Context, Init, Value, PreNarrowingType, + /*IgnoreFloatToIntegralConversion*/ false)) { + // The value doesn't fit. + case NK_Constant_Narrowing: + S.Diag(Init->getBeginLoc(), diag::err_c23_constexpr_init_not_representable) + << Value.getAsString(S.Context, PreNarrowingType) << ToType; + return; + + // Conversion to a narrower type. + case NK_Type_Narrowing: + S.Diag(Init->getBeginLoc(), diag::err_c23_constexpr_init_type_mismatch) + << ToType << FromType; + return; + + // Since we only reuse narrowing check for C23 constexpr variables here, we're + // not really interested in these cases. + case NK_Dependent_Narrowing: + case NK_Variable_Narrowing: + case NK_Not_Narrowing: + return; + } + llvm_unreachable("unhandled case in switch"); +} + +static void CheckC23ConstexprInitStringLiteral(const StringLiteral *SE, + Sema &SemaRef, QualType &TT) { + assert(SemaRef.getLangOpts().C23); + // character that string literal contains fits into TT - target type. + const ArrayType *AT = SemaRef.Context.getAsArrayType(TT); + QualType CharType = AT->getElementType(); + uint32_t BitWidth = SemaRef.Context.getTypeSize(CharType); + bool isUnsigned = CharType->isUnsignedIntegerType(); + llvm::APSInt Value(BitWidth, isUnsigned); + for (unsigned I = 0, N = SE->getLength(); I != N; ++I) { + int64_t C = SE->getCodeUnitS(I, SemaRef.Context.getCharWidth()); + Value = C; + if (Value != C) { + SemaRef.Diag(SemaRef.getLocationOfStringLiteralByte(SE, I), + diag::err_c23_constexpr_init_not_representable) + << C << CharType; + return; + } + } + return; +} + //===----------------------------------------------------------------------===// // Initialization helper functions //===----------------------------------------------------------------------===// diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 7d38043..a03f3ea 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -332,7 +332,8 @@ static const Expr *IgnoreNarrowingConversion(ASTContext &Ctx, NarrowingKind StandardConversionSequence::getNarrowingKind( ASTContext &Ctx, const Expr *Converted, APValue &ConstantValue, QualType &ConstantType, bool IgnoreFloatToIntegralConversion) const { - assert(Ctx.getLangOpts().CPlusPlus && "narrowing check outside C++"); + assert((Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23) && + "narrowing check outside C++"); // C++11 [dcl.init.list]p7: // A narrowing conversion is an implicit conversion ... @@ -414,20 +415,41 @@ NarrowingKind StandardConversionSequence::getNarrowingKind( if (Initializer->isValueDependent()) return NK_Dependent_Narrowing; - if (Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)) { + Expr::EvalResult R; + if ((Ctx.getLangOpts().C23 && Initializer->EvaluateAsRValue(R, Ctx)) || + Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)) { // Constant! + if (Ctx.getLangOpts().C23) + ConstantValue = R.Val; assert(ConstantValue.isFloat()); llvm::APFloat FloatVal = ConstantValue.getFloat(); // Convert the source value into the target type. bool ignored; - llvm::APFloat::opStatus ConvertStatus = FloatVal.convert( - Ctx.getFloatTypeSemantics(ToType), - llvm::APFloat::rmNearestTiesToEven, &ignored); - // If there was no overflow, the source value is within the range of - // values that can be represented. - if (ConvertStatus & llvm::APFloat::opOverflow) { - ConstantType = Initializer->getType(); - return NK_Constant_Narrowing; + llvm::APFloat Converted = FloatVal; + llvm::APFloat::opStatus ConvertStatus = + Converted.convert(Ctx.getFloatTypeSemantics(ToType), + llvm::APFloat::rmNearestTiesToEven, &ignored); + Converted.convert(Ctx.getFloatTypeSemantics(FromType), + llvm::APFloat::rmNearestTiesToEven, &ignored); + if (Ctx.getLangOpts().C23) { + if (FloatVal.isNaN() && Converted.isNaN() && + !FloatVal.isSignaling() && !Converted.isSignaling()) { + // Quiet NaNs are considered the same value, regardless of + // payloads. + return NK_Not_Narrowing; + } + // For normal values, check exact equality. + if (!Converted.bitwiseIsEqual(FloatVal)) { + ConstantType = Initializer->getType(); + return NK_Constant_Narrowing; + } + } else { + // If there was no overflow, the source value is within the range of + // values that can be represented. + if (ConvertStatus & llvm::APFloat::opOverflow) { + ConstantType = Initializer->getType(); + return NK_Constant_Narrowing; + } } } else { return NK_Variable_Narrowing; @@ -494,7 +516,30 @@ NarrowingKind StandardConversionSequence::getNarrowingKind( } return NK_Not_Narrowing; } + case ICK_Complex_Real: + if (FromType->isComplexType() && !ToType->isComplexType()) + return NK_Type_Narrowing; + return NK_Not_Narrowing; + case ICK_Floating_Promotion: + if (Ctx.getLangOpts().C23) { + const Expr *Initializer = IgnoreNarrowingConversion(Ctx, Converted); + Expr::EvalResult R; + if (Initializer->EvaluateAsRValue(R, Ctx)) { + ConstantValue = R.Val; + assert(ConstantValue.isFloat()); + llvm::APFloat FloatVal = ConstantValue.getFloat(); + // C23 6.7.3p6 If the initializer has real type and a signaling NaN + // value, the unqualified versions of the type of the initializer and + // the corresponding real type of the object declared shall be + // compatible. + if (FloatVal.isNaN() && FloatVal.isSignaling()) { + ConstantType = Initializer->getType(); + return NK_Constant_Narrowing; + } + } + } + return NK_Not_Narrowing; default: // Other kinds of conversions are not narrowings. return NK_Not_Narrowing; |