summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Smith <richard-llvm@metafoo.co.uk>2016-06-25 00:15:56 +0000
committerRichard Smith <richard-llvm@metafoo.co.uk>2016-06-25 00:15:56 +0000
commit3c1c202adacce7479418fdb83d8257e2d9e0f125 (patch)
treeaeab1dcbc306bb445995aa1b7a5b0f70013ec6e0
parent525764918cc1cc572d5605961201068d914cc04e (diff)
Implement C++17 P0386R2, inline variables. (The 'inline' specifier gives a
variable weak discardable linkage and partially-ordered initialization, and is implied for constexpr static data members.) git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@273754 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/clang/AST/Decl.h27
-rw-r--r--include/clang/Basic/DiagnosticSemaKinds.td13
-rw-r--r--include/clang/Sema/SemaInternal.h9
-rw-r--r--lib/AST/ASTContext.cpp8
-rw-r--r--lib/AST/ASTDumper.cpp4
-rw-r--r--lib/AST/Decl.cpp30
-rw-r--r--lib/CodeGen/CodeGenModule.cpp6
-rw-r--r--lib/CodeGen/ItaniumCXXABI.cpp20
-rw-r--r--lib/Sema/Sema.cpp17
-rw-r--r--lib/Sema/SemaDecl.cpp97
-rw-r--r--lib/Sema/SemaDeclCXX.cpp3
-rw-r--r--lib/Sema/SemaDeclObjC.cpp3
-rw-r--r--lib/Sema/SemaTemplateInstantiate.cpp3
-rw-r--r--lib/Sema/SemaTemplateInstantiateDecl.cpp29
-rw-r--r--lib/Serialization/ASTReaderDecl.cpp2
-rw-r--r--lib/Serialization/ASTWriterDecl.cpp5
-rw-r--r--test/CXX/basic/basic.def/p2.cpp8
-rw-r--r--test/CXX/basic/basic.def/p4.cpp6
-rw-r--r--test/CXX/class/class.static/class.static.data/p2.cpp7
-rw-r--r--test/CXX/class/class.static/class.static.data/p3.cpp15
-rw-r--r--test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp29
-rw-r--r--test/CXX/dcl.dcl/dcl.spec/dcl.inline/p1.cpp8
-rw-r--r--test/CXX/dcl.dcl/dcl.spec/dcl.inline/p5.cpp15
-rw-r--r--test/CXX/temp/temp.spec/temp.inst/p1.cpp10
-rw-r--r--test/CodeGenCXX/cxx1z-inline-variables.cpp63
-rw-r--r--test/Sema/inline.c2
-rw-r--r--www/cxx_status.html2
27 files changed, 371 insertions, 70 deletions
diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h
index cd7d2538a1..41f48e0ba7 100644
--- a/include/clang/AST/Decl.h
+++ b/include/clang/AST/Decl.h
@@ -881,6 +881,12 @@ protected:
/// variable; see isARCPseudoStrong() for details.
unsigned ARCPseudoStrong : 1;
+ /// \brief Whether this variable is (C++1z) inline.
+ unsigned IsInline : 1;
+
+ /// \brief Whether this variable has (C++1z) inline explicitly specified.
+ unsigned IsInlineSpecified : 1;
+
/// \brief Whether this variable is (C++0x) constexpr.
unsigned IsConstexpr : 1;
@@ -1102,9 +1108,6 @@ public:
/// definition of a static data member.
bool isOutOfLine() const override;
- /// \brief If this is a static data member, find its out-of-line definition.
- VarDecl *getOutOfLineDefinition();
-
/// isFileVarDecl - Returns true for file scoped variable declaration.
bool isFileVarDecl() const {
Kind K = getKind();
@@ -1250,6 +1253,24 @@ public:
NonParmVarDeclBits.ARCPseudoStrong = ps;
}
+ /// Whether this variable is (C++1z) inline.
+ bool isInline() const {
+ return isa<ParmVarDecl>(this) ? false : NonParmVarDeclBits.IsInline;
+ }
+ bool isInlineSpecified() const {
+ return isa<ParmVarDecl>(this) ? false
+ : NonParmVarDeclBits.IsInlineSpecified;
+ }
+ void setInlineSpecified() {
+ assert(!isa<ParmVarDecl>(this));
+ NonParmVarDeclBits.IsInline = true;
+ NonParmVarDeclBits.IsInlineSpecified = true;
+ }
+ void setImplicitlyInline() {
+ assert(!isa<ParmVarDecl>(this));
+ NonParmVarDeclBits.IsInline = true;
+ }
+
/// Whether this variable is (C++11) constexpr.
bool isConstexpr() const {
return isa<ParmVarDecl>(this) ? false : NonParmVarDeclBits.IsConstexpr;
diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index 44d59dc537..a4d39f4fbf 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -339,12 +339,16 @@ def err_language_linkage_spec_not_ascii : Error<
def warn_use_out_of_scope_declaration : Warning<
"use of out-of-scope declaration of %0">;
def err_inline_non_function : Error<
- "'inline' can only appear on functions">;
+ "'inline' can only appear on functions%select{| and non-local variables}0">;
def err_noreturn_non_function : Error<
"'_Noreturn' can only appear on functions">;
def warn_qual_return_type : Warning<
"'%0' type qualifier%s1 on return type %plural{1:has|:have}1 no effect">,
InGroup<IgnoredQualifiers>, DefaultIgnore;
+def warn_deprecated_redundant_constexpr_static_def : Warning<
+ "out-of-line definition of constexpr static data member is redundant "
+ "in C++17 and is deprecated">,
+ InGroup<Deprecated>, DefaultIgnore;
def warn_decl_shadow :
Warning<"declaration shadows a %select{"
@@ -1086,6 +1090,12 @@ def warn_cxx14_compat_static_assert_no_message : Warning<
"static_assert with no message is incompatible with C++ standards before C++1z">,
DefaultIgnore, InGroup<CXXPre1zCompat>;
+def ext_inline_variable : ExtWarn<
+ "inline variables are a C++1z extension">, InGroup<CXX1z>;
+def warn_cxx14_compat_inline_variable : Warning<
+ "inline variables are incompatible with C++ standards before C++1z">,
+ DefaultIgnore, InGroup<CXXPre1zCompat>;
+
def warn_inline_namespace_reopened_noninline : Warning<
"inline namespace cannot be reopened as a non-inline namespace">;
def err_inline_namespace_mismatch : Error<
@@ -4305,6 +4315,7 @@ def warn_undefined_internal : Warning<
InGroup<DiagGroup<"undefined-internal">>;
def warn_undefined_inline : Warning<"inline function %q0 is not defined">,
InGroup<DiagGroup<"undefined-inline">>;
+def err_undefined_inline_var : Error<"inline variable %q0 is not defined">;
def note_used_here : Note<"used here">;
def err_internal_linkage_redeclaration : Error<
diff --git a/include/clang/Sema/SemaInternal.h b/include/clang/Sema/SemaInternal.h
index ffe69717d3..76567f3b77 100644
--- a/include/clang/Sema/SemaInternal.h
+++ b/include/clang/Sema/SemaInternal.h
@@ -73,10 +73,11 @@ inline void MarkVarDeclODRUsed(VarDecl *Var,
// Keep track of used but undefined variables.
// FIXME: We shouldn't suppress this warning for static data members.
if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly &&
- !Var->isExternallyVisible() &&
- !(Var->isStaticDataMember() && Var->hasInit())) {
- SourceLocation &old = SemaRef.UndefinedButUsed[Var->getCanonicalDecl()];
- if (old.isInvalid()) old = Loc;
+ (!Var->isExternallyVisible() || Var->isInline()) &&
+ !(Var->isStaticDataMember() && Var->hasInit())) {
+ SourceLocation &old = SemaRef.UndefinedButUsed[Var->getCanonicalDecl()];
+ if (old.isInvalid())
+ old = Loc;
}
QualType CaptureType, DeclRefType;
SemaRef.tryCaptureVariable(Var, Loc, Sema::TryCapture_Implicit,
diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp
index 572cebc681..276e9873ea 100644
--- a/lib/AST/ASTContext.cpp
+++ b/lib/AST/ASTContext.cpp
@@ -8486,15 +8486,19 @@ static GVALinkage basicGVALinkageForVariable(const ASTContext &Context,
if (Context.isMSStaticDataMemberInlineDefinition(VD))
return GVA_DiscardableODR;
+ GVALinkage StrongLinkage = GVA_StrongExternal;
+ if (VD->isInline())
+ StrongLinkage = GVA_DiscardableODR;
+
switch (VD->getTemplateSpecializationKind()) {
case TSK_Undeclared:
- return GVA_StrongExternal;
+ return StrongLinkage;
case TSK_ExplicitSpecialization:
return Context.getTargetInfo().getCXXABI().isMicrosoft() &&
VD->isStaticDataMember()
? GVA_StrongODR
- : GVA_StrongExternal;
+ : StrongLinkage;
case TSK_ExplicitInstantiationDefinition:
return GVA_StrongODR;
diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp
index fc206b05c1..60420c71fc 100644
--- a/lib/AST/ASTDumper.cpp
+++ b/lib/AST/ASTDumper.cpp
@@ -1196,6 +1196,10 @@ void ASTDumper::VisitVarDecl(const VarDecl *D) {
OS << " __module_private__";
if (D->isNRVOVariable())
OS << " nrvo";
+ if (D->isInline())
+ OS << " inline";
+ if (D->isConstexpr())
+ OS << " constexpr";
if (D->hasInit()) {
switch (D->getInitStyle()) {
case VarDecl::CInit: OS << " cinit"; break;
diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp
index d59c4b422b..e94d7b93f7 100644
--- a/lib/AST/Decl.cpp
+++ b/lib/AST/Decl.cpp
@@ -592,12 +592,14 @@ static LinkageInfo getLVForNamespaceScopeDecl(const NamedDecl *D,
if (Var->getStorageClass() == SC_Static)
return LinkageInfo::internal();
- // - a non-volatile object or reference that is explicitly declared const
- // or constexpr and neither explicitly declared extern nor previously
- // declared to have external linkage; or (there is no equivalent in C99)
+ // - a non-inline, non-volatile object or reference that is explicitly
+ // declared const or constexpr and neither explicitly declared extern
+ // nor previously declared to have external linkage; or (there is no
+ // equivalent in C99)
if (Context.getLangOpts().CPlusPlus &&
Var->getType().isConstQualified() &&
- !Var->getType().isVolatileQualified()) {
+ !Var->getType().isVolatileQualified() &&
+ !Var->isInline()) {
const VarDecl *PrevVar = Var->getPreviousDecl();
if (PrevVar)
return getLVForDecl(PrevVar, computation);
@@ -1912,7 +1914,9 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
// C++ [basic.def]p2:
// A declaration is a definition unless [...] it contains the 'extern'
// specifier or a linkage-specification and neither an initializer [...],
- // it declares a static data member in a class declaration [...].
+ // it declares a non-inline static data member in a class declaration [...],
+ // it declares a static data member outside a class definition and the variable
+ // was defined within the class with the constexpr specifier [...],
// C++1y [temp.expl.spec]p15:
// An explicit specialization of a static data member or an explicit
// specialization of a static data member template is a definition if the
@@ -1922,6 +1926,8 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
// a static data member template outside the containing class?
if (isStaticDataMember()) {
if (isOutOfLine() &&
+ !(getCanonicalDecl()->isInline() &&
+ getCanonicalDecl()->isConstexpr()) &&
(hasInit() ||
// If the first declaration is out-of-line, this may be an
// instantiation of an out-of-line partial specialization of a variable
@@ -1932,6 +1938,8 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
TSK_ExplicitSpecialization) ||
isa<VarTemplatePartialSpecializationDecl>(this)))
return Definition;
+ else if (!isOutOfLine() && isInline())
+ return Definition;
else
return DeclarationOnly;
}
@@ -2072,18 +2080,6 @@ bool VarDecl::isOutOfLine() const {
return false;
}
-VarDecl *VarDecl::getOutOfLineDefinition() {
- if (!isStaticDataMember())
- return nullptr;
-
- for (auto RD : redecls()) {
- if (RD->getLexicalDeclContext()->isFileContext())
- return RD;
- }
-
- return nullptr;
-}
-
void VarDecl::setInit(Expr *I) {
if (auto *Eval = Init.dyn_cast<EvaluatedStmt *>()) {
Eval->~EvaluatedStmt();
diff --git a/lib/CodeGen/CodeGenModule.cpp b/lib/CodeGen/CodeGenModule.cpp
index 02858b3864..ac00bf85cd 100644
--- a/lib/CodeGen/CodeGenModule.cpp
+++ b/lib/CodeGen/CodeGenModule.cpp
@@ -3746,6 +3746,12 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
case Decl::Namespace:
EmitNamespace(cast<NamespaceDecl>(D));
break;
+ case Decl::CXXRecord:
+ // Emit any static data members, they may be definitions.
+ for (auto *I : cast<CXXRecordDecl>(D)->decls())
+ if (isa<VarDecl>(I) || isa<CXXRecordDecl>(I))
+ EmitTopLevelDecl(I);
+ break;
// No code generation needed.
case Decl::UsingShadow:
case Decl::ClassTemplate:
diff --git a/lib/CodeGen/ItaniumCXXABI.cpp b/lib/CodeGen/ItaniumCXXABI.cpp
index b33ef73fa4..43d293a2c7 100644
--- a/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/lib/CodeGen/ItaniumCXXABI.cpp
@@ -1905,10 +1905,18 @@ void ItaniumCXXABI::EmitGuardedInit(CodeGenFunction &CGF,
bool shouldPerformInit) {
CGBuilderTy &Builder = CGF.Builder;
- // We only need to use thread-safe statics for local non-TLS variables;
- // global initialization is always single-threaded.
+ // Inline variables that weren't instantiated from variable templates have
+ // partially-ordered initialization within their translation unit.
+ bool NonTemplateInline =
+ D.isInline() &&
+ !isTemplateInstantiation(D.getTemplateSpecializationKind());
+
+ // We only need to use thread-safe statics for local non-TLS variables and
+ // inline variables; other global initialization is always single-threaded
+ // or (through lazy dynamic loading in multiple threads) unsequenced.
bool threadsafe = getContext().getLangOpts().ThreadsafeStatics &&
- D.isLocalVarDecl() && !D.getTLSKind();
+ (D.isLocalVarDecl() || NonTemplateInline) &&
+ !D.getTLSKind();
// If we have a global variable with internal linkage and thread-safe statics
// are disabled, we can just let the guard variable be of type i8.
@@ -1962,7 +1970,11 @@ void ItaniumCXXABI::EmitGuardedInit(CodeGenFunction &CGF,
if (!D.isLocalVarDecl() && C &&
CGM.getTarget().getTriple().isOSBinFormatELF()) {
guard->setComdat(C);
- CGF.CurFn->setComdat(C);
+ // An inline variable's guard function is run from the per-TU
+ // initialization function, not via a dedicated global ctor function, so
+ // we can't put it in a comdat.
+ if (!NonTemplateInline)
+ CGF.CurFn->setComdat(C);
} else if (CGM.supportsCOMDAT() && guard->isWeakForLinker()) {
guard->setComdat(CGM.getModule().getOrInsertComdat(guard->getName()));
}
diff --git a/lib/Sema/Sema.cpp b/lib/Sema/Sema.cpp
index d7697bfc9b..a87f13d7e6 100644
--- a/lib/Sema/Sema.cpp
+++ b/lib/Sema/Sema.cpp
@@ -469,7 +469,8 @@ static bool ShouldRemoveFromUnused(Sema *SemaRef, const DeclaratorDecl *D) {
return false;
}
-/// Obtains a sorted list of functions that are undefined but ODR-used.
+/// Obtains a sorted list of functions and variables that are undefined but
+/// ODR-used.
void Sema::getUndefinedButUsed(
SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> > &Undefined) {
for (const auto &UndefinedUse : UndefinedButUsed) {
@@ -488,9 +489,10 @@ void Sema::getUndefinedButUsed(
!FD->getMostRecentDecl()->isInlined())
continue;
} else {
- if (cast<VarDecl>(ND)->hasDefinition() != VarDecl::DeclarationOnly)
+ auto *VD = cast<VarDecl>(ND);
+ if (VD->hasDefinition() != VarDecl::DeclarationOnly)
continue;
- if (ND->isExternallyVisible())
+ if (VD->isExternallyVisible() && !VD->getMostRecentDecl()->isInline())
continue;
}
@@ -522,10 +524,15 @@ static void checkUndefinedButUsed(Sema &S) {
if (!ND->isExternallyVisible()) {
S.Diag(ND->getLocation(), diag::warn_undefined_internal)
<< isa<VarDecl>(ND) << ND;
- } else {
- assert(cast<FunctionDecl>(ND)->getMostRecentDecl()->isInlined() &&
+ } else if (auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ assert(FD->getMostRecentDecl()->isInlined() &&
"used object requires definition but isn't inline or internal?");
+ // FIXME: This is ill-formed; we should reject.
S.Diag(ND->getLocation(), diag::warn_undefined_inline) << ND;
+ } else {
+ assert(cast<VarDecl>(ND)->getMostRecentDecl()->isInline() &&
+ "used var requires definition but isn't inline or internal?");
+ S.Diag(ND->getLocation(), diag::err_undefined_inline_var) << ND;
}
if (I->second.isValid())
S.Diag(I->second, diag::note_used_here);
diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp
index c46e39dc9a..0567a59f8d 100644
--- a/lib/Sema/SemaDecl.cpp
+++ b/lib/Sema/SemaDecl.cpp
@@ -1457,6 +1457,9 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const {
if (VD->isStaticDataMember() &&
VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
return false;
+
+ if (VD->isInline() && !isMainFileLoc(*this, VD->getLocation()))
+ return false;
} else {
return false;
}
@@ -3621,6 +3624,23 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
return New->setInvalidDecl();
}
+ if (New->isInline() && !Old->getMostRecentDecl()->isInline()) {
+ if (VarDecl *Def = Old->getDefinition()) {
+ // C++1z [dcl.fcn.spec]p4:
+ // If the definition of a variable appears in a translation unit before
+ // its first declaration as inline, the program is ill-formed.
+ Diag(New->getLocation(), diag::err_inline_decl_follows_def) << New;
+ Diag(Def->getLocation(), diag::note_previous_definition);
+ }
+ }
+
+ // If this redeclaration makes the function inline, we may need to add it to
+ // UndefinedButUsed.
+ if (!Old->isInline() && New->isInline() && Old->isUsed(false) &&
+ !Old->getDefinition() && !New->isThisDeclarationADefinition())
+ UndefinedButUsed.insert(std::make_pair(Old->getCanonicalDecl(),
+ SourceLocation()));
+
if (New->getTLSKind() != Old->getTLSKind()) {
if (!Old->getTLSKind()) {
Diag(New->getLocation(), diag::err_thread_non_thread) << New->getDeclName();
@@ -3652,6 +3672,12 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
New->getDeclContext()->isDependentContext())) {
// The previous definition is hidden, and multiple definitions are
// permitted (in separate TUs). Form another definition of it.
+ } else if (Old->isStaticDataMember() &&
+ Old->getCanonicalDecl()->isInline() &&
+ Old->getCanonicalDecl()->isConstexpr()) {
+ // This definition won't be a definition any more once it's been merged.
+ Diag(New->getLocation(),
+ diag::warn_deprecated_redundant_constexpr_static_def);
} else {
Diag(New->getLocation(), diag::err_redefinition) << New;
Diag(Def->getLocation(), diag::note_previous_definition);
@@ -3680,6 +3706,9 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
New->setAccess(Old->getAccess());
if (NewTemplate)
NewTemplate->setAccess(New->getAccess());
+
+ if (Old->isInline())
+ New->setImplicitlyInline();
}
/// ParsedFreeStandingDeclSpec - This method is invoked when a declspec with
@@ -3836,6 +3865,10 @@ Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS, DeclSpec &DS,
<< DS.getSourceRange();
}
+ if (DS.isInlineSpecified())
+ Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
+
if (DS.isConstexprSpecified()) {
// C++0x [dcl.constexpr]p1: constexpr can only be applied to declarations
// and definitions of functions and variables.
@@ -5261,11 +5294,7 @@ NamedDecl *Sema::findLocallyScopedExternCDecl(DeclarationName Name) {
/// does not identify a function.
void Sema::DiagnoseFunctionSpecifiers(const DeclSpec &DS) {
// FIXME: We should probably indicate the identifier in question to avoid
- // confusion for constructs like "inline int a(), b;"
- if (DS.isInlineSpecified())
- Diag(DS.getInlineSpecLoc(),
- diag::err_inline_non_function);
-
+ // confusion for constructs like "virtual int a(), b;"
if (DS.isVirtualSpecified())
Diag(DS.getVirtualSpecLoc(),
diag::err_virtual_non_function);
@@ -5294,6 +5323,9 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC,
DiagnoseFunctionSpecifiers(D.getDeclSpec());
+ if (D.getDeclSpec().isInlineSpecified())
+ Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
if (D.getDeclSpec().isConstexprSpecified())
Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_invalid_constexpr)
<< 1;
@@ -6098,8 +6130,14 @@ Sema::ActOnVariableDeclarator(Scope *S, Declarator &D, DeclContext *DC,
NewVD->setTemplateParameterListsInfo(
Context, TemplateParamLists.drop_back(VDTemplateParamLists));
- if (D.getDeclSpec().isConstexprSpecified())
+ if (D.getDeclSpec().isConstexprSpecified()) {
NewVD->setConstexpr(true);
+ // C++1z [dcl.spec.constexpr]p1:
+ // A static data member declared with the constexpr specifier is
+ // implicitly an inline variable.
+ if (NewVD->isStaticDataMember() && getLangOpts().CPlusPlus1z)
+ NewVD->setImplicitlyInline();
+ }
if (D.getDeclSpec().isConceptSpecified()) {
if (VarTemplateDecl *VTD = NewVD->getDescribedVarTemplate())
@@ -6142,6 +6180,20 @@ Sema::ActOnVariableDeclarator(Scope *S, Declarator &D, DeclContext *DC,
}
}
+ if (D.getDeclSpec().isInlineSpecified()) {
+ if (CurContext->isFunctionOrMethod()) {
+ // 'inline' is not allowed on block scope variable declaration.
+ Diag(D.getDeclSpec().getInlineSpecLoc(),
+ diag::err_inline_declaration_block_scope) << Name
+ << FixItHint::CreateRemoval(D.getDeclSpec().getInlineSpecLoc());
+ } else {
+ Diag(D.getDeclSpec().getInlineSpecLoc(),
+ getLangOpts().CPlusPlus1z ? diag::warn_cxx14_compat_inline_variable
+ : diag::ext_inline_variable);
+ NewVD->setInlineSpecified();
+ }
+ }
+
// Set the lexical context. If the declarator has a C++ scope specifier, the
// lexical context will be different from the semantic context.
NewVD->setLexicalDeclContext(CurContext);
@@ -9759,7 +9811,7 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
diag::ext_aggregate_init_not_constant)
<< Culprit->getSourceRange();
}
- } else if (VDecl->isStaticDataMember() &&
+ } else if (VDecl->isStaticDataMember() && !VDecl->isInline() &&
VDecl->getLexicalDeclContext()->isRecord()) {
// This is an in-class initialization for a static data member, e.g.,
//
@@ -9773,8 +9825,8 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
// const enumeration type, see 9.4.2.
//
// C++11 [class.static.data]p3:
- // If a non-volatile const static data member is of integral or
- // enumeration type, its declaration in the class definition can
+ // If a non-volatile non-inline const static data member is of integral
+ // or enumeration type, its declaration in the class definition can
// specify a brace-or-equal-initializer in which every initalizer-clause
// that is an assignment-expression is a constant expression. A static
// data member of literal type can be declared in the class definition
@@ -9957,14 +10009,21 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl,
// the definition of a variable [...] or the declaration of a static data
// member.
if (Var->isConstexpr() && !Var->isThisDeclarationADefinition()) {
- if (Var->isStaticDataMember())
- Diag(Var->getLocation(),
- diag::err_constexpr_static_mem_var_requires_init)
- << Var->getDeclName();
- else
+ if (Var->isStaticDataMember()) {
+ // C++1z removes the relevant rule; the in-class declaration is always
+ // a definition there.
+ if (!getLangOpts().CPlusPlus1z) {
+ Diag(Var->getLocation(),
+ diag::err_constexpr_static_mem_var_requires_init)
+ << Var->getDeclName();
+ Var->setInvalidDecl();
+ return;
+ }
+ } else {
Diag(Var->getLocation(), diag::err_invalid_constexpr_var_decl);
- Var->setInvalidDecl();
- return;
+ Var->setInvalidDecl();
+ return;
+ }
}
// C++ Concepts TS [dcl.spec.concept]p1: [...] A variable template
@@ -10754,6 +10813,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D) {
if (DeclSpec::TSCS TSCS = DS.getThreadStorageClassSpec())
Diag(DS.getThreadStorageClassSpecLoc(), diag::err_invalid_thread)
<< DeclSpec::getSpecifierName(TSCS);
+ if (DS.isInlineSpecified())
+ Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
if (DS.isConstexprSpecified())
Diag(DS.getConstexprSpecLoc(), diag::err_invalid_constexpr)
<< 0;
@@ -13327,6 +13389,9 @@ FieldDecl *Sema::HandleField(Scope *S, RecordDecl *Record,
DiagnoseFunctionSpecifiers(D.getDeclSpec());
+ if (D.getDeclSpec().isInlineSpecified())
+ Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
diag::err_invalid_thread)
diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp
index 8c0e9eaddf..b0f3356e1c 100644
--- a/lib/Sema/SemaDeclCXX.cpp
+++ b/lib/Sema/SemaDeclCXX.cpp
@@ -13995,6 +13995,9 @@ MSPropertyDecl *Sema::HandleMSProperty(Scope *S, RecordDecl *Record,
DiagnoseFunctionSpecifiers(D.getDeclSpec());
+ if (D.getDeclSpec().isInlineSpecified())
+ Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
diag::err_invalid_thread)
diff --git a/lib/Sema/SemaDeclObjC.cpp b/lib/Sema/SemaDeclObjC.cpp
index f4d82b3c31..738de77cec 100644
--- a/lib/Sema/SemaDeclObjC.cpp
+++ b/lib/Sema/SemaDeclObjC.cpp
@@ -4612,6 +4612,9 @@ Decl *Sema::ActOnObjCExceptionDecl(Scope *S, Declarator &D) {
Diag(DS.getStorageClassSpecLoc(), diag::err_storage_spec_on_catch_parm)
<< DeclSpec::getSpecifierName(SCS);
}
+ if (DS.isInlineSpecified())
+ Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
+ << getLangOpts().CPlusPlus1z;
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
diag::err_invalid_thread)
diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp
index 10a3b865bc..ba12fcf540 100644
--- a/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2520,8 +2520,7 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
// specialization and is only an explicit instantiation definition
// of members whose definition is visible at the point of
// instantiation.
- if (!Var->getInstantiatedFromStaticDataMember()
- ->getOutOfLineDefinition())
+ if (!Var->getInstantiatedFromStaticDataMember()->getDefinition())
continue;
Var->setTemplateSpecializationKind(TSK, PointOfInstantiation);
diff --git a/lib/Sema/SemaTemplateInstantiateDecl.cpp b/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 58c0ab027e..dbb8c6e0c6 100644
--- a/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -3881,11 +3881,12 @@ void Sema::BuildVariableInstantiation(
Context.setManglingNumber(NewVar, Context.getManglingNumber(OldVar));
Context.setStaticLocalNumber(NewVar, Context.getStaticLocalNumber(OldVar));
- // Delay instantiation of the initializer for variable templates until a
- // definition of the variable is needed. We need it right away if the type
- // contains 'auto'.
+ // Delay instantiation of the initializer for variable templates or inline
+ // static data members until a definition of the variable is needed. We need
+ // it right away if the type contains 'auto'.
if ((!isa<VarTemplateSpecializationDecl>(NewVar) &&
- !InstantiatingVarTemplate) ||
+ !InstantiatingVarTemplate &&
+ !(OldVar->isInline() && OldVar->isThisDeclarationADefinition())) ||
NewVar->getType()->isUndeducedType())
InstantiateVariableInitializer(NewVar, OldVar, TemplateArgs);
@@ -3901,6 +3902,13 @@ void Sema::BuildVariableInstantiation(
void Sema::InstantiateVariableInitializer(
VarDecl *Var, VarDecl *OldVar,
const MultiLevelTemplateArgumentList &TemplateArgs) {
+ // We propagate the 'inline' flag with the initializer, because it
+ // would otherwise imply that the variable is a definition for a
+ // non-static data member.
+ if (OldVar->isInlineSpecified())
+ Var->setInlineSpecified();
+ else if (OldVar->isInline())
+ Var->setImplicitlyInline();
if (Var->getAnyInitializer())
// We already have an initializer in the class.
@@ -4083,7 +4091,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
assert(PatternDecl && "data member was not instantiated from a template?");
assert(PatternDecl->isStaticDataMember() && "not a static data member?");
- Def = PatternDecl->getOutOfLineDefinition();
+ Def = PatternDecl->getDefinition();
}
// FIXME: Check that the definition is visible before trying to instantiate
@@ -4181,11 +4189,16 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
LocalInstantiationScope Local(*this);
VarDecl *OldVar = Var;
- if (!VarSpec)
+ if (Def->isStaticDataMember() && !Def->isOutOfLine()) {
+ // We're instantiating an inline static data member whose definition was
+ // provided inside the class.
+ // FIXME: Update record?
+ InstantiateVariableInitializer(Var, Def, TemplateArgs);
+ } else if (!VarSpec) {
Var = cast_or_null<VarDecl>(SubstDecl(Def, Var->getDeclContext(),
TemplateArgs));
- else if (Var->isStaticDataMember() &&
- Var->getLexicalDeclContext()->isRecord()) {
+ } else if (Var->isStaticDataMember() &&
+ Var->getLexicalDeclContext()->isRecord()) {
// We need to instantiate the definition of a static data member template,
// and all we have is the in-class declaration of it. Instantiate a separate
// declaration of the definition.
diff --git a/lib/Serialization/ASTReaderDecl.cpp b/lib/Serialization/ASTReaderDecl.cpp
index 325acd3181..f993cf48a7 100644
--- a/lib/Serialization/ASTReaderDecl.cpp
+++ b/lib/Serialization/ASTReaderDecl.cpp
@@ -1217,6 +1217,8 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
VD->NonParmVarDeclBits.NRVOVariable = Record[Idx++];
VD->NonParmVarDeclBits.CXXForRangeDecl = Record[Idx++];
VD->NonParmVarDeclBits.ARCPseudoStrong = Record[Idx++];
+ VD->NonParmVarDeclBits.IsInline = Record[Idx++];
+ VD->NonParmVarDeclBits.IsInlineSpecified = Record[Idx++];
VD->NonParmVarDeclBits.IsConstexpr = Record[Idx++];
VD->NonParmVarDeclBits.IsInitCapture = Record[Idx++];
VD->NonParmVarDeclBits.PreviousDeclInSameBlockScope = Record[Idx++];
diff --git a/lib/Serialization/ASTWriterDecl.cpp b/lib/Serialization/ASTWriterDecl.cpp
index ed0a262638..04a2d6bd8c 100644
--- a/lib/Serialization/ASTWriterDecl.cpp
+++ b/lib/Serialization/ASTWriterDecl.cpp
@@ -895,6 +895,8 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
Record.push_back(D->isNRVOVariable());
Record.push_back(D->isCXXForRangeDecl());
Record.push_back(D->isARCPseudoStrong());
+ Record.push_back(D->isInline());
+ Record.push_back(D->isInlineSpecified());
Record.push_back(D->isConstexpr());
Record.push_back(D->isInitCapture());
Record.push_back(D->isPreviousDeclInSameBlockScope());
@@ -941,6 +943,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
D->getInit() == nullptr &&
!isa<ParmVarDecl>(D) &&
!isa<VarTemplateSpecializationDecl>(D) &&
+ !D->isInline() &&
!D->isConstexpr() &&
!D->isInitCapture() &&
!D->isPreviousDeclInSameBlockScope() &&
@@ -1916,6 +1919,8 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isNRVOVariable
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isCXXForRangeDecl
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isARCPseudoStrong
+ Abv->Add(BitCodeAbbrevOp(0)); // isInline
+ Abv->Add(BitCodeAbbrevOp(0)); // isInlineSpecified
Abv->Add(BitCodeAbbrevOp(0)); // isConstexpr
Abv->Add(BitCodeAbbrevOp(0)); // isInitCapture
Abv->Add(BitCodeAbbrevOp(0)); // isPrevDeclInSameScope
diff --git a/test/CXX/basic/basic.def/p2.cpp b/test/CXX/basic/basic.def/p2.cpp
new file mode 100644
index 0000000000..598a79a8a3
--- /dev/null
+++ b/test/CXX/basic/basic.def/p2.cpp
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s -Wdeprecated
+
+namespace {
+ struct A {
+ static constexpr int n = 0;
+ };
+ const int A::n; // expected-warning {{deprecated}}
+}
diff --git a/test/CXX/basic/basic.def/p4.cpp b/test/CXX/basic/basic.def/p4.cpp
new file mode 100644
index 0000000000..c3919156bb
--- /dev/null
+++ b/test/CXX/basic/basic.def/p4.cpp
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s
+
+inline int f(); // expected-warning {{inline function 'f' is not defined}}
+extern inline int n; // expected-error {{inline variable 'n' is not defined}}
+
+int use = f() + n; // expected-note 2{{used here}}
diff --git a/test/CXX/class/class.static/class.static.data/p2.cpp b/test/CXX/class/class.static/class.static.data/p2.cpp
new file mode 100644
index 0000000000..8c38276641
--- /dev/null
+++ b/test/CXX/class/class.static/class.static.data/p2.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s
+
+struct X {
+ static struct A a;
+ static inline struct B b; // expected-error {{incomplete type}} expected-note {{forward decl}}
+ static inline struct C c = {}; // expected-error {{incomplete type}} expected-note {{forward decl}}
+};
diff --git a/test/CXX/class/class.static/class.static.data/p3.cpp b/test/CXX/class/class.static/class.static.data/p3.cpp
index 1607bac802..413017d133 100644
--- a/test/CXX/class/class.static/class.static.data/p3.cpp
+++ b/test/CXX/class/class.static/class.static.data/p3.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
struct NonLit { // expected-note 3{{no constexpr constructors}}
NonLit();
@@ -6,7 +7,7 @@ struct NonLit { // expected-note 3{{no constexpr constructors}}
struct S {
static constexpr int a = 0;
- static constexpr int b; // expected-error {{declaration of constexpr static data member 'b' requires an initializer}}
+ static constexpr int b; // expected-error {{initializ}} expected-note 0-1{{previous}}
static constexpr int c = 0;
static const int d;
@@ -16,19 +17,27 @@ struct S {
static const double f = 0.0; // expected-error {{requires 'constexpr' specifier}} expected-note {{add 'constexpr'}}
static char *const g = 0; // expected-error {{requires 'constexpr' specifier}}
static const NonLit h = NonLit(); // expected-error {{must be initialized out of line}}
+
+ static inline int i; // expected-note {{previous}} expected-warning 0-1{{extension}}
+ static inline int j; // expected-note {{previous}} expected-warning 0-1{{extension}}
+ static constexpr int k = 0;
};
constexpr int S::a;
-constexpr int S::b = 0;
+constexpr int S::b = 0; // expected-error 0-1{{redefinition}}
const int S::c;
constexpr int S::d = 0;
constexpr int S::d2;
+int S::i; // expected-error {{redefinition}}
+int S::j; // expected-error {{redefinition}}
+const int S::k; // ok (deprecated)
+
template<typename T>
struct U {
static constexpr int a = 0;
- static constexpr int b; // expected-error {{declaration of constexpr static data member 'b' requires an initializer}}
+ static constexpr int b; // expected-error {{initializ}}
static constexpr NonLit h = NonLit(); // expected-error {{cannot have non-literal type 'const NonLit'}}
static constexpr T c = T(); // expected-error {{cannot have non-literal type}}
static const T d;
diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp
index 35dbec93e5..5a4c5c9a9d 100644
--- a/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp
+++ b/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p1.cpp
@@ -1,4 +1,6 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
struct notlit { // expected-note {{not literal because}}
notlit() {}
@@ -26,7 +28,12 @@ void f2(constexpr int i) {} // expected-error {{function parameter cannot be con
// non-static member
struct s2 {
constexpr int mi1; // expected-error {{non-static data member cannot be constexpr; did you intend to make it const?}}
- static constexpr int mi2; // expected-error {{requires an initializer}}
+ static constexpr int mi2;
+#if __cplusplus <= 201402L
+ // expected-error@-2 {{requires an initializer}}
+#else
+ // expected-error@-4 {{default initialization of an object of const}}
+#endif
mutable constexpr int mi3 = 3; // expected-error-re {{non-static data member cannot be constexpr{{$}}}} expected-error {{'mutable' and 'const' cannot be mixed}}
};
// typedef
@@ -71,7 +78,7 @@ struct ConstexprDtor {
template <typename T> constexpr T ft(T t) { return t; }
template <typename T> T gt(T t) { return t; }
struct S {
- template<typename T> constexpr T f(); // expected-warning {{C++14}}
+ template<typename T> constexpr T f(); // expected-warning 0-1{{C++14}} expected-note 0-1{{candidate}}
template <typename T>
T g() const; // expected-note-re {{candidate template ignored: could not match 'T (){{( __attribute__\(\(thiscall\)\))?}} const' against 'char (){{( __attribute__\(\(thiscall\)\))?}}'}}
};
@@ -82,7 +89,15 @@ template <> char ft(char c) { return c; } // expected-note {{previous}}
template <> constexpr char ft(char nl); // expected-error {{constexpr declaration of 'ft<char>' follows non-constexpr declaration}}
template <> constexpr int gt(int nl) { return nl; }
template <> notlit S::f() const { return notlit(); }
-template <> constexpr int S::g() { return 0; } // expected-note {{previous}} expected-warning {{C++14}}
+#if __cplusplus >= 201402L
+// expected-error@-2 {{no function template matches}}
+#endif
+template <> constexpr int S::g() { return 0; } // expected-note {{previous}}
+#if __cplusplus < 201402L
+// expected-warning@-2 {{C++14}}
+#else
+// expected-error@-4 {{does not match any declaration in 'S'}}
+#endif
template <> int S::g() const; // expected-error {{non-constexpr declaration of 'g<int>' follows constexpr declaration}}
// specializations can drop the 'constexpr' but not the implied 'const'.
template <> char S::g() { return 0; } // expected-error {{no function template matches}}
@@ -123,3 +138,11 @@ int next(constexpr int x) { // expected-error {{function parameter cannot be con
}
extern constexpr int memsz; // expected-error {{constexpr variable declaration must be a definition}}
+
+namespace {
+ struct A {
+ static constexpr int n = 0;
+ };
+ // FIXME: We should diagnose this prior to C++17.
+ const int &r = A::n;
+}
diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p1.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p1.cpp
new file mode 100644
index 0000000000..6db0b04a74
--- /dev/null
+++ b/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p1.cpp
@@ -0,0 +1,8 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s
+
+inline int f(); // ok
+inline int n; // ok
+
+inline typedef int t; // expected-error {{'inline' can only appear on functions and non-local variables}}
+inline struct S {}; // expected-error {{'inline' can only appear on functions and non-local variables}}
+inline struct T {} s; // ok
diff --git a/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p5.cpp b/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p5.cpp
new file mode 100644
index 0000000000..0ca7bbc5fa
--- /dev/null
+++ b/test/CXX/dcl.dcl/dcl.spec/dcl.inline/p5.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++1z -verify %s
+
+void x() {
+ inline int f(int); // expected-error {{inline declaration of 'f' not allowed in block scope}}
+ inline int n; // expected-error {{inline declaration of 'n' not allowed in block scope}}
+ static inline int m; // expected-error {{inline declaration of 'm' not allowed in block scope}}
+}
+
+inline void g();
+struct X {
+ inline void f();
+ // FIXME: This is ill-formed per [dcl.inline]p5.
+ inline void g();
+ inline void h() {}
+};
diff --git a/test/CXX/temp/temp.spec/temp.inst/p1.cpp b/test/CXX/temp/temp.spec/temp.inst/p1.cpp
index adf812b714..3d2d6d7c3a 100644
--- a/test/CXX/temp/temp.spec/temp.inst/p1.cpp
+++ b/test/CXX/temp/temp.spec/temp.inst/p1.cpp
@@ -54,6 +54,16 @@ namespace ScopedEnum {
int test2 = g<int>(); // expected-note {{here}}
}
+// - static data members
+namespace StaticDataMembers {
+ template<typename T>
+ struct A {
+ static const int n = T::error; // expected-error {{has no members}}
+ static inline int m = T::error; // expected-warning {{extension}}
+ };
+ A<int> ai; // expected-note {{here}}
+}
+
// And it cases the implicit instantiations of the definitions of:
// - unscoped member enumerations
diff --git a/test/CodeGenCXX/cxx1z-inline-variables.cpp b/test/CodeGenCXX/cxx1z-inline-variables.cpp
new file mode 100644
index 0000000000..90d0ed6131
--- /dev/null
+++ b/test/CodeGenCXX/cxx1z-inline-variables.cpp
@@ -0,0 +1,63 @@
+// RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple x86_64-linux-gnu | FileCheck %s
+
+struct Q {
+ // CHECK: @_ZN1Q1kE = linkonce_odr constant i32 5, comdat
+ static constexpr int k = 5;
+};
+const int &r = Q::k;
+
+int f();
+
+// const does not imply internal linkage.
+// CHECK: @external_inline = linkonce_odr constant i32 5, comdat
+inline const int external_inline = 5;
+const int &use1 = external_inline;
+
+// static still does, though.
+// CHECK: @_ZL15internal_inline = internal constant i32 5
+static inline const int internal_inline = 5;
+const int &use2 = internal_inline;
+
+int a = f();
+// CHECK: @b = linkonce_odr global i32 0, comdat
+// CHECK: @_ZGV1b = linkonce_odr global i64 0, comdat($b)
+inline int b = f();
+int c = f();
+
+template<typename T> struct X {
+ static int a;
+ static inline int b;
+ static int c;
+};
+// CHECK: @_ZN1XIiE1aE = linkonce_odr global i32 10
+// CHECK: @_ZN1XIiE1bE = global i32 20
+// CHECK-NOT: @_ZN1XIiE1cE
+template<> inline int X<int>::a = 10;
+int &use3 = X<int>::a;
+template<> int X<int>::b = 20;
+template<> inline int X<int>::c = 30;
+
+// CHECK-LABEL: define {{.*}}global_var_init
+// CHECK: call i32 @_Z1fv
+
+// CHECK-LABEL: define {{.*}}global_var_init
+// CHECK-NOT: comdat
+// CHECK-SAME: {{$}}
+// CHECK: load atomic {{.*}} acquire
+// CHECK: br
+// CHECK: __cxa_guard_acquire(i64* @_ZGV1b)
+// CHECK: br
+// CHECK: call i32 @_Z1fv
+// CHECK: __cxa_guard_release(i64* @_ZGV1b)
+
+// CHECK-LABEL: define {{.*}}global_var_init
+// CHECK: call i32 @_Z1fv
+
+template<typename T> inline int d = f();
+int e = d<int>;
+
+// CHECK-LABEL: define {{.*}}global_var_init{{.*}}comdat
+// CHECK: _ZGV1dIiE
+// CHECK-NOT: __cxa_guard_acquire(i64* @_ZGV1b)
+// CHECK: call i32 @_Z1fv
+// CHECK-NOT: __cxa_guard_release(i64* @_ZGV1b)
diff --git a/test/Sema/inline.c b/test/Sema/inline.c
index eced058f8d..33b25206d7 100644
--- a/test/Sema/inline.c
+++ b/test/Sema/inline.c
@@ -49,7 +49,7 @@ inline int useConst () {
#include "inline.c"
// Check that we don't allow illegal uses of inline
-inline int a; // expected-error{{'inline' can only appear on functions}}
+inline int a; // expected-warning{{inline variables are a C++1z extension}}
typedef inline int b; // expected-error{{'inline' can only appear on functions}}
int d(inline int a); // expected-error{{'inline' can only appear on functions}}
diff --git a/www/cxx_status.html b/www/cxx_status.html
index 4b5211acf8..580d456e67 100644
--- a/www/cxx_status.html
+++ b/www/cxx_status.html
@@ -715,7 +715,7 @@ as the draft C++1z standard evolves.</p>
<tr>
<td>Inline variables</td>
<td><a href="http://wg21.link/p0386r2">P0386R2</a></td>
- <td class="none" align="center">No</td>
+ <td class="svn" align="center">SVN</td>
</tr>
<tr>
<td>Structured bindings</td>