Skip to content

Commit 3a31970

Browse files
committed
[C2x] Implement support for nullptr and nullptr_t
This introduces support for nullptr and nullptr_t in C2x mode. The proposal accepted by WG14 is: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm Note, there are quite a few incompatibilities with the C++ feature in some of the edge cases of this feature. Therefore, there are some FIXME comments in tests for testing behavior that might change after WG14 has resolved national body comments (a process we've not yet started). So this implementation might change slightly depending on the resolution of comments. This is called out explicitly in the release notes as well. Differential Revision: https://reviews.llvm.org/D135099
1 parent 1af7541 commit 3a31970

21 files changed

+481
-55
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,32 @@ C2x Feature Support
452452
typeof(Val) OtherVal; // type is '__attribute__((address_space(1))) const _Atomic int'
453453
typeof_unqual(Val) OtherValUnqual; // type is 'int'
454454
455+
- Implemented `WG14 N3042 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3042.htm>`_,
456+
Introduce the nullptr constant. This introduces a new type ``nullptr_t``,
457+
declared in ``<stddef.h>`` which represents the type of the null pointer named
458+
constant, ``nullptr``. This constant is implicitly convertible to any pointer
459+
type and represents a type-safe null value.
460+
461+
Note, there are some known incompatibilities with this same feature in C++.
462+
The following examples were discovered during implementation and are subject
463+
to change depending on how national body comments are resolved by WG14 (C
464+
status is based on standard requirements, not necessarily implementation
465+
behavior):
466+
467+
.. code-block:: c
468+
469+
nullptr_t null_val;
470+
(nullptr_t)nullptr; // Rejected in C, accepted in C++, Clang accepts
471+
(void)(1 ? nullptr : 0); // Rejected in C, accepted in C++, Clang rejects
472+
(void)(1 ? null_val : 0); // Rejected in C, accepted in C++, Clang rejects
473+
bool b1 = nullptr; // Accepted in C, rejected in C++, Clang rejects
474+
b1 = null_val; // Accepted in C, rejected in C++, Clang rejects
475+
null_val = 0; // Rejected in C, accepted in C++, Clang rejects
476+
477+
void func(nullptr_t);
478+
func(0); // Rejected in C, accepted in C++, Clang rejects
479+
480+
455481
C++ Language Changes in Clang
456482
-----------------------------
457483
- Implemented DR692, DR1395 and DR1432. Use the ``-fclang-abi-compat=15`` option

clang/include/clang/AST/ExprCXX.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,8 @@ class CXXBoolLiteralExpr : public Expr {
761761
/// The null pointer literal (C++11 [lex.nullptr])
762762
///
763763
/// Introduced in C++11, the only literal of type \c nullptr_t is \c nullptr.
764+
/// This also implements the null pointer literal in C2x (C2x 6.4.1) which is
765+
/// intended to have the same semantics as the feature in C++.
764766
class CXXNullPtrLiteralExpr : public Expr {
765767
public:
766768
CXXNullPtrLiteralExpr(QualType Ty, SourceLocation Loc)

clang/include/clang/AST/PrettyPrinter.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ struct PrintingPolicy {
6565
SuppressStrongLifetime(false), SuppressLifetimeQualifiers(false),
6666
SuppressTemplateArgsInCXXConstructors(false),
6767
SuppressDefaultTemplateArgs(true), Bool(LO.Bool),
68-
Nullptr(LO.CPlusPlus11), Restrict(LO.C99), Alignof(LO.CPlusPlus11),
68+
Nullptr(LO.CPlusPlus11 || LO.C2x), NullptrTypeInNamespace(LO.CPlusPlus),
69+
Restrict(LO.C99), Alignof(LO.CPlusPlus11),
6970
UnderscoreAlignof(LO.C11), UseVoidForZeroParams(!LO.CPlusPlus),
7071
SplitTemplateClosers(!LO.CPlusPlus11), TerseOutput(false),
7172
PolishForDeclaration(false), Half(LO.Half),
@@ -196,6 +197,9 @@ struct PrintingPolicy {
196197
/// constant.
197198
unsigned Nullptr : 1;
198199

200+
/// Whether 'nullptr_t' is in namespace 'std' or not.
201+
unsigned NullptrTypeInNamespace : 1;
202+
199203
/// Whether we can use 'restrict' rather than '__restrict'.
200204
unsigned Restrict : 1;
201205

clang/include/clang/AST/Type.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2223,7 +2223,8 @@ class alignas(8) Type : public ExtQualsTypeCommonBase {
22232223
bool isObjCARCBridgableType() const;
22242224
bool isCARCBridgableType() const;
22252225
bool isTemplateTypeParmType() const; // C++ template type parameter
2226-
bool isNullPtrType() const; // C++11 std::nullptr_t
2226+
bool isNullPtrType() const; // C++11 std::nullptr_t or
2227+
// C2x nullptr_t
22272228
bool isNothrowT() const; // C++ std::nothrow_t
22282229
bool isAlignValT() const; // C++17 std::align_val_t
22292230
bool isStdByteType() const; // C++17 std::byte

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,11 @@ def warn_cxx98_compat_noexcept_expr : Warning<
702702
InGroup<CXX98Compat>, DefaultIgnore;
703703
def warn_cxx98_compat_nullptr : Warning<
704704
"'nullptr' is incompatible with C++98">, InGroup<CXX98Compat>, DefaultIgnore;
705+
def ext_c_nullptr : Extension<
706+
"'nullptr' is a C2x extension">, InGroup<C2x>;
707+
def warn_c17_compat_nullptr : Warning<
708+
"'nullptr' is incompatible with C standards before C2x">,
709+
InGroup<CPre2xCompat>, DefaultIgnore;
705710

706711
def warn_wrong_clang_attr_namespace : Warning<
707712
"'__clang__' is a predefined macro name, not an attribute scope specifier; "

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8694,6 +8694,9 @@ def warn_cast_function_type : Warning<
86948694
InGroup<CastFunctionType>, DefaultIgnore;
86958695
def err_cast_pointer_to_non_pointer_int : Error<
86968696
"pointer cannot be cast to type %0">;
8697+
def err_nullptr_cast : Error<
8698+
"cannot cast an object of type %select{'nullptr_t' to %1|%1 to 'nullptr_t'}0"
8699+
>;
86978700
def err_cast_to_bfloat16 : Error<"cannot type-cast to __bf16">;
86988701
def err_cast_from_bfloat16 : Error<"cannot type-cast from __bf16">;
86998702
def err_typecheck_expect_scalar_operand : Error<

clang/include/clang/Basic/TokenKinds.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ CXX11_KEYWORD(char32_t , KEYNOMS18)
390390
CXX11_KEYWORD(constexpr , 0)
391391
CXX11_KEYWORD(decltype , 0)
392392
CXX11_KEYWORD(noexcept , 0)
393-
CXX11_KEYWORD(nullptr , 0)
393+
CXX11_KEYWORD(nullptr , KEYC2X)
394394
CXX11_KEYWORD(static_assert , KEYMSCOMPAT|KEYC2X)
395395
CXX11_KEYWORD(thread_local , KEYC2X)
396396

clang/lib/AST/Expr.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3930,7 +3930,7 @@ Expr::isNullPointerConstant(ASTContext &Ctx,
39303930
if (getType().isNull())
39313931
return NPCK_NotNull;
39323932

3933-
// C++11 nullptr_t is always a null pointer constant.
3933+
// C++11/C2x nullptr_t is always a null pointer constant.
39343934
if (getType()->isNullPtrType())
39353935
return NPCK_CXX11_nullptr;
39363936

clang/lib/AST/Type.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3084,7 +3084,7 @@ StringRef BuiltinType::getName(const PrintingPolicy &Policy) const {
30843084
case Char32:
30853085
return "char32_t";
30863086
case NullPtr:
3087-
return "std::nullptr_t";
3087+
return Policy.NullptrTypeInNamespace ? "std::nullptr_t" : "nullptr_t";
30883088
case Overload:
30893089
return "<overloaded function type>";
30903090
case BoundMember:

clang/lib/Headers/stddef.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ using ::std::nullptr_t;
9797
#undef __need_NULL
9898
#endif /* defined(__need_NULL) */
9999

100+
/* FIXME: This is using the placeholder dates Clang produces for these macros
101+
in C2x mode; switch to the correct values once they've been published. */
102+
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L
103+
typedef typeof(nullptr) nullptr_t;
104+
#endif /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L */
105+
100106
#if defined(__need_STDDEF_H_misc)
101107
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) || \
102108
(defined(__cplusplus) && __cplusplus >= 201103L)

clang/lib/Parse/ParseExpr.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,12 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
10031003
break;
10041004

10051005
case tok::kw_nullptr:
1006-
Diag(Tok, diag::warn_cxx98_compat_nullptr);
1006+
if (getLangOpts().CPlusPlus)
1007+
Diag(Tok, diag::warn_cxx98_compat_nullptr);
1008+
else
1009+
Diag(Tok, getLangOpts().C2x ? diag::warn_c17_compat_nullptr
1010+
: diag::ext_c_nullptr);
1011+
10071012
Res = Actions.ActOnCXXNullPtrLiteral(ConsumeToken());
10081013
break;
10091014

clang/lib/Sema/SemaCast.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2985,6 +2985,37 @@ void CastOperation::CheckCStyleCast() {
29852985
return;
29862986
}
29872987

2988+
// C2x 6.5.4p4:
2989+
// The type nullptr_t shall not be converted to any type other than void,
2990+
// bool, or a pointer type. No type other than nullptr_t shall be converted
2991+
// to nullptr_t.
2992+
if (SrcType->isNullPtrType()) {
2993+
// FIXME: 6.3.2.4p2 says that nullptr_t can be converted to itself, but
2994+
// 6.5.4p4 is a constraint check and nullptr_t is not void, bool, or a
2995+
// pointer type. We're not going to diagnose that as a constraint violation.
2996+
if (!DestType->isVoidType() && !DestType->isBooleanType() &&
2997+
!DestType->isPointerType() && !DestType->isNullPtrType()) {
2998+
Self.Diag(SrcExpr.get()->getExprLoc(), diag::err_nullptr_cast)
2999+
<< /*nullptr to type*/ 0 << DestType;
3000+
SrcExpr = ExprError();
3001+
return;
3002+
}
3003+
if (!DestType->isNullPtrType()) {
3004+
// Implicitly cast from the null pointer type to the type of the
3005+
// destination.
3006+
CastKind CK = DestType->isPointerType() ? CK_NullToPointer : CK_BitCast;
3007+
SrcExpr = ImplicitCastExpr::Create(Self.Context, DestType, CK,
3008+
SrcExpr.get(), nullptr, VK_PRValue,
3009+
Self.CurFPFeatureOverrides());
3010+
}
3011+
}
3012+
if (DestType->isNullPtrType() && !SrcType->isNullPtrType()) {
3013+
Self.Diag(SrcExpr.get()->getExprLoc(), diag::err_nullptr_cast)
3014+
<< /*type to nullptr*/ 1 << SrcType;
3015+
SrcExpr = ExprError();
3016+
return;
3017+
}
3018+
29883019
if (DestType->isExtVectorType()) {
29893020
SrcExpr = Self.CheckExtVectorCast(OpRange, DestType, SrcExpr.get(), Kind);
29903021
return;

clang/lib/Sema/SemaCodeComplete.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,6 +2645,13 @@ static void AddOrdinaryNameResults(Sema::ParserCompletionContext CCC, Scope *S,
26452645
Results.AddResult(Result(Builder.TakeString()));
26462646
}
26472647

2648+
if (SemaRef.getLangOpts().C2x) {
2649+
// nullptr
2650+
Builder.AddResultTypeChunk("nullptr_t");
2651+
Builder.AddTypedTextChunk("nullptr");
2652+
Results.AddResult(Result(Builder.TakeString()));
2653+
}
2654+
26482655
// sizeof expression
26492656
Builder.AddResultTypeChunk("size_t");
26502657
Builder.AddTypedTextChunk("sizeof");

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16780,11 +16780,10 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
1678016780
AllowFoldKind FoldKind = NoFold;
1678116781

1678216782
if (!getLangOpts().CPlusPlus) {
16783-
// In C mode only allow folding and strip the implicit conversion
16784-
// to the type of the first _Static_assert argument that would
16785-
// otherwise suppress diagnostics for arguments that convert to int.
16783+
// In C mode, allow folding as an extension for better compatibility with
16784+
// C++ in terms of expressions like static_assert("test") or
16785+
// static_assert(nullptr).
1678616786
FoldKind = AllowFold;
16787-
BaseExpr = BaseExpr->IgnoreImpCasts();
1678816787
}
1678916788

1679016789
if (!Failed && VerifyIntegerConstantExpression(

clang/lib/Sema/SemaExpr.cpp

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8724,6 +8724,12 @@ QualType Sema::CheckConditionalOperands(ExprResult &Cond, ExprResult &LHS,
87248724
return ResTy;
87258725
}
87268726

8727+
// C2x 6.5.15p7:
8728+
// ... if both the second and third operands have nullptr_t type, the
8729+
// result also has that type.
8730+
if (LHSTy->isNullPtrType() && Context.hasSameType(LHSTy, RHSTy))
8731+
return ResTy;
8732+
87278733
// C99 6.5.15p6 - "if one operand is a null pointer constant, the result has
87288734
// the type of the other operand."
87298735
if (!checkConditionalNullPointer(*this, RHS, LHSTy)) return LHSTy;
@@ -9981,6 +9987,24 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
99819987
return Incompatible;
99829988
}
99839989

9990+
// This check seems unnatural, however it is necessary to ensure the proper
9991+
// conversion of functions/arrays. If the conversion were done for all
9992+
// DeclExpr's (created by ActOnIdExpression), it would mess up the unary
9993+
// expressions that suppress this implicit conversion (&, sizeof). This needs
9994+
// to happen before we check for null pointer conversions because C does not
9995+
// undergo the same implicit conversions as C++ does above (by the calls to
9996+
// TryImplicitConversion() and PerformImplicitConversion()) which insert the
9997+
// lvalue to rvalue cast before checking for null pointer constraints. This
9998+
// addresses code like: nullptr_t val; int *ptr; ptr = val;
9999+
//
10000+
// Suppress this for references: C++ 8.5.3p5.
10001+
if (!LHSType->isReferenceType()) {
10002+
// FIXME: We potentially allocate here even if ConvertRHS is false.
10003+
RHS = DefaultFunctionArrayLvalueConversion(RHS.get(), Diagnose);
10004+
if (RHS.isInvalid())
10005+
return Incompatible;
10006+
}
10007+
998410008
// C99 6.5.16.1p1: the left operand is a pointer and the right is
998510009
// a null pointer constant.
998610010
if ((LHSType->isPointerType() || LHSType->isObjCObjectPointerType() ||
@@ -10005,18 +10029,6 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
1000510029
return Compatible;
1000610030
}
1000710031

10008-
// This check seems unnatural, however it is necessary to ensure the proper
10009-
// conversion of functions/arrays. If the conversion were done for all
10010-
// DeclExpr's (created by ActOnIdExpression), it would mess up the unary
10011-
// expressions that suppress this implicit conversion (&, sizeof).
10012-
//
10013-
// Suppress this for references: C++ 8.5.3p5.
10014-
if (!LHSType->isReferenceType()) {
10015-
// FIXME: We potentially allocate here even if ConvertRHS is false.
10016-
RHS = DefaultFunctionArrayLvalueConversion(RHS.get(), Diagnose);
10017-
if (RHS.isInvalid())
10018-
return Incompatible;
10019-
}
1002010032
CastKind Kind;
1002110033
Sema::AssignConvertType result =
1002210034
CheckAssignmentConstraints(LHSType, RHS, Kind, ConvertRHS);
@@ -12571,34 +12583,54 @@ QualType Sema::CheckCompareOperands(ExprResult &LHS, ExprResult &RHS,
1257112583
return computeResultTy();
1257212584
}
1257312585

12574-
if (getLangOpts().CPlusPlus) {
12575-
// C++ [expr.eq]p4:
12576-
// Two operands of type std::nullptr_t or one operand of type
12577-
// std::nullptr_t and the other a null pointer constant compare equal.
12578-
if (!IsOrdered && LHSIsNull && RHSIsNull) {
12579-
if (LHSType->isNullPtrType()) {
12580-
RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer);
12581-
return computeResultTy();
12582-
}
12583-
if (RHSType->isNullPtrType()) {
12584-
LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer);
12585-
return computeResultTy();
12586-
}
12587-
}
1258812586

12589-
// Comparison of Objective-C pointers and block pointers against nullptr_t.
12590-
// These aren't covered by the composite pointer type rules.
12591-
if (!IsOrdered && RHSType->isNullPtrType() &&
12592-
(LHSType->isObjCObjectPointerType() || LHSType->isBlockPointerType())) {
12587+
// C++ [expr.eq]p4:
12588+
// Two operands of type std::nullptr_t or one operand of type
12589+
// std::nullptr_t and the other a null pointer constant compare
12590+
// equal.
12591+
// C2x 6.5.9p5:
12592+
// If both operands have type nullptr_t or one operand has type nullptr_t
12593+
// and the other is a null pointer constant, they compare equal.
12594+
if (!IsOrdered && LHSIsNull && RHSIsNull) {
12595+
if (LHSType->isNullPtrType()) {
1259312596
RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer);
1259412597
return computeResultTy();
1259512598
}
12596-
if (!IsOrdered && LHSType->isNullPtrType() &&
12597-
(RHSType->isObjCObjectPointerType() || RHSType->isBlockPointerType())) {
12599+
if (RHSType->isNullPtrType()) {
12600+
LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer);
12601+
return computeResultTy();
12602+
}
12603+
}
12604+
12605+
if (!getLangOpts().CPlusPlus && !IsOrdered && (LHSIsNull || RHSIsNull)) {
12606+
// C2x 6.5.9p6:
12607+
// Otherwise, at least one operand is a pointer. If one is a pointer and
12608+
// the other is a null pointer constant, the null pointer constant is
12609+
// converted to the type of the pointer.
12610+
if (LHSIsNull && RHSType->isPointerType()) {
1259812611
LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer);
1259912612
return computeResultTy();
1260012613
}
12614+
if (RHSIsNull && LHSType->isPointerType()) {
12615+
RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer);
12616+
return computeResultTy();
12617+
}
12618+
}
12619+
12620+
// Comparison of Objective-C pointers and block pointers against nullptr_t.
12621+
// These aren't covered by the composite pointer type rules.
12622+
if (!IsOrdered && RHSType->isNullPtrType() &&
12623+
(LHSType->isObjCObjectPointerType() || LHSType->isBlockPointerType())) {
12624+
RHS = ImpCastExprToType(RHS.get(), LHSType, CK_NullToPointer);
12625+
return computeResultTy();
12626+
}
12627+
if (!IsOrdered && LHSType->isNullPtrType() &&
12628+
(RHSType->isObjCObjectPointerType() || RHSType->isBlockPointerType())) {
12629+
LHS = ImpCastExprToType(LHS.get(), RHSType, CK_NullToPointer);
12630+
return computeResultTy();
12631+
}
1260112632

12633+
if (getLangOpts().CPlusPlus) {
1260212634
if (IsRelational &&
1260312635
((LHSType->isNullPtrType() && RHSType->isPointerType()) ||
1260412636
(RHSType->isNullPtrType() && LHSType->isPointerType()))) {

clang/test/C/C11/n1330.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -verify %s
1+
// RUN: %clang_cc1 -verify -Wgnu-folding-constant %s
22

33
/* WG14 N1330: Yes
44
* Static assertions
@@ -55,7 +55,7 @@ void test(void) {
5555

5656
// Ensure that only an integer constant expression can be used as the
5757
// controlling expression.
58-
_Static_assert(1.0f, "this should not compile"); // expected-error {{static assertion expression is not an integral constant expression}}
58+
_Static_assert(1.0f, "this should not compile"); // expected-warning {{expression is not an integer constant expression; folding it to a constant is a GNU extension}}
5959
}
6060

6161
// FIXME: This is using the placeholder date Clang produces for the macro in

0 commit comments

Comments
 (0)