Skip to content

[modules] Handle friend function that was a definition but became only a declaration during AST deserialization #132214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 3, 2025
4 changes: 4 additions & 0 deletions clang/include/clang/AST/ExternalASTSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ class ExternalASTSource : public RefCountedBase<ExternalASTSource> {

virtual ExtKind hasExternalDefinitions(const Decl *D);

/// True if this function declaration was a definition before in its own
/// module.
virtual bool wasThisDeclarationADefinition(const FunctionDecl *FD);

/// Finds all declarations lexically contained within the given
/// DeclContext, after applying an optional filter predicate.
///
Expand Down
16 changes: 16 additions & 0 deletions clang/include/clang/Serialization/ASTReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,20 @@ class ASTReader

llvm::DenseMap<const Decl *, bool> DefinitionSource;

/// The set of extra flags about declarations that we have read from
/// the module file.
struct ExternalDeclarationBits {
/// Indicates if given function declaration was a definition but its body
/// was removed due to declaration merging.
bool ThisDeclarationWasADefinition : 1;

ExternalDeclarationBits() : ThisDeclarationWasADefinition(false) {}
};

/// A mapping from declarations to extra bits of information about this decl.
llvm::DenseMap<const Decl *, ExternalDeclarationBits>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
llvm::DenseMap<const Decl *, ExternalDeclarationBits>
llvm::DenseMap<const Decl *, bool>

nit: I think we can avoid the abstraction here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case we don't even need map, I replaced with with a set.

ExternalDeclarationBitsMap;

bool shouldDisableValidationForFile(const serialization::ModuleFile &M) const;

/// Reads a statement from the specified cursor.
Expand Down Expand Up @@ -2374,6 +2388,8 @@ class ASTReader

ExtKind hasExternalDefinitions(const Decl *D) override;

bool wasThisDeclarationADefinition(const FunctionDecl *FD) override;

/// Retrieve a selector from the given module with its local ID
/// number.
Selector getLocalSelector(ModuleFile &M, unsigned LocalID);
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/ExternalASTSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ ExternalASTSource::hasExternalDefinitions(const Decl *D) {
return EK_ReplyHazy;
}

bool ExternalASTSource::wasThisDeclarationADefinition(const FunctionDecl *FD) {
return false;
}

void ExternalASTSource::FindFileRegionDecls(FileID File, unsigned Offset,
unsigned Length,
SmallVectorImpl<Decl *> &Decls) {}
Expand Down
12 changes: 7 additions & 5 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2572,11 +2572,13 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(
// Friend function defined withing class template may stop being function
// definition during AST merges from different modules, in this case decl
// with function body should be used for instantiation.
if (isFriend) {
const FunctionDecl *Defn = nullptr;
if (D->hasBody(Defn)) {
D = const_cast<FunctionDecl *>(Defn);
FunctionTemplate = Defn->getDescribedFunctionTemplate();
if (ExternalASTSource *Source = SemaRef.Context.getExternalSource()) {
if (isFriend && Source->wasThisDeclarationADefinition(D)) {
const FunctionDecl *Defn = nullptr;
if (D->hasBody(Defn)) {
D = const_cast<FunctionDecl *>(Defn);
FunctionTemplate = Defn->getDescribedFunctionTemplate();
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9660,6 +9660,13 @@ ExternalASTSource::ExtKind ASTReader::hasExternalDefinitions(const Decl *FD) {
return I->second ? EK_Never : EK_Always;
}

bool ASTReader::wasThisDeclarationADefinition(const FunctionDecl *FD) {
auto I = ExternalDeclarationBitsMap.find(FD);
if (I == ExternalDeclarationBitsMap.end())
return false;
return I->second.ThisDeclarationWasADefinition;
}

Selector ASTReader::getLocalSelector(ModuleFile &M, unsigned LocalID) {
return DecodeSelector(getGlobalSelectorID(M, LocalID));
}
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,9 @@ void ASTDeclReader::ReadFunctionDefinition(FunctionDecl *FD) {
}
// Store the offset of the body so we can lazily load it later.
Reader.PendingBodies[FD] = GetCurrentCursorOffset();
// For now remember ThisDeclarationWasADefinition only for friend functions.
if (FD->getFriendObjectKind())
Reader.ExternalDeclarationBitsMap[FD].ThisDeclarationWasADefinition = true;
}

void ASTDeclReader::Visit(Decl *D) {
Expand Down
39 changes: 39 additions & 0 deletions clang/test/SemaCXX/friend-default-parameters-modules.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// RUN: rm -fR %t
// RUN: split-file %s %t
// RUN: cd %t
// RUN: %clang_cc1 -std=c++20 -fmodule-map-file=modules.map -xc++ -emit-module -fmodule-name=foo modules.map -o foo.pcm
// RUN: %clang_cc1 -std=c++20 -fmodule-map-file=modules.map -O1 -emit-obj main.cc -verify -fmodule-file=foo.pcm

//--- modules.map
module "foo" {
export *
module "foo.h" {
export *
header "foo.h"
}
}

//--- foo.h
#pragma once

template <int>
void Create(const void* = nullptr);

template <int>
struct ObjImpl {
template <int>
friend void ::Create(const void*);
};

template <int I>
void Create(const void*) {
(void) ObjImpl<I>{};
}

//--- main.cc
// expected-no-diagnostics
#include "foo.h"

int main() {
Create<42>();
}
21 changes: 21 additions & 0 deletions clang/test/SemaCXX/friend-default-parameters.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %clang_cc1 -std=c++20 -verify -emit-llvm-only %s

template <int>
void Create(const void* = nullptr);

template <int>
struct ObjImpl {
template <int>
friend void ::Create(const void*);
};

template <int I>
void Create(const void*) {
(void) ObjImpl<I>{};
}

int main() {
Create<42>();
}

// expected-no-diagnostics
Loading