Skip to content

Commit 9583b39

Browse files
cor3ntinzyn0217
andauthored
[Clang] Normalize constraints before checking for satisfaction (#141776)
In the standard, constraint satisfaction checking is done on the normalized form of a constraint. Clang instead substitutes on the non-normalized form, which causes us to report substitution failures in template arguments or concept ids, which is non-conforming but unavoidable without a parameter mapping This patch normalizes before satisfaction checking. However, we preserve concept-id nodes in the normalized form, solely for diagnostics purposes. This addresses #61811 and related concepts conformance bugs, ideally to make the remaining implementation of concept template parameters easier Fixes #135190 Fixes #61811 Co-authored-by: Younan Zhang <zyn7109@gmail.com>
1 parent db39ef9 commit 9583b39

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2638
-1197
lines changed

clang/docs/InternalsManual.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,6 +2859,67 @@ This library is called by the :ref:`Parser library <Parser>` during parsing to
28592859
do semantic analysis of the input. For valid programs, Sema builds an AST for
28602860
parsed constructs.
28612861

2862+
2863+
Concept Satisfaction Checking and Subsumption
2864+
---------------------------------------------
2865+
2866+
As per the C++ standard, constraints are `normalized <https://eel.is/c++draft/temp.constr.normal>`_
2867+
and the normal form is used both for subsumption, and constraint checking.
2868+
Both depend on a parameter mapping that substitutes lazily. In particular,
2869+
we should not substitute in unused arguments.
2870+
2871+
Clang follows the order of operations prescribed by the standard.
2872+
2873+
Normalization happens prior to satisfaction and subsumption
2874+
and is handled by ``NormalizedConstraint``.
2875+
2876+
Clang preserves in the normalized form intermediate concept-ids
2877+
(``ConceptIdConstraint``) This is used for diagnostics only and no substitution
2878+
happens in a ConceptIdConstraint if its expression is satisfied.
2879+
2880+
The normal form of the associated constraints of a declaration is cached in
2881+
Sema::NormalizationCache such that it is only computed once.
2882+
2883+
A ``NormalizedConstraint`` is a recursive data structure, where each node
2884+
contains a parameter mapping, represented by the indexes of all parameter
2885+
being used.
2886+
2887+
Checking satisfaction is done by ``ConstraintSatisfactionChecker``, recursively
2888+
walking ``NormalizedConstraint``. At each level, we substitute the outermost
2889+
level of the template arguments referenced in the parameter mapping of a
2890+
normalized expression (``MultiLevelTemplateArgumentList``).
2891+
2892+
For the following example,
2893+
2894+
.. code-block:: c++
2895+
2896+
template <typename T>
2897+
concept A = __is_same(T, int);
2898+
2899+
template <typename U>
2900+
concept B = A<U> && __is_same(U, int);
2901+
2902+
The normal form of B is
2903+
2904+
.. code-block:: c++
2905+
2906+
__is_same(T, int) /*T->U, innermost level*/
2907+
&& __is_same(U, int) {U->U} /*T->U, outermost level*/
2908+
2909+
After substitution in the mapping, we substitute in the constraint expression
2910+
using that copy of the ``MultiLevelTemplateArgumentList``, and then evaluate it.
2911+
2912+
Because this is expensive, it is cached in
2913+
``UnsubstitutedConstraintSatisfactionCache``.
2914+
2915+
Any error during satisfaction is recorded in ``ConstraintSatisfaction``.
2916+
for nested requirements, ``ConstraintSatisfaction`` is stored (including
2917+
diagnostics) in the AST, which is something we might want to improve.
2918+
2919+
When an atomic constraint is not satified, we try to substitute into any
2920+
enclosing concept-id using the same mechanism described above, for
2921+
diagnostics purpose, and inject that in the ``ConstraintSatisfaction``.
2922+
28622923
.. _CodeGen:
28632924

28642925
The CodeGen Library

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ C++23 Feature Support
160160
C++20 Feature Support
161161
^^^^^^^^^^^^^^^^^^^^^
162162

163+
- Clang now normalizes constraints before checking whether they are satisfied, as mandated by the standard.
164+
As a result, Clang no longer incorrectly diagnoses substitution failures in template arguments only
165+
used in concept-ids, and produces better diagnostics for satisfaction failure. (#GH61811) (#GH135190)
166+
163167
C++17 Feature Support
164168
^^^^^^^^^^^^^^^^^^^^^
165169

clang/include/clang/AST/ASTConcept.h

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,20 @@ namespace clang {
2828

2929
class ConceptDecl;
3030
class TemplateDecl;
31+
class ConceptReference;
3132
class Expr;
3233
class NamedDecl;
3334
struct PrintingPolicy;
3435

36+
/// Unsatisfied constraint expressions if the template arguments could be
37+
/// substituted into them, or a diagnostic if substitution resulted in
38+
/// an invalid expression.
39+
///
40+
using ConstraintSubstitutionDiagnostic = std::pair<SourceLocation, StringRef>;
41+
using UnsatisfiedConstraintRecord =
42+
llvm::PointerUnion<const Expr *, const ConceptReference *,
43+
const ConstraintSubstitutionDiagnostic *>;
44+
3545
/// The result of a constraint satisfaction check, containing the necessary
3646
/// information to diagnose an unsatisfied constraint.
3747
class ConstraintSatisfaction : public llvm::FoldingSetNode {
@@ -48,16 +58,13 @@ class ConstraintSatisfaction : public llvm::FoldingSetNode {
4858
ArrayRef<TemplateArgument> TemplateArgs)
4959
: ConstraintOwner(ConstraintOwner), TemplateArgs(TemplateArgs) {}
5060

51-
using SubstitutionDiagnostic = std::pair<SourceLocation, StringRef>;
52-
using Detail = llvm::PointerUnion<Expr *, SubstitutionDiagnostic *>;
53-
5461
bool IsSatisfied = false;
5562
bool ContainsErrors = false;
5663

5764
/// \brief The substituted constraint expr, if the template arguments could be
5865
/// substituted into them, or a diagnostic if substitution resulted in an
5966
/// invalid expression.
60-
llvm::SmallVector<Detail, 4> Details;
67+
llvm::SmallVector<UnsatisfiedConstraintRecord, 4> Details;
6168

6269
void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &C) {
6370
Profile(ID, C, ConstraintOwner, TemplateArgs);
@@ -69,19 +76,12 @@ class ConstraintSatisfaction : public llvm::FoldingSetNode {
6976

7077
bool HasSubstitutionFailure() {
7178
for (const auto &Detail : Details)
72-
if (Detail.dyn_cast<SubstitutionDiagnostic *>())
79+
if (Detail.dyn_cast<const ConstraintSubstitutionDiagnostic *>())
7380
return true;
7481
return false;
7582
}
7683
};
7784

78-
/// Pairs of unsatisfied atomic constraint expressions along with the
79-
/// substituted constraint expr, if the template arguments could be
80-
/// substituted into them, or a diagnostic if substitution resulted in
81-
/// an invalid expression.
82-
using UnsatisfiedConstraintRecord =
83-
llvm::PointerUnion<Expr *, std::pair<SourceLocation, StringRef> *>;
84-
8585
/// \brief The result of a constraint satisfaction check, containing the
8686
/// necessary information to diagnose an unsatisfied constraint.
8787
///
@@ -101,6 +101,10 @@ struct ASTConstraintSatisfaction final :
101101
return getTrailingObjects() + NumRecords;
102102
}
103103

104+
ArrayRef<UnsatisfiedConstraintRecord> records() const {
105+
return {begin(), end()};
106+
}
107+
104108
ASTConstraintSatisfaction(const ASTContext &C,
105109
const ConstraintSatisfaction &Satisfaction);
106110
ASTConstraintSatisfaction(const ASTContext &C,
@@ -282,6 +286,11 @@ class TypeConstraint {
282286
}
283287
};
284288

289+
/// Insertion operator for diagnostics. This allows sending ConceptReferences's
290+
/// into a diagnostic with <<.
291+
const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
292+
const ConceptReference *C);
293+
285294
} // clang
286295

287296
#endif // LLVM_CLANG_AST_ASTCONCEPT_H

clang/include/clang/AST/ASTContext.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3877,7 +3877,6 @@ typename clang::LazyGenerationalUpdatePtr<Owner, T, Update>::ValueType
38773877
return new (Ctx) LazyData(Source, Value);
38783878
return Value;
38793879
}
3880-
38813880
template <> struct llvm::DenseMapInfo<llvm::FoldingSetNodeID> {
38823881
static FoldingSetNodeID getEmptyKey() { return FoldingSetNodeID{}; }
38833882

clang/include/clang/Sema/Sema.h

Lines changed: 64 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
#include "clang/Sema/Redeclaration.h"
6666
#include "clang/Sema/Scope.h"
6767
#include "clang/Sema/SemaBase.h"
68+
#include "clang/Sema/SemaConcept.h"
6869
#include "clang/Sema/TypoCorrection.h"
6970
#include "clang/Sema/Weak.h"
7071
#include "llvm/ADT/APInt.h"
@@ -11694,8 +11695,9 @@ class Sema final : public SemaBase {
1169411695
ExprResult
1169511696
CheckConceptTemplateId(const CXXScopeSpec &SS, SourceLocation TemplateKWLoc,
1169611697
const DeclarationNameInfo &ConceptNameInfo,
11697-
NamedDecl *FoundDecl, ConceptDecl *NamedConcept,
11698-
const TemplateArgumentListInfo *TemplateArgs);
11698+
NamedDecl *FoundDecl, TemplateDecl *NamedConcept,
11699+
const TemplateArgumentListInfo *TemplateArgs,
11700+
bool DoCheckConstraintSatisfaction = true);
1169911701

1170011702
void diagnoseMissingTemplateArguments(TemplateName Name, SourceLocation Loc);
1170111703
void diagnoseMissingTemplateArguments(const CXXScopeSpec &SS,
@@ -12025,6 +12027,13 @@ class Sema final : public SemaBase {
1202512027
bool UpdateArgsWithConversions = true,
1202612028
bool *ConstraintsNotSatisfied = nullptr);
1202712029

12030+
bool CheckTemplateArgumentList(
12031+
TemplateDecl *Template, TemplateParameterList *Params,
12032+
SourceLocation TemplateLoc, TemplateArgumentListInfo &TemplateArgs,
12033+
const DefaultArguments &DefaultArgs, bool PartialTemplateArgs,
12034+
CheckTemplateArgumentInfo &CTAI, bool UpdateArgsWithConversions = true,
12035+
bool *ConstraintsNotSatisfied = nullptr);
12036+
1202812037
bool CheckTemplateTypeArgument(
1202912038
TemplateTypeParmDecl *Param, TemplateArgumentLoc &Arg,
1203012039
SmallVectorImpl<TemplateArgument> &SugaredConverted,
@@ -12783,6 +12792,18 @@ class Sema final : public SemaBase {
1278312792
void MarkUsedTemplateParameters(const Expr *E, bool OnlyDeduced,
1278412793
unsigned Depth, llvm::SmallBitVector &Used);
1278512794

12795+
/// Mark which template parameters are named in a given expression.
12796+
///
12797+
/// Unlike MarkUsedTemplateParameters, this excludes parameter that
12798+
/// are used but not directly named by an expression - i.e. it excludes
12799+
/// any template parameter that denotes the type of a referenced NTTP.
12800+
///
12801+
/// \param Used a bit vector whose elements will be set to \c true
12802+
/// to indicate when the corresponding template parameter will be
12803+
/// deduced.
12804+
void MarkUsedTemplateParametersForSubsumptionParameterMapping(
12805+
const Expr *E, unsigned Depth, llvm::SmallBitVector &Used);
12806+
1278612807
/// Mark which template parameters can be deduced from a given
1278712808
/// template argument list.
1278812809
///
@@ -12799,6 +12820,9 @@ class Sema final : public SemaBase {
1279912820
void MarkUsedTemplateParameters(ArrayRef<TemplateArgument> TemplateArgs,
1280012821
unsigned Depth, llvm::SmallBitVector &Used);
1280112822

12823+
void MarkUsedTemplateParameters(ArrayRef<TemplateArgumentLoc> TemplateArgs,
12824+
unsigned Depth, llvm::SmallBitVector &Used);
12825+
1280212826
void
1280312827
MarkDeducedTemplateParameters(const FunctionTemplateDecl *FunctionTemplate,
1280412828
llvm::SmallBitVector &Deduced) {
@@ -13096,6 +13120,9 @@ class Sema final : public SemaBase {
1309613120
/// Whether we're substituting into constraints.
1309713121
bool InConstraintSubstitution;
1309813122

13123+
/// Whether we're substituting into the parameter mapping of a constraint.
13124+
bool InParameterMappingSubstitution;
13125+
1309913126
/// The point of instantiation or synthesis within the source code.
1310013127
SourceLocation PointOfInstantiation;
1310113128

@@ -13359,6 +13386,11 @@ class Sema final : public SemaBase {
1335913386
const MultiLevelTemplateArgumentList &TemplateArgs,
1336013387
TemplateArgumentListInfo &Outputs);
1336113388

13389+
bool SubstTemplateArgumentsInParameterMapping(
13390+
ArrayRef<TemplateArgumentLoc> Args, SourceLocation BaseLoc,
13391+
const MultiLevelTemplateArgumentList &TemplateArgs,
13392+
TemplateArgumentListInfo &Out, bool BuildPackExpansionTypes);
13393+
1336213394
/// Retrieve the template argument list(s) that should be used to
1336313395
/// instantiate the definition of the given declaration.
1336413396
///
@@ -13820,6 +13852,12 @@ class Sema final : public SemaBase {
1382013852
CodeSynthesisContexts.back().InConstraintSubstitution;
1382113853
}
1382213854

13855+
bool inParameterMappingSubstitution() const {
13856+
return !CodeSynthesisContexts.empty() &&
13857+
CodeSynthesisContexts.back().InParameterMappingSubstitution &&
13858+
!inConstraintSubstitution();
13859+
}
13860+
1382313861
using EntityPrinter = llvm::function_ref<void(llvm::raw_ostream &)>;
1382413862

1382513863
/// \brief create a Requirement::SubstitutionDiagnostic with only a
@@ -14704,6 +14742,10 @@ class Sema final : public SemaBase {
1470414742
SatisfactionStack.swap(NewSS);
1470514743
}
1470614744

14745+
using ConstrainedDeclOrNestedRequirement =
14746+
llvm::PointerUnion<const NamedDecl *,
14747+
const concepts::NestedRequirement *>;
14748+
1470714749
/// Check whether the given expression is a valid constraint expression.
1470814750
/// A diagnostic is emitted if it is not, false is returned, and
1470914751
/// PossibleNonPrimary will be set to true if the failure might be due to a
@@ -14728,44 +14770,12 @@ class Sema final : public SemaBase {
1472814770
/// \returns true if an error occurred and satisfaction could not be checked,
1472914771
/// false otherwise.
1473014772
bool CheckConstraintSatisfaction(
14731-
const NamedDecl *Template,
14773+
ConstrainedDeclOrNestedRequirement Entity,
1473214774
ArrayRef<AssociatedConstraint> AssociatedConstraints,
1473314775
const MultiLevelTemplateArgumentList &TemplateArgLists,
14734-
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction) {
14735-
llvm::SmallVector<Expr *, 4> Converted;
14736-
return CheckConstraintSatisfaction(Template, AssociatedConstraints,
14737-
Converted, TemplateArgLists,
14738-
TemplateIDRange, Satisfaction);
14739-
}
14740-
14741-
/// \brief Check whether the given list of constraint expressions are
14742-
/// satisfied (as if in a 'conjunction') given template arguments.
14743-
/// Additionally, takes an empty list of Expressions which is populated with
14744-
/// the instantiated versions of the ConstraintExprs.
14745-
/// \param Template the template-like entity that triggered the constraints
14746-
/// check (either a concept or a constrained entity).
14747-
/// \param ConstraintExprs a list of constraint expressions, treated as if
14748-
/// they were 'AND'ed together.
14749-
/// \param ConvertedConstraints a out parameter that will get populated with
14750-
/// the instantiated version of the ConstraintExprs if we successfully checked
14751-
/// satisfaction.
14752-
/// \param TemplateArgList the multi-level list of template arguments to
14753-
/// substitute into the constraint expression. This should be relative to the
14754-
/// top-level (hence multi-level), since we need to instantiate fully at the
14755-
/// time of checking.
14756-
/// \param TemplateIDRange The source range of the template id that
14757-
/// caused the constraints check.
14758-
/// \param Satisfaction if true is returned, will contain details of the
14759-
/// satisfaction, with enough information to diagnose an unsatisfied
14760-
/// expression.
14761-
/// \returns true if an error occurred and satisfaction could not be checked,
14762-
/// false otherwise.
14763-
bool CheckConstraintSatisfaction(
14764-
const NamedDecl *Template,
14765-
ArrayRef<AssociatedConstraint> AssociatedConstraints,
14766-
llvm::SmallVectorImpl<Expr *> &ConvertedConstraints,
14767-
const MultiLevelTemplateArgumentList &TemplateArgList,
14768-
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction);
14776+
SourceRange TemplateIDRange, ConstraintSatisfaction &Satisfaction,
14777+
const ConceptReference *TopLevelConceptId = nullptr,
14778+
Expr **ConvertedExpr = nullptr);
1476914779

1477014780
/// \brief Check whether the given non-dependent constraint expression is
1477114781
/// satisfied. Returns false and updates Satisfaction with the satisfaction
@@ -14831,16 +14841,17 @@ class Sema final : public SemaBase {
1483114841
/// \param First whether this is the first time an unsatisfied constraint is
1483214842
/// diagnosed for this error.
1483314843
void DiagnoseUnsatisfiedConstraint(const ConstraintSatisfaction &Satisfaction,
14844+
SourceLocation Loc = {},
1483414845
bool First = true);
1483514846

1483614847
/// \brief Emit diagnostics explaining why a constraint expression was deemed
1483714848
/// unsatisfied.
1483814849
void
14839-
DiagnoseUnsatisfiedConstraint(const ASTConstraintSatisfaction &Satisfaction,
14850+
DiagnoseUnsatisfiedConstraint(const ConceptSpecializationExpr *ConstraintExpr,
1484014851
bool First = true);
1484114852

1484214853
const NormalizedConstraint *getNormalizedAssociatedConstraints(
14843-
const NamedDecl *ConstrainedDecl,
14854+
ConstrainedDeclOrNestedRequirement Entity,
1484414855
ArrayRef<AssociatedConstraint> AssociatedConstraints);
1484514856

1484614857
/// \brief Check whether the given declaration's associated constraints are
@@ -14865,6 +14876,15 @@ class Sema final : public SemaBase {
1486514876
const NamedDecl *D1, ArrayRef<AssociatedConstraint> AC1,
1486614877
const NamedDecl *D2, ArrayRef<AssociatedConstraint> AC2);
1486714878

14879+
/// Cache the satisfaction of an atomic constraint.
14880+
/// The key is based on the unsubstituted expression and the parameter
14881+
/// mapping. This lets us not substituting the mapping more than once,
14882+
/// which is (very!) expensive.
14883+
/// FIXME: this should be private.
14884+
llvm::DenseMap<llvm::FoldingSetNodeID,
14885+
UnsubstitutedConstraintSatisfactionCacheResult>
14886+
UnsubstitutedConstraintSatisfactionCache;
14887+
1486814888
private:
1486914889
/// Caches pairs of template-like decls whose associated constraints were
1487014890
/// checked for subsumption and whether or not the first's constraints did in
@@ -14875,8 +14895,11 @@ class Sema final : public SemaBase {
1487514895
/// constrained declarations). If an error occurred while normalizing the
1487614896
/// associated constraints of the template or concept, nullptr will be cached
1487714897
/// here.
14878-
llvm::DenseMap<const NamedDecl *, NormalizedConstraint *> NormalizationCache;
14898+
llvm::DenseMap<ConstrainedDeclOrNestedRequirement, NormalizedConstraint *>
14899+
NormalizationCache;
1487914900

14901+
/// Cache whether the associated constraint of a declaration
14902+
/// is satisfied.
1488014903
llvm::ContextualFoldingSet<ConstraintSatisfaction, const ASTContext &>
1488114904
SatisfactionCache;
1488214905

0 commit comments

Comments
 (0)