Skip to content

[Clang] Add __builtin_common_type #99473

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 9 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// The identifier '__type_pack_element'.
mutable IdentifierInfo *TypePackElementName = nullptr;

/// The identifier '__common_type'.
mutable IdentifierInfo *CommonTypeName = nullptr;

QualType ObjCConstantStringType;
mutable RecordDecl *CFConstantStringTagDecl = nullptr;
mutable TypedefDecl *CFConstantStringTypeDecl = nullptr;
Expand Down Expand Up @@ -606,6 +609,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
mutable ExternCContextDecl *ExternCContext = nullptr;
mutable BuiltinTemplateDecl *MakeIntegerSeqDecl = nullptr;
mutable BuiltinTemplateDecl *TypePackElementDecl = nullptr;
mutable BuiltinTemplateDecl *CommonTypeDecl = nullptr;

/// The associated SourceManager object.
SourceManager &SourceMgr;
Expand Down Expand Up @@ -1107,6 +1111,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
ExternCContextDecl *getExternCContextDecl() const;
BuiltinTemplateDecl *getMakeIntegerSeqDecl() const;
BuiltinTemplateDecl *getTypePackElementDecl() const;
BuiltinTemplateDecl *getCommonTypeDecl() const;

// Builtin Types.
CanQualType VoidTy;
Expand Down Expand Up @@ -1984,6 +1989,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
return TypePackElementName;
}

IdentifierInfo *getCommonTypeName() const {
if (!CommonTypeName)
CommonTypeName = &Idents.get("__common_type");
return CommonTypeName;
}

/// Retrieve the Objective-C "instancetype" type, if already known;
/// otherwise, returns a NULL type;
QualType getObjCInstanceType() {
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/AST/DeclID.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,16 @@ enum PredefinedDeclIDs {

/// The internal '__type_pack_element' template.
PREDEF_DECL_TYPE_PACK_ELEMENT_ID = 17,

/// The internal '__common_type' template.
PREDEF_DECL_COMMON_TYPE_ID = 18,
};

/// The number of declaration IDs that are predefined.
///
/// For more information about predefined declarations, see the
/// \c PredefinedDeclIDs type and the PREDEF_DECL_*_ID constants.
const unsigned int NUM_PREDEF_DECL_IDS = 18;
const unsigned int NUM_PREDEF_DECL_IDS = 19;

/// GlobalDeclID means DeclID in the current ASTContext and LocalDeclID means
/// DeclID specific to a certain ModuleFile. Specially, in ASTWriter, the
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/Basic/Builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ enum BuiltinTemplateKind : int {
BTK__make_integer_seq,

/// This names the __type_pack_element BuiltinTemplateDecl.
BTK__type_pack_element
BTK__type_pack_element,

/// This names the __common_type BuiltinTemplateDecl.
BTK__common_type,
};

} // end namespace clang
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -2282,6 +2282,10 @@ class Sema final : public SemaBase {
/// Check to see if a given expression could have '.c_str()' called on it.
bool hasCStrMethod(const Expr *E);

// Check whether a type member 'Type::Name' exists, and if yes, return the
// type. If there is no type, the QualType is null
QualType getTypeMember(StringRef Name, QualType Type);

/// Diagnose pointers that are always non-null.
/// \param E the expression containing the pointer
/// \param NullKind NPCK_NotNull if E is a cast to bool, otherwise, E is
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,13 @@ ASTContext::getTypePackElementDecl() const {
return TypePackElementDecl;
}

BuiltinTemplateDecl *ASTContext::getCommonTypeDecl() const {
if (!CommonTypeDecl)
CommonTypeDecl =
buildBuiltinTemplateDecl(BTK__common_type, getCommonTypeName());
return CommonTypeDecl;
}

RecordDecl *ASTContext::buildImplicitRecord(StringRef Name,
RecordDecl::TagKind TK) const {
SourceLocation Loc;
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5408,6 +5408,9 @@ ExpectedDecl ASTNodeImporter::VisitBuiltinTemplateDecl(BuiltinTemplateDecl *D) {
case BuiltinTemplateKind::BTK__type_pack_element:
ToD = Importer.getToContext().getTypePackElementDecl();
break;
case BuiltinTemplateKind::BTK__common_type:
ToD = Importer.getToContext().getCommonTypeDecl();
break;
}
assert(ToD && "BuiltinTemplateDecl of unsupported kind!");
Importer.MapImported(D, ToD);
Expand Down
53 changes: 53 additions & 0 deletions clang/lib/AST/DeclTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1599,13 +1599,66 @@ createTypePackElementParameterList(const ASTContext &C, DeclContext *DC) {
nullptr);
}

static TemplateParameterList *createCommonTypeList(const ASTContext &C,
DeclContext *DC) {
// class... Args
auto *Args = TemplateTypeParmDecl::Create(
C, DC, {}, {}, /*Depth=*/1, /*Position=*/0, /*Id=*/nullptr,
/*Typename=*/false, /*ParameterPack=*/true);
Args->setImplicit();

// <class... Args>
auto *BaseTemplateList =
TemplateParameterList::Create(C, {}, {}, Args, {}, nullptr);

// template <class... Args> class BaseTemplate
auto *BaseTemplate = TemplateTemplateParmDecl::Create(
C, DC, {}, /*Depth=*/0, /*Position=*/0, /*ParameterPack=*/false, {},
/*Typename=*/false, BaseTemplateList);

// class TypeMember
auto *TypeMember = TemplateTypeParmDecl::Create(
C, DC, {}, {}, /*Depth=*/1, /*Position=*/0, /*Id=*/nullptr,
/*Typename=*/false, /*ParameterPack=*/false);

// <class TypeMember>
auto *HasTypeMemberList =
TemplateParameterList::Create(C, {}, {}, TypeMember, {}, nullptr);

// template <class TypeMember> class HasTypeMember
auto *HasTypeMember =
TemplateTemplateParmDecl::Create(C, DC, {}, /*Depth=*/0, /*Position=*/1,
/*ParameterPack=*/false, {},
/*Typename=*/false, HasTypeMemberList);

// class HasNoTypeMember
auto *HasNoTypeMember = TemplateTypeParmDecl::Create(
C, DC, {}, {}, /*Depth=*/0, /*Position=*/2, /*Id=*/nullptr,
/*Typename=*/false, /*ParameterPack=*/false);

// class... Ts
auto *Ts = TemplateTypeParmDecl::Create(
C, DC, {}, {}, /*Depth=*/0, /*Position=*/3,
/*Id=*/nullptr, /*Typename=*/false, /*ParameterPack=*/true);
Ts->setImplicit();

// template <template <class... Args> class BaseTemplate,
// template <class TypeMember> class HasTypeMember, class HasNoTypeMember,
// class... Ts>
return TemplateParameterList::Create(
C, {}, {}, {BaseTemplate, HasTypeMember, HasNoTypeMember, Ts}, {},
nullptr);
}

static TemplateParameterList *createBuiltinTemplateParameterList(
const ASTContext &C, DeclContext *DC, BuiltinTemplateKind BTK) {
switch (BTK) {
case BTK__make_integer_seq:
return createMakeIntegerSeqParameterList(C, DC);
case BTK__type_pack_element:
return createTypePackElementParameterList(C, DC);
case BTK__common_type:
return createCommonTypeList(C, DC);
}

llvm_unreachable("unhandled BuiltinTemplateKind!");
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Lex/PPMacroExpansion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1822,6 +1822,7 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
// Report builtin templates as being builtins.
.Case("__make_integer_seq", getLangOpts().CPlusPlus)
.Case("__type_pack_element", getLangOpts().CPlusPlus)
.Case("__common_type", getLangOpts().CPlusPlus)
// Likewise for some builtin preprocessor macros.
// FIXME: This is inconsistent; we usually suggest detecting
// builtin macros via #ifdef. Don't add more cases here.
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6844,6 +6844,14 @@ CXXRecordMembersNamed(StringRef Name, Sema &S, QualType Ty) {
return Results;
}

QualType Sema::getTypeMember(StringRef Name, QualType Type) {
auto Results = CXXRecordMembersNamed<TypeDecl>(Name, *this, Type);
assert(Results.size() <= 1);
if (Results.empty())
return {};
return Context.getTypeDeclType(*Results.begin());
}

/// Check if we could call '.c_str()' on an object.
///
/// FIXME: This returns the wrong results in some cases (if cv-qualifiers don't
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ bool Sema::LookupBuiltin(LookupResult &R) {
R.addDecl(getASTContext().getTypePackElementDecl());
return true;
}
if (II == getASTContext().getCommonTypeName()) {
R.addDecl(getASTContext().getCommonTypeDecl());
return true;
}
}

// Check if this is an OpenCL Builtin, and if so, insert its overloads.
Expand Down
160 changes: 159 additions & 1 deletion clang/lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,141 @@ void Sema::NoteAllFoundTemplates(TemplateName Name) {
}
}

static std::optional<QualType> commonTypeImpl(Sema &S,
TemplateName BaseTemplate,
SourceLocation TemplateLoc,
ArrayRef<TemplateArgument> Ts) {
auto lookUpCommonType = [&](TemplateArgument T1,
TemplateArgument T2) -> std::optional<QualType> {
// Don't bother looking for other specializations if both types are
// builtins - users aren't allowed to specialize for them
Comment on lines +3084 to +3085
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we diagnose the case where users do this, please? Similarly for specialising common_type<>, common_type<T>, and common_type<T1, T2, T3, Ts...>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can do that, but I don't think that's part of this project. AFAICT this requires tinkering with the template specialization code and check whether a specialization is allowed to exist. To me that seems like an entire project on its own.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to handle the first case in this patch because it is currently silently changing the outcome of common_type<builtin, builtin>. Although the standard gives us licence to silently change this, it isn't fair to users who either weren't aware of the consequences of specialising this, or are depending on such code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How is this any different from all the other cases where we switched to builtins, which results in potential breaks?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. There are only a handful of cases that this applies to, but they should also break loudly. In that case, I'd prefer it if getting that diagnostic logic into Clang preceded submitting this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given the precedent I don't think it's reasonable to block this patch on diagnosing some edge cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is only precedent due to it not having been previously recognised as an issue. It has now been identified and should be addressed, instead of increasing the problem's surface area.

We should do things right, not wrong because "they were done that way previously".

Copy link
Member

Choose a reason for hiding this comment

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

I believe it would make sense for such a diagnostic to work today, even without the builtin. For example, it would be nice for the following code to be diagnosed with a warning (or an error, whatever):

#include <type_traits>

template <>
struct std::common_type<int, int> { using type = long; }; // should warn

struct Foo { };
template <>
struct std::common_type<Foo> { using type = int; }; // should warn

// etc...

I don't think this should be tied to the implementation of the builtin, i.e. it should be diagnosed where the user writes the invalid specialization, not where we use std::common_type_t. As such, I think it would make sense to tackle this as a follow-up and I filed an issue for it: #101509

if (T1.getAsType()->isBuiltinType() && T2.getAsType()->isBuiltinType())
return commonTypeImpl(S, BaseTemplate, TemplateLoc, {T1, T2});

TemplateArgumentListInfo Args;
Args.addArgument(TemplateArgumentLoc(
T1, S.Context.getTrivialTypeSourceInfo(T1.getAsType())));
Args.addArgument(TemplateArgumentLoc(
T2, S.Context.getTrivialTypeSourceInfo(T2.getAsType())));
QualType BaseTemplateInst =
S.CheckTemplateIdType(BaseTemplate, TemplateLoc, Args);
if (S.RequireCompleteType(TemplateLoc, BaseTemplateInst,
diag::err_incomplete_type))
return std::nullopt;
if (QualType Type = S.getTypeMember("type", BaseTemplateInst);
!Type.isNull()) {
return Type;
}
return std::nullopt;
};

// Note A: For the common_type trait applied to a template parameter pack T of
// types, the member type shall be either defined or not present as follows:
switch (Ts.size()) {

// If sizeof...(T) is zero, there shall be no member type.
case 0:
return std::nullopt;

// If sizeof...(T) is one, let T0 denote the sole type constituting the
// pack T. The member typedef-name type shall denote the same type, if any, as
// common_type_t<T0, T0>; otherwise there shall be no member type.
case 1:
return lookUpCommonType(Ts[0], Ts[0]);

// If sizeof...(T) is two, let the first and second types constituting T be
// denoted by T1 and T2, respectively, and let D1 and D2 denote the same types
// as decay_t<T1> and decay_t<T2>, respectively.
case 2: {
QualType T1 = Ts[0].getAsType();
QualType T2 = Ts[1].getAsType();
QualType D1 = S.BuiltinDecay(T1, {});
QualType D2 = S.BuiltinDecay(T2, {});

// If is_same_v<T1, D1> is false or is_same_v<T2, D2> is false, let C denote
// the same type, if any, as common_type_t<D1, D2>.
if (!S.Context.hasSameType(T1, D1) || !S.Context.hasSameType(T2, D2)) {
return lookUpCommonType(D1, D2);
}

// Otherwise, if decay_t<decltype(false ? declval<D1>() : declval<D2>())>
// denotes a valid type, let C denote that type.
{
auto CheckConditionalOperands =
[&](bool ConstRefQual) -> std::optional<QualType> {
EnterExpressionEvaluationContext UnevaluatedContext(
S, Sema::ExpressionEvaluationContext::Unevaluated);
Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true);
Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl());

// false
OpaqueValueExpr CondExpr({}, S.Context.BoolTy,
ExprValueKind::VK_PRValue);
ExprResult Cond = &CondExpr;

auto EVK =
ConstRefQual ? ExprValueKind::VK_LValue : ExprValueKind::VK_PRValue;
if (ConstRefQual) {
D1.addConst();
D2.addConst();
}

// declval<D1>()
OpaqueValueExpr LHSExpr(TemplateLoc, D1, EVK);
ExprResult LHS = &LHSExpr;

// declval<D2>()
OpaqueValueExpr RHSExpr(TemplateLoc, D2, EVK);
ExprResult RHS = &RHSExpr;

ExprValueKind VK = VK_PRValue;
ExprObjectKind OK = OK_Ordinary;

// decltype(false ? declval<D1>() : declval<D2>())
QualType Result =
S.CheckConditionalOperands(Cond, LHS, RHS, VK, OK, TemplateLoc);
Copy link
Contributor

Choose a reason for hiding this comment

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

So, I think we can call CXXCheckConditionalOperands here.

But the question I have is, should we factor out the part of CXXCheckConditionalOperands that deals with types directly and use that?

One one hand, doing surgery to CXXCheckConditionalOperands requires care and might be a tad difficult, on the other hand the whole danse we are doing here is a bit convoluted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAICT we'd have to do that for all the extensions as well, which seems like it'd be a lot of work. We'd also only save the SFINAE context and the opaque values, which seems to me like not that much code. It'd probably be a bit faster, but I'm not sure it's that significant.


if (Result.isNull() || SFINAE.hasErrorOccurred())
return std::nullopt;

// decay_t<decltype(false ? declval<D1>() : declval<D2>())>
return S.BuiltinDecay(Result, TemplateLoc);
};

if (auto Res = CheckConditionalOperands(false))
return Res;

// Let:
// CREF(A) be add_lvalue_reference_t<const remove_reference_t<A>>,
// COND-RES(X, Y) be
// decltype(false ? declval<X(&)()>()() : declval<Y(&)()>()()).

// C++20 only
// Otherwise, if COND-RES(CREF(D1), CREF(D2)) denotes a type, let C denote
// the type decay_t<COND-RES(CREF(D1), CREF(D2))>.
if (!S.Context.getLangOpts().CPlusPlus20)
return std::nullopt;
return CheckConditionalOperands(true);
}
}

// If sizeof...(T) is greater than two, let T1, T2, and R, respectively,
// denote the first, second, and (pack of) remaining types constituting T. Let
// C denote the same type, if any, as common_type_t<T1, T2>. If there is such
// a type C, the member typedef-name type shall denote the same type, if any,
// as common_type_t<C, R...>. Otherwise, there shall be no member type.
default: {
std::optional<QualType> Result = Ts[Ts.size() - 1].getAsType();
for (size_t i = Ts.size() - 1; i != 0; --i) {
Result = lookUpCommonType(Ts[i - 1].getAsType(), *Result);
if (!Result)
return std::nullopt;
}
return Result;
}
}
}

static QualType
checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
ArrayRef<TemplateArgument> Converted,
Expand Down Expand Up @@ -3114,7 +3249,7 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
TemplateLoc, SyntheticTemplateArgs);
}

case BTK__type_pack_element:
case BTK__type_pack_element: {
// Specializations of
// __type_pack_element<Index, T_1, ..., T_N>
// are treated like T_Index.
Expand All @@ -3140,6 +3275,29 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
int64_t N = Index.getExtValue();
return Ts.getPackAsArray()[N].getAsType();
}

case BTK__common_type: {
assert(Converted.size() == 4);
if (Converted[0].isDependent() || Converted[1].isDependent() ||
Converted[2].isDependent() || Converted[3].isDependent())
return Context.getCanonicalTemplateSpecializationType(TemplateName(BTD),
Converted);

TemplateName BaseTemplate = Converted[0].getAsTemplate();
TemplateName HasTypeMember = Converted[1].getAsTemplate();
QualType HasNoTypeMember = Converted[2].getAsType();
ArrayRef<TemplateArgument> Ts = Converted[3].getPackAsArray();
if (auto CT = commonTypeImpl(SemaRef, BaseTemplate, TemplateLoc, Ts)) {
TemplateArgumentListInfo TAs;
TAs.addArgument(TemplateArgumentLoc(
TemplateArgument(*CT), SemaRef.Context.getTrivialTypeSourceInfo(
*CT, TemplateArgs[1].getLocation())));

return SemaRef.CheckTemplateIdType(HasTypeMember, TemplateLoc, TAs);
}
return HasNoTypeMember;
Comment on lines +3313 to +3315
Copy link
Contributor

Choose a reason for hiding this comment

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

This is missing wrapping the result in a TemplateSpecializationType, just like the other builtin templates do, because users of CheckTemplateId assume that it returns a TST of whatever TemplateName it is checking.
Otherwise, these users will wrap the resulting type in a TemplateSpecializationTypeLoc which is unrelated to what is returned.

Worse, in the negative case, HasNoTypeMember can be something which is not a TST at all, and this will lead to crashes when we try to build a TypeLoc with the wrong kind.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I understand. __type_pack_element above also just returns a type in the usual case.

}
}
llvm_unreachable("unexpected BuiltinTemplateDecl!");
}

Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Serialization/ASTReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7789,6 +7789,9 @@ static Decl *getPredefinedDecl(ASTContext &Context, PredefinedDeclIDs ID) {

case PREDEF_DECL_TYPE_PACK_ELEMENT_ID:
return Context.getTypePackElementDecl();

case PREDEF_DECL_COMMON_TYPE_ID:
return Context.getCommonTypeDecl();
}
llvm_unreachable("PredefinedDeclIDs unknown enum value");
}
Expand Down
Loading
Loading