[clang] [clang] On-demand parsing for templated and internal-linkage functions (PR #210893)
Vipul Cariappa via cfe-commits
cfe-commits at lists.llvm.org
Tue Jul 21 00:17:57 PDT 2026
https://github.com/Vipul-Cariappa created https://github.com/llvm/llvm-project/pull/210893
This PR introduces a new flag `-fparse-functions-ondemand`.
When this feature is enabled, the body/definition of templated functions and functions with internal linkage are only parsed if they are used.
Templated functions are only parsed when instantiated, and internal-linkage functions are parsed if used (i.e. `MarkFunctionReferenced`). The late parsing is built on top of existing functionality used for `-fdelayed-template-parsing`, which is used for MSVC compatibility. The same functionality is extended to defer parsing of regular (internal-linkage) functions.
The lookup mechanism is updated to make sure lookups behave the same as when parsing eagerly. This is done by filtering the lookup results by source location. I accept a lookup if it was declared before (in TU) the location where the lookup is being performed.
Unused (internal-linkage) functions remain unparsed, and any errors within them are not reported.
The included test covers a wide range of lookup semantics related tests, including argument-dependent lookups.
Initial idea: https://discourse.llvm.org/t/gsoc-2024-on-demand-parsing-in-clang/76912
cc @vgvassilev
>From 958805744e4cd10f52f6fc1f46f9381b8d901266 Mon Sep 17 00:00:00 2001
From: Vipul Cariappa <vipulcariappa at gmail.com>
Date: Mon, 20 Jul 2026 11:02:25 +0530
Subject: [PATCH] [clang] delay parsing of all templated & internal-linkage
functions
The function bodies are only parsed if the function is used.
Otherwise it remains unparsed.
---
clang/include/clang/Basic/LangOptions.def | 1 +
clang/include/clang/Options/Options.td | 6 +
clang/include/clang/Sema/Sema.h | 1 +
clang/lib/Driver/ToolChains/Clang.cpp | 4 +
clang/lib/Frontend/CompilerInvocation.cpp | 5 +
clang/lib/Parse/ParseCXXInlineMethods.cpp | 26 +-
clang/lib/Parse/Parser.cpp | 116 +++--
clang/lib/Sema/SemaExpr.cpp | 13 +
clang/lib/Sema/SemaLookup.cpp | 58 ++-
clang/lib/Sema/SemaTemplate.cpp | 20 +
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 9 +
clang/test/Parser/ParseFunctionsOndemand.cpp | 429 ++++++++++++++++++
12 files changed, 622 insertions(+), 66 deletions(-)
create mode 100644 clang/test/Parser/ParseFunctionsOndemand.cpp
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 1fb491a54a278..c1c4c42c30605 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -340,6 +340,7 @@ ENUM_LANGOPT(AddressSpaceMapMangling , AddrSpaceMapMangling, 2, ASMM_Target, Not
LANGOPT(IncludeDefaultHeader, 1, 0, NotCompatible, "Include default header file for OpenCL")
LANGOPT(DeclareOpenCLBuiltins, 1, 0, NotCompatible, "Declare OpenCL builtin functions")
LANGOPT(DelayedTemplateParsing , 1, 0, Benign, "delayed template parsing")
+LANGOPT(ParseFunctionsOnDemand , 1, 0, Benign, "on-demand function parsing")
LANGOPT(BlocksRuntimeOptional , 1, 0, NotCompatible, "optional blocks runtime")
LANGOPT(
CompleteMemberPointers, 1, 0, NotCompatible,
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 4afb089e8a51f..0829adfee3c04 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3614,6 +3614,12 @@ defm delayed_template_parsing : BoolFOption<"delayed-template-parsing",
"Parse templated function definitions at the end of the translation unit">,
NegFlag<SetFalse, [], [], "Disable delayed template parsing">,
BothFlags<[], [ClangOption, CLOption]>>;
+defm parse_functions_ondemand : BoolFOption<"parse-functions-ondemand",
+ LangOpts<"ParseFunctionsOnDemand">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option],
+ "Parse internal-linkage function definitions on demand">,
+ NegFlag<SetFalse, [], [], "Disable on-demand function parsing">,
+ BothFlags<[], [ClangOption, CLOption]>>;
def fms_memptr_rep_EQ : Joined<["-"], "fms-memptr-rep=">, Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
Values<"single,multiple,virtual">, NormalizedValuesScope<"LangOptions">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 8a30f6319bcef..dc69d08b99d8b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12554,6 +12554,7 @@ class Sema final : public SemaBase {
void MarkAsLateParsedTemplate(FunctionDecl *FD, Decl *FnD,
CachedTokens &Toks);
void UnmarkAsLateParsedTemplate(FunctionDecl *FD);
+ bool ParseLateFunctionDefinition(FunctionDecl *FD);
bool IsInsideALocalClassWithinATemplateFunction();
/// We've found a use of a templated declaration that would trigger an
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 8c9b98795b194..271301fdeed65 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7822,6 +7822,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("-fdelayed-template-parsing");
}
+ if (Args.hasFlag(options::OPT_fparse_functions_ondemand,
+ options::OPT_fno_parse_functions_ondemand, false))
+ CmdArgs.push_back("-fparse-functions-ondemand");
+
if (Args.hasFlag(options::OPT_fpch_validate_input_files_content,
options::OPT_fno_pch_validate_input_files_content, false))
CmdArgs.push_back("-fvalidate-ast-input-files-content");
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 56362e758a78b..51a22e3a93be1 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -4126,6 +4126,11 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args,
#include "clang/Options/Options.inc"
#undef LANG_OPTION_WITH_MARSHALLING
+ // delayed parsing has incompatible lookup semantics with ondemand parsing
+ if (Opts.ParseFunctionsOnDemand && Opts.DelayedTemplateParsing)
+ Diags.Report(diag::err_drv_argument_not_allowed_with)
+ << "-fparse-functions-ondemand" << "-fdelayed-template-parsing";
+
// "Modules semantics" (e.g. cross-translation-unit declaration merging) are
// needed for both Clang (header) modules and C++20 modules, so enable them
// for either.
diff --git a/clang/lib/Parse/ParseCXXInlineMethods.cpp b/clang/lib/Parse/ParseCXXInlineMethods.cpp
index be531e567046e..65f2026ee4678 100644
--- a/clang/lib/Parse/ParseCXXInlineMethods.cpp
+++ b/clang/lib/Parse/ParseCXXInlineMethods.cpp
@@ -144,24 +144,30 @@ NamedDecl *Parser::ParseCXXInlineMethodDef(
return FnD;
}
+ FunctionDecl *FD = FnD ? FnD->getAsFunction() : nullptr;
+ bool IsDelayedInternalFunction =
+ getLangOpts().ParseFunctionsOnDemand && FD && !FD->isExternallyVisible();
+ bool IsDelayedTemplateFunction =
+ getLangOpts().DelayedTemplateParsing &&
+ (((Actions.CurContext->isDependentContext() ||
+ (TemplateInfo.Kind != ParsedTemplateKind::NonTemplate &&
+ TemplateInfo.Kind != ParsedTemplateKind::ExplicitSpecialization)) &&
+ !Actions.IsInsideALocalClassWithinATemplateFunction()));
+
// In delayed template parsing mode, if we are within a class template
// or if we are about to parse function member template then consume
// the tokens and store them for parsing at the end of the translation unit.
- if (getLangOpts().DelayedTemplateParsing &&
- D.getFunctionDefinitionKind() == FunctionDefinitionKind::Definition &&
+ // In on-demand parsing mode, internal-linkage member definitions are also
+ // handled in the same way.
+ if (D.getFunctionDefinitionKind() == FunctionDefinitionKind::Definition &&
!D.getDeclSpec().hasConstexprSpecifier() &&
- !(FnD && FnD->getAsFunction() &&
- FnD->getAsFunction()->getReturnType()->getContainedAutoType()) &&
- ((Actions.CurContext->isDependentContext() ||
- (TemplateInfo.Kind != ParsedTemplateKind::NonTemplate &&
- TemplateInfo.Kind != ParsedTemplateKind::ExplicitSpecialization)) &&
- !Actions.IsInsideALocalClassWithinATemplateFunction())) {
+ !(FD && FD->getReturnType()->getContainedAutoType()) &&
+ (IsDelayedTemplateFunction || IsDelayedInternalFunction)) {
CachedTokens Toks;
LexTemplateFunctionForLateParsing(Toks);
- if (FnD) {
- FunctionDecl *FD = FnD->getAsFunction();
+ if (FD) {
Actions.CheckForFunctionRedefinition(FD);
Actions.MarkAsLateParsedTemplate(FD, FnD, Toks);
}
diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp
index 6a21acd9dc4ef..88e88bae8dc64 100644
--- a/clang/lib/Parse/Parser.cpp
+++ b/clang/lib/Parse/Parser.cpp
@@ -26,6 +26,7 @@
#include "clang/Sema/ParsedTemplate.h"
#include "clang/Sema/Scope.h"
#include "clang/Sema/SemaCodeCompletion.h"
+#include "clang/Sema/SemaOpenMP.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
@@ -1234,51 +1235,79 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
Diag(AL.getLoc(), diag::warn_attribute_on_function_definition) << AL;
}
- // In delayed template parsing mode, for function template we consume the
- // tokens and store them for late parsing at the end of the translation unit.
- if (getLangOpts().DelayedTemplateParsing && Tok.isNot(tok::equal) &&
- TemplateInfo.Kind == ParsedTemplateKind::Template &&
- LateParsedAttrs->empty() && Actions.canDelayFunctionBody(D)) {
- MultiTemplateParamsArg TemplateParameterLists(*TemplateInfo.TemplateParams);
+ MultiTemplateParamsArg TemplateParameterLists(
+ TemplateInfo.TemplateParams ? *TemplateInfo.TemplateParams
+ : MultiTemplateParamsArg{});
- ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope |
- Scope::CompoundStmtScope);
- Scope *ParentScope = getCurScope()->getParent();
+ bool CanDelayFunctionBody = Actions.canDelayFunctionBody(D);
+ bool IsDelayedTemplateFunction =
+ getLangOpts().DelayedTemplateParsing &&
+ TemplateInfo.Kind == ParsedTemplateKind::Template && CanDelayFunctionBody;
+ bool ShouldProbeInternalLinkage = getLangOpts().ParseFunctionsOnDemand &&
+ !CurParsedObjCImpl && CanDelayFunctionBody;
- D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition);
- Decl *DP = Actions.HandleDeclarator(ParentScope, D,
- TemplateParameterLists);
- D.complete(DP);
- D.getMutableDeclSpec().abort();
+ // Enter a scope for the function body.
+ ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope |
+ Scope::CompoundStmtScope);
+ Scope *ParentScope = getCurScope()->getParent();
- if (SkipFunctionBodies && (!DP || Actions.canSkipFunctionBody(DP)) &&
- trySkippingFunctionBody()) {
- BodyScope.Exit();
- return Actions.ActOnSkippedFunctionBody(DP);
- }
+ D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition);
+
+ // Check and handle if we are in an `omp begin/end declare variant` scope.
+ SmallVector<FunctionDecl *, 4> Bases;
+ if (getLangOpts().OpenMP && Actions.OpenMP().isInOpenMPDeclareVariantScope())
+ Actions.OpenMP().ActOnStartOfFunctionDefinitionInOpenMPDeclareVariantScope(
+ ParentScope, D, TemplateParameterLists, Bases);
+
+ Decl *FuncDecl =
+ Actions.HandleDeclarator(ParentScope, D, TemplateParameterLists);
+
+ // Break out of the ParsingDeclarator context before we parse the body.
+ D.complete(FuncDecl);
+
+ // Break out of the ParsingDeclSpec context, too. This const_cast is
+ // safe because we're always the sole owner.
+ D.getMutableDeclSpec().abort();
+
+ // Finish the OpenMP declare-variant handling on any early-return path.
+ auto FinishDeclareVariant = [&](Decl *D) {
+ if (!Bases.empty())
+ Actions.OpenMP()
+ .ActOnFinishedFunctionDefinitionInOpenMPDeclareVariantScope(D, Bases);
+ };
+
+ // For function templates in delayed template parsing mode, and for
+ // internal-linkage functions in on-demand parsing mode, consume the tokens
+ // and store them for late parsing.
+ if (Tok.isNot(tok::equal) && LateParsedAttrs->empty() &&
+ (IsDelayedTemplateFunction || ShouldProbeInternalLinkage)) {
+ FunctionDecl *FnD = FuncDecl ? FuncDecl->getAsFunction() : nullptr;
+ bool ShouldDelayFunction =
+ IsDelayedTemplateFunction || (FnD && !FnD->isExternallyVisible());
+ if (ShouldDelayFunction) {
+ if (SkipFunctionBodies &&
+ (!FuncDecl || Actions.canSkipFunctionBody(FuncDecl)) &&
+ trySkippingFunctionBody()) {
+ BodyScope.Exit();
+ FinishDeclareVariant(FuncDecl);
+ return Actions.ActOnSkippedFunctionBody(FuncDecl);
+ }
- CachedTokens Toks;
- LexTemplateFunctionForLateParsing(Toks);
+ CachedTokens Toks;
+ LexTemplateFunctionForLateParsing(Toks);
- if (DP) {
- FunctionDecl *FnD = DP->getAsFunction();
- Actions.CheckForFunctionRedefinition(FnD);
- Actions.MarkAsLateParsedTemplate(FnD, DP, Toks);
+ if (FnD) {
+ Actions.CheckForFunctionRedefinition(FnD);
+ Actions.MarkAsLateParsedTemplate(FnD, FuncDecl, Toks);
+ }
+ FinishDeclareVariant(FuncDecl);
+ return FuncDecl;
}
- return DP;
}
+
if (CurParsedObjCImpl && !TemplateInfo.TemplateParams &&
(Tok.is(tok::l_brace) || Tok.is(tok::kw_try) || Tok.is(tok::colon)) &&
Actions.CurContext->isTranslationUnit()) {
- ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope |
- Scope::CompoundStmtScope);
- Scope *ParentScope = getCurScope()->getParent();
-
- D.setFunctionDefinitionKind(FunctionDefinitionKind::Definition);
- Decl *FuncDecl = Actions.HandleDeclarator(ParentScope, D,
- MultiTemplateParamsArg());
- D.complete(FuncDecl);
- D.getMutableDeclSpec().abort();
if (FuncDecl) {
// Consume the tokens and store them for later parsing.
StashAwayMethodOrFunctionBodyTokens(FuncDecl);
@@ -1288,10 +1317,6 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
// FIXME: Should we really fall through here?
}
- // Enter a scope for the function body.
- ParseScope BodyScope(this, Scope::FnScope | Scope::DeclScope |
- Scope::CompoundStmtScope);
-
// Parse function body eagerly if it is either '= delete;' or '= default;' as
// ActOnStartOfFunctionDef needs to know whether the function is deleted.
StringLiteral *DeletedMessage = nullptr;
@@ -1336,11 +1361,9 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
// Tell the actions module that we have entered a function definition with the
// specified Declarator for the function.
SkipBodyInfo SkipBody;
- Decl *Res = Actions.ActOnStartOfFunctionDef(getCurScope(), D,
- TemplateInfo.TemplateParams
- ? *TemplateInfo.TemplateParams
- : MultiTemplateParamsArg(),
+ Decl *Res = Actions.ActOnStartOfFunctionDef(getCurScope(), FuncDecl,
&SkipBody, BodyKind);
+ FinishDeclareVariant(Res);
if (SkipBody.ShouldSkip) {
// Do NOT enter SkipFunctionBody if we already consumed the tokens.
@@ -1361,13 +1384,6 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D,
return Res;
}
- // Break out of the ParsingDeclarator context before we parse the body.
- D.complete(Res);
-
- // Break out of the ParsingDeclSpec context, too. This const_cast is
- // safe because we're always the sole owner.
- D.getMutableDeclSpec().abort();
-
if (BodyKind != Sema::FnBodyKind::Other) {
Actions.SetFunctionBodyKind(Res, KWLoc, BodyKind, DeletedMessage);
Stmt *GeneratedBody = Res ? Res->getBody() : nullptr;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b844670543a55..50901d8a3c1d5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -19057,6 +19057,19 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
if (getLangOpts().CUDA)
CUDA().CheckCall(Loc, Func);
+ if (const FunctionDecl *Definition;
+ getLangOpts().ParseFunctionsOnDemand && NeedDefinition &&
+ Func->hasBody(Definition) && !Definition->getBody() &&
+ Definition->isLateTemplateParsed() &&
+ Definition->getTemplatedKind() == FunctionDecl::TK_NonTemplate) {
+ FunctionDecl *LateParsedFD = const_cast<FunctionDecl *>(Definition);
+ if (!LateParsedFD->instantiationIsPending()) {
+ LateParsedFD->setInstantiationIsPending(true);
+ PendingInstantiations.push_back(std::make_pair(LateParsedFD, Loc));
+ }
+ return;
+ }
+
// If we need a definition, try to create one.
if (NeedDefinition && !Func->getBody()) {
runWithSufficientStackSpace(Loc, [&] {
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 43129800e9813..6037092465ba1 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -1125,6 +1125,44 @@ static void DeclareImplicitMemberFunctionsWithName(Sema &S,
}
}
+static bool IsVisibleAtLateParsedLookupPoint(Sema &S, SourceLocation LookupLoc,
+ NamedDecl *D) {
+ if (!S.getLangOpts().ParseFunctionsOnDemand)
+ return true;
+
+ if (D->isImplicit())
+ return true;
+
+ auto *FD = dyn_cast_or_null<FunctionDecl>(S.CurContext);
+ if (!FD || !FD->isLateTemplateParsed())
+ return true;
+
+ SourceLocation DeclLoc = D->getCanonicalDecl()->getLocation();
+
+ // can this be an assert on valid source locations at this point?
+ if (DeclLoc.isInvalid() || LookupLoc.isInvalid())
+ return true;
+
+ if (DeclLoc == LookupLoc ||
+ S.getSourceManager().isBeforeInTranslationUnit(DeclLoc, LookupLoc))
+ return true;
+
+ if (auto *CXXMD = dyn_cast<CXXMethodDecl>(FD)) {
+ if (auto *RD = CXXMD->getParent()->getDefinition()) {
+ if (RD->getEndLoc().isInvalid())
+ return true;
+ return S.getSourceManager().isBeforeInTranslationUnit(DeclLoc,
+ RD->getEndLoc());
+ }
+ }
+ return false;
+}
+
+static bool IsVisibleAtLateParsedLookupPoint(Sema &S, LookupResult &R,
+ NamedDecl *D) {
+ return IsVisibleAtLateParsedLookupPoint(S, R.getLookupNameInfo().getLoc(), D);
+}
+
// Adds all qualifying matches for a name within a decl context to the
// given lookup result. Returns true if any matches were found.
static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) {
@@ -1139,8 +1177,10 @@ static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) {
DeclContext::lookup_result DR = DC->lookup(R.getLookupName());
for (NamedDecl *D : DR) {
if ((D = R.getAcceptableDecl(D))) {
- R.addDecl(D);
- Found = true;
+ if (IsVisibleAtLateParsedLookupPoint(S, R, D)) {
+ R.addDecl(D);
+ Found = true;
+ }
}
}
@@ -1347,7 +1387,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
bool SearchNamespaceScope = true;
// Check whether the IdResolver has anything in this scope.
for (; I != IEnd && S->isDeclScope(*I); ++I) {
- if (NamedDecl *ND = R.getAcceptableDecl(*I)) {
+ if (NamedDecl *ND = R.getAcceptableDecl(*I);
+ ND && IsVisibleAtLateParsedLookupPoint(*this, R, ND)) {
if (NameKind == LookupRedeclarationWithLinkage &&
!(*I)->isTemplateParameter()) {
// If it's a template parameter, we still find it, so we can diagnose
@@ -1500,7 +1541,8 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) {
// Check whether the IdResolver has anything in this scope.
bool Found = false;
for (; I != IEnd && S->isDeclScope(*I); ++I) {
- if (NamedDecl *ND = R.getAcceptableDecl(*I)) {
+ if (NamedDecl *ND = R.getAcceptableDecl(*I);
+ ND && IsVisibleAtLateParsedLookupPoint(*this, R, ND)) {
// We found something. Look for anything else in our scope
// with this same name and in an acceptable identifier
// namespace, so that we can construct an overload set if we
@@ -2243,7 +2285,8 @@ bool Sema::LookupName(LookupResult &R, Scope *S, bool AllowBuiltinCreation,
for (IdentifierResolver::iterator I = IdResolver.begin(Name),
IEnd = IdResolver.end();
I != IEnd; ++I)
- if (NamedDecl *D = R.getAcceptableDecl(*I)) {
+ if (NamedDecl *D = R.getAcceptableDecl(*I);
+ D && IsVisibleAtLateParsedLookupPoint(*this, R, D)) {
if (NameKind == LookupRedeclarationWithLinkage) {
// Determine whether this (or a previous) declaration is
// out-of-scope.
@@ -2297,7 +2340,8 @@ bool Sema::LookupName(LookupResult &R, Scope *S, bool AllowBuiltinCreation,
}
// If the declaration is in the right namespace and visible, add it.
- if (NamedDecl *LastD = R.getAcceptableDecl(*LastI))
+ if (NamedDecl *LastD = R.getAcceptableDecl(*LastI);
+ LastD && IsVisibleAtLateParsedLookupPoint(*this, R, LastD))
R.addDecl(LastD);
}
@@ -3919,6 +3963,8 @@ void Sema::ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc,
if (!isa<FunctionDecl>(Underlying) &&
!isa<FunctionTemplateDecl>(Underlying))
continue;
+ if (!IsVisibleAtLateParsedLookupPoint(*this, Loc, D))
+ continue;
// The declaration is visible to argument-dependent lookup if either
// it's ordinarily visible or declared as a friend in an associated
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 643392833759d..e3f028503c4e5 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -11766,6 +11766,26 @@ void Sema::UnmarkAsLateParsedTemplate(FunctionDecl *FD) {
FD->setLateTemplateParsed(false);
}
+bool Sema::ParseLateFunctionDefinition(FunctionDecl *FD) {
+ if (!getLangOpts().ParseFunctionsOnDemand || !FD ||
+ !FD->isLateTemplateParsed() || FD->getBody() || FD->willHaveBody() ||
+ FD->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
+ return false;
+
+ if (!LateTemplateParser)
+ return false;
+
+ if (FD->isFromASTFile() && ExternalSource)
+ ExternalSource->ReadLateParsedTemplates(LateParsedTemplateMap);
+
+ auto LPTIter = LateParsedTemplateMap.find(FD);
+ if (LPTIter == LateParsedTemplateMap.end())
+ return false;
+
+ LateTemplateParser(OpaqueParser, *LPTIter->second);
+ return FD->getBody() != nullptr;
+}
+
bool Sema::IsInsideALocalClassWithinATemplateFunction() {
DeclContext *DC = CurContext;
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 921a9f965fb9c..6fdb2b16bd79d 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -7270,6 +7270,15 @@ void Sema::PerformPendingInstantiations(bool LocalOnly, bool AtEndOfTU) {
// Instantiate function definitions
if (FunctionDecl *Function = dyn_cast<FunctionDecl>(Inst.first)) {
+ if (getLangOpts().ParseFunctionsOnDemand &&
+ Function->isLateTemplateParsed() &&
+ Function->getTemplatedKind() == FunctionDecl::TK_NonTemplate) {
+ ParseLateFunctionDefinition(Function);
+ if (Function->isDefined())
+ Function->setInstantiationIsPending(false);
+ continue;
+ }
+
bool DefinitionRequired = Function->getTemplateSpecializationKind() ==
TSK_ExplicitInstantiationDefinition;
if (Function->isMultiVersion()) {
diff --git a/clang/test/Parser/ParseFunctionsOndemand.cpp b/clang/test/Parser/ParseFunctionsOndemand.cpp
new file mode 100644
index 0000000000000..09240c80ff2d4
--- /dev/null
+++ b/clang/test/Parser/ParseFunctionsOndemand.cpp
@@ -0,0 +1,429 @@
+// RUN: %clang_cc1 -fparse-functions-ondemand -fsyntax-only -verify -Wall -Wextra -Werror -ferror-limit 0 -std=c++11 %s
+// RUN: not %clang_cc1 -fparse-functions-ondemand -fdelayed-template-parsing -fsyntax-only -std=c++11 %s 2>&1 | FileCheck %s --check-prefix=MUTEX
+
+// MUTEX: error: invalid argument '-fparse-functions-ondemand' not allowed with '-fdelayed-template-parsing'
+
+// Internal-linkage bodies are parsed only when referenced; external ones eagerly.
+namespace InternalFunctionBodies {
+static void static_diagnoses_on_use() {
+ undeclared_static(); // expected-error {{use of undeclared identifier 'undeclared_static'}}
+}
+
+static void prior_static();
+void prior_static() {
+ undeclared_prior_static(); // expected-error {{use of undeclared identifier 'undeclared_prior_static'}}
+}
+
+static void unreferenced_prior_static();
+void unreferenced_prior_static() {
+ undeclared_prior_static(); // this should not give an error
+}
+
+namespace {
+void anon_namespace_function() {
+ undeclared_anon_namespace(); // expected-error {{use of undeclared identifier 'undeclared_anon_namespace'}}
+}
+
+void unreferenced_anon_namespace_function() {
+ undeclared_anon_namespace(); // this should not give an error
+}
+} // namespace
+
+void external_function_diagnoses_eagerly() {
+ undeclared_external(); // expected-error {{use of undeclared identifier 'undeclared_external'}}
+}
+
+static void unreferenced_static_body_is_delayed() {
+ undeclared_but_unreferenced(); // this should not give an error
+}
+
+void trigger_internal_function_bodies() {
+ static_diagnoses_on_use();
+ prior_static();
+ anon_namespace_function();
+}
+} // namespace InternalFunctionBodies
+
+// A delayed body resolves overloads using only those declared before it.
+namespace SourceOrderOverloads {
+struct One {};
+struct Two {}; // expected-note 2 {{candidate constructor}}
+
+static One foo_hidden_later(int);
+static void call_before_later_overload() {
+ Two value = foo_hidden_later(0.0); // expected-error {{no viable conversion from 'One' to 'Two'}}
+}
+static Two foo_hidden_later(double);
+
+static Two foo_forward_declared(double);
+static void call_forward_declared_overload() {
+ Two _ = foo_forward_declared(0.0);
+}
+static Two foo_forward_declared(double);
+
+static One foo_both_before(int);
+static Two foo_both_before(double);
+static void call_both_overloads_before() {
+ Two _ = foo_both_before(0.0);
+}
+
+void trigger_source_order_overloads() {
+ call_before_later_overload();
+ call_forward_declared_overload();
+ call_both_overloads_before();
+}
+} // namespace SourceOrderOverloads
+
+// A delayed body sees namespace-scope names only if declared before it.
+namespace NamespaceScopeSourceOrder {
+static int later_variable_hidden() {
+ return later_variable; // expected-error {{use of undeclared identifier 'later_variable'}}
+}
+static int later_variable;
+
+static int later_function_hidden() {
+ return later_function(); // expected-error {{use of undeclared identifier 'later_function'}}
+}
+static int later_function();
+
+static int builtin_lookup_still_works() {
+ return __builtin_abs(-1);
+}
+
+void trigger_namespace_scope_source_order() {
+ later_variable_hidden();
+ later_function_hidden();
+ builtin_lookup_still_works();
+}
+} // namespace NamespaceScopeSourceOrder
+
+// Member bodies are delayed too; when parsed they see the whole class but only
+// prior namespace-scope names.
+namespace ClassMethodLookup {
+struct NormalClass {
+ void sees_later_method() {
+ later_method();
+ }
+ void later_method();
+
+ int sees_later_static_member() {
+ return later_static_member;
+ }
+ static int later_static_member;
+};
+
+namespace {
+struct InternalClass {
+ void unreferenced_body_is_delayed() {
+ undeclared_in_method(); // this should not give an error
+ }
+
+ void sees_later_member() {
+ later_member();
+ }
+ void later_member() {}
+
+ void diagnoses_on_use() {
+ undeclared_in_referenced_method(); // expected-error {{use of undeclared identifier 'undeclared_in_referenced_method'}}
+ }
+
+ void later_global_hidden() {
+ later_global_after_class; // expected-error {{use of undeclared identifier 'later_global_after_class'}}
+ }
+
+ static int static_member_later_global_hidden() {
+ return later_global_after_class; // expected-error {{use of undeclared identifier 'later_global_after_class'}}
+ }
+
+ static int static_member_sees_later_static_member() {
+ return later_static_member;
+ }
+ static int later_static_member;
+};
+} // namespace
+
+int InternalClass::later_static_member = 10;
+int later_global_after_class = 10;
+
+void trigger_class_method_lookup() {
+ InternalClass object;
+ object.sees_later_member();
+ object.diagnoses_on_use();
+ object.later_global_hidden();
+ InternalClass::static_member_later_global_hidden();
+ InternalClass::static_member_sees_later_static_member();
+}
+} // namespace ClassMethodLookup
+
+// Out-of-line member bodies see namespace-scope names declared before the
+// out-of-line definition.
+namespace OutOfClassMethods {
+namespace {
+struct S {
+ int sees_prior_global();
+ int hides_later_global();
+};
+
+int prior_global; // expected-note {{'prior_global' declared here}}
+
+int S::sees_prior_global() {
+ return prior_global;
+}
+
+int S::hides_later_global() {
+ return later_global; // expected-error {{use of undeclared identifier 'later_global'}}
+}
+
+int later_global; // expected-note 2 {{'OutOfClassMethods::later_global' declared here}}
+} // namespace
+
+void trigger_out_of_class_methods() {
+ S s;
+ s.sees_prior_global();
+ s.hides_later_global();
+}
+} // namespace OutOfClassMethods
+
+// Inline friend and friend-class member bodies follow the same prior-only
+// namespace-scope visibility.
+namespace FriendFunctionsAndClasses {
+int prior_global;
+
+namespace {
+struct FriendFunctionPrior {
+ friend int friend_function_prior(FriendFunctionPrior) {
+ return prior_global;
+ }
+};
+
+struct FriendFunctionLater {
+ friend int friend_function_later(FriendFunctionLater) {
+ return later_global; // expected-error {{use of undeclared identifier 'later_global'}}
+ }
+};
+
+struct Host {
+ friend struct FriendClass;
+};
+
+struct FriendClass {
+ static int sees_prior_global() {
+ return prior_global;
+ }
+ static int hides_later_global() {
+ return later_global; // expected-error {{use of undeclared identifier 'later_global'}}
+ }
+};
+} // namespace
+
+int later_global;
+
+void trigger_friend_functions_and_classes() {
+ friend_function_prior(FriendFunctionPrior{});
+ friend_function_later(FriendFunctionLater{});
+ FriendClass::sees_prior_global();
+ FriendClass::hides_later_global();
+}
+} // namespace FriendFunctionsAndClasses
+
+// ADL from a delayed body finds only functions declared before it.
+namespace ADLLookup {
+struct One {};
+struct Two {};
+
+namespace Hidden {
+struct A {};
+}
+
+static void adl_later_function_hidden() {
+ Hidden::A a;
+ Two value = adl_target(a); // expected-error {{use of undeclared identifier 'adl_target'}}
+}
+
+namespace Hidden {
+One adl_target(A);
+}
+
+namespace Visible {
+struct A {};
+One adl_target(A);
+}
+
+static void adl_prior_function_visible() {
+ Visible::A a;
+ One _ = adl_target(a);
+}
+
+void trigger_adl_lookup() {
+ adl_later_function_hidden();
+ adl_prior_function_visible();
+}
+} // namespace ADLLookup
+
+// Qualified lookup from a delayed body finds only members declared before it.
+namespace QualifiedLookup {
+struct One {};
+struct Two {};
+
+namespace Hidden {}
+
+static void qualified_later_function_hidden() {
+ Hidden::h(); // expected-error {{no member named 'h' in namespace 'QualifiedLookup::Hidden'}}
+}
+
+namespace Hidden {
+One h();
+}
+
+namespace Visible {
+One h();
+}
+
+static void qualified_prior_function_visible() {
+ One _ = Visible::h();
+}
+
+void trigger_qualified_lookup() {
+ qualified_later_function_hidden();
+ qualified_prior_function_visible();
+}
+} // namespace QualifiedLookup
+
+// Default arguments, noexcept, and trailing-return are parsed eagerly (at the
+// declaration), so they cannot see later names; delete/default bodies are fine.
+namespace DeclarationParts {
+static int default_argument(int value = default_argument_later_global) { // expected-error {{use of undeclared identifier 'default_argument_later_global'}}
+ return value;
+}
+int default_argument_later_global;
+
+static void noexcept_expr() noexcept(noexcept(noexcept_later_global)) {} // expected-error {{use of undeclared identifier 'noexcept_later_global'}}
+int noexcept_later_global;
+
+static auto trailing_return() -> decltype(trailing_return_later_global) { // expected-error {{use of undeclared identifier 'trailing_return_later_global'}}
+ return 0;
+}
+int trailing_return_later_global;
+
+void deleted_function() = delete;
+
+struct DefaultedConstructor {
+ DefaultedConstructor() = default;
+};
+} // namespace DeclarationParts
+
+// Local-class member bodies are parsed with their enclosing function's body.
+namespace LocalClassCases {
+void local_class_method_diagnoses_on_use() {
+ struct S {
+ void f() {
+ undeclared_in_local_class(); // expected-error {{use of undeclared identifier 'undeclared_in_local_class'}}
+ }
+ };
+
+ S s;
+ s.f();
+}
+
+void local_class_method_sees_local_typedef() {
+ typedef int T;
+ struct S {
+ T f() { return 0; }
+ };
+
+ S s;
+ s.f();
+}
+} // namespace LocalClassCases
+
+namespace VirtualFunctions {
+// Constructing an object of a polymorphic internal-linkage class emits its
+// vtable, which references every virtual function. So all virtual function
+// bodies are parsed when the object is used, while unreferenced non-virtual
+// members stay unparsed.
+namespace {
+struct UsedObject {
+ virtual void first_virtual() {
+ undeclared_in_first_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_first_virtual'}}
+ }
+ virtual void second_virtual() {
+ undeclared_in_second_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_second_virtual'}}
+ }
+ void unreferenced_non_virtual() {
+ undeclared_in_non_virtual(); // this should not give an error
+ }
+};
+
+// No object is ever created, so none of the virtual bodies are parsed.
+struct NeverInstantiated {
+ virtual void unused_first_virtual() {
+ undeclared_in_unused_first(); // this should not give an error
+ }
+ virtual void unused_second_virtual() {
+ undeclared_in_unused_second(); // this should not give an error
+ }
+};
+
+// Only a pointer is formed; without constructing an object the vtable is not
+// emitted and the virtual bodies stay unparsed.
+struct OnlyPointerUsed {
+ virtual void pointer_virtual() {
+ undeclared_in_pointer_virtual(); // this should not give an error
+ }
+};
+
+// Calling a single virtual function still emits the vtable, so every virtual
+// body is parsed -- not just the one that was called.
+struct CallOneVirtual {
+ virtual void called_virtual() {
+ undeclared_in_called_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_called_virtual'}}
+ }
+ virtual void other_virtual() {
+ undeclared_in_other_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_other_virtual'}}
+ }
+};
+
+// Using a derived object emits both the derived and base vtables, so virtual
+// bodies from the whole hierarchy are parsed.
+struct Base {
+ virtual void base_virtual() {
+ undeclared_in_base_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_base_virtual'}}
+ }
+};
+
+struct Derived : Base {
+ void base_virtual() override {
+ undeclared_in_override_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_override_virtual'}}
+ }
+ virtual void derived_virtual() {
+ undeclared_in_derived_virtual(); // expected-error {{use of undeclared identifier 'undeclared_in_derived_virtual'}}
+ }
+};
+} // namespace
+
+void trigger_virtual_functions() {
+ UsedObject used;
+ (void)used;
+
+ CallOneVirtual call;
+ call.called_virtual();
+
+ Derived derived;
+ (void)derived;
+
+ OnlyPointerUsed *pointer = nullptr;
+ (void)pointer;
+}
+} // namespace VirtualFunctions
+
+// Template bodies are still parsed on instantiation.
+namespace TemplateCases {
+template <class T>
+void template_body_diagnoses_on_instantiation() {
+ undeclared_in_template(); // expected-error {{use of undeclared identifier 'undeclared_in_template'}}
+}
+
+void trigger_template_cases() {
+ template_body_diagnoses_on_instantiation<int>();
+}
+} // namespace TemplateCases
More information about the cfe-commits
mailing list