Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 1 | //===-- SemaConcept.cpp - Semantic Analysis for Constraints and Concepts --===// |
| 2 | // |
| 3 | // The LLVM Compiler Infrastructure |
| 4 | // |
| 5 | // This file is distributed under the University of Illinois Open Source |
| 6 | // License. See LICENSE.TXT for details. |
| 7 | // |
| 8 | //===----------------------------------------------------------------------===// |
| 9 | // |
| 10 | // This file implements semantic analysis for C++ constraints and concepts. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "clang/Sema/Sema.h" |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 15 | #include "clang/Sema/SemaInternal.h" |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 16 | #include "clang/Sema/SemaDiagnostic.h" |
| 17 | #include "clang/Sema/TemplateDeduction.h" |
| 18 | #include "clang/Sema/Template.h" |
| 19 | #include "clang/AST/ExprCXX.h" |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 20 | #include "llvm/ADT/DenseMap.h" |
| 21 | #include "llvm/ADT/PointerUnion.h" |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 22 | using namespace clang; |
| 23 | using namespace sema; |
| 24 | |
| 25 | bool Sema::CheckConstraintExpression(Expr *ConstraintExpression) { |
| 26 | // C++2a [temp.constr.atomic]p1 |
| 27 | // ..E shall be a constant expression of type bool. |
| 28 | |
| 29 | ConstraintExpression = ConstraintExpression->IgnoreParenImpCasts(); |
| 30 | |
| 31 | if (auto *BinOp = dyn_cast<BinaryOperator>(ConstraintExpression)) { |
| 32 | if (BinOp->getOpcode() == BO_LAnd || BinOp->getOpcode() == BO_LOr) |
| 33 | return CheckConstraintExpression(BinOp->getLHS()) && |
| 34 | CheckConstraintExpression(BinOp->getRHS()); |
| 35 | } else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpression)) |
| 36 | return CheckConstraintExpression(C->getSubExpr()); |
| 37 | |
| 38 | // An atomic constraint! |
| 39 | if (ConstraintExpression->isTypeDependent()) |
| 40 | return true; |
| 41 | |
| 42 | QualType Type = ConstraintExpression->getType(); |
| 43 | if (!Context.hasSameUnqualifiedType(Type, Context.BoolTy)) { |
| 44 | Diag(ConstraintExpression->getExprLoc(), |
| 45 | diag::err_non_bool_atomic_constraint) << Type |
| 46 | << ConstraintExpression->getSourceRange(); |
| 47 | return false; |
| 48 | } |
| 49 | return true; |
| 50 | } |
| 51 | |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 52 | template <typename AtomicEvaluator> |
| 53 | static bool |
| 54 | calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr, |
| 55 | ConstraintSatisfaction &Satisfaction, |
| 56 | AtomicEvaluator &&Evaluator) { |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 57 | ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts(); |
| 58 | |
| 59 | if (auto *BO = dyn_cast<BinaryOperator>(ConstraintExpr)) { |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 60 | if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr) { |
| 61 | if (calculateConstraintSatisfaction(S, BO->getLHS(), Satisfaction, |
| 62 | Evaluator)) |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 63 | return true; |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 64 | |
| 65 | bool IsLHSSatisfied = Satisfaction.IsSatisfied; |
| 66 | |
| 67 | if (BO->getOpcode() == BO_LOr && IsLHSSatisfied) |
| 68 | // [temp.constr.op] p3 |
| 69 | // A disjunction is a constraint taking two operands. To determine if |
| 70 | // a disjunction is satisfied, the satisfaction of the first operand |
| 71 | // is checked. If that is satisfied, the disjunction is satisfied. |
| 72 | // Otherwise, the disjunction is satisfied if and only if the second |
| 73 | // operand is satisfied. |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 74 | return false; |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 75 | |
| 76 | if (BO->getOpcode() == BO_LAnd && !IsLHSSatisfied) |
| 77 | // [temp.constr.op] p2 |
| 78 | // A conjunction is a constraint taking two operands. To determine if |
| 79 | // a conjunction is satisfied, the satisfaction of the first operand |
| 80 | // is checked. If that is not satisfied, the conjunction is not |
| 81 | // satisfied. Otherwise, the conjunction is satisfied if and only if |
| 82 | // the second operand is satisfied. |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 83 | return false; |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 84 | |
| 85 | return calculateConstraintSatisfaction(S, BO->getRHS(), Satisfaction, |
| 86 | std::forward<AtomicEvaluator>(Evaluator)); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 87 | } |
| 88 | } |
| 89 | else if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 90 | return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction, |
| 91 | std::forward<AtomicEvaluator>(Evaluator)); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 92 | |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 93 | // An atomic constraint expression |
| 94 | ExprResult SubstitutedAtomicExpr = Evaluator(ConstraintExpr); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 95 | |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 96 | if (SubstitutedAtomicExpr.isInvalid()) |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 97 | return true; |
| 98 | |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 99 | if (!SubstitutedAtomicExpr.isUsable()) |
| 100 | // Evaluator has decided satisfaction without yielding an expression. |
| 101 | return false; |
| 102 | |
| 103 | EnterExpressionEvaluationContext ConstantEvaluated( |
| 104 | S, Sema::ExpressionEvaluationContext::ConstantEvaluated); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 105 | SmallVector<PartialDiagnosticAt, 2> EvaluationDiags; |
| 106 | Expr::EvalResult EvalResult; |
| 107 | EvalResult.Diag = &EvaluationDiags; |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 108 | if (!SubstitutedAtomicExpr.get()->EvaluateAsRValue(EvalResult, S.Context)) { |
| 109 | // C++2a [temp.constr.atomic]p1 |
| 110 | // ...E shall be a constant expression of type bool. |
| 111 | S.Diag(SubstitutedAtomicExpr.get()->getBeginLoc(), |
| 112 | diag::err_non_constant_constraint_expression) |
| 113 | << SubstitutedAtomicExpr.get()->getSourceRange(); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 114 | for (const PartialDiagnosticAt &PDiag : EvaluationDiags) |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 115 | S.Diag(PDiag.first, PDiag.second); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 116 | return true; |
| 117 | } |
| 118 | |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 119 | Satisfaction.IsSatisfied = EvalResult.Val.getInt().getBoolValue(); |
| 120 | if (!Satisfaction.IsSatisfied) |
| 121 | Satisfaction.Details.emplace_back(ConstraintExpr, |
| 122 | SubstitutedAtomicExpr.get()); |
Saar Raz | 5d98ba6 | 2019-10-15 15:24:26 +0000 | [diff] [blame] | 123 | |
| 124 | return false; |
Saar Raz | ffa214e | 2019-10-25 00:09:37 +0300 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | template <typename TemplateDeclT> |
| 128 | static bool calculateConstraintSatisfaction( |
| 129 | Sema &S, TemplateDeclT *Template, ArrayRef<TemplateArgument> TemplateArgs, |
| 130 | SourceLocation TemplateNameLoc, MultiLevelTemplateArgumentList &MLTAL, |
| 131 | const Expr *ConstraintExpr, ConstraintSatisfaction &Satisfaction) { |
| 132 | return calculateConstraintSatisfaction( |
| 133 | S, ConstraintExpr, Satisfaction, [&](const Expr *AtomicExpr) { |
| 134 | EnterExpressionEvaluationContext ConstantEvaluated( |
| 135 | S, Sema::ExpressionEvaluationContext::ConstantEvaluated); |
| 136 | |
| 137 | // Atomic constraint - substitute arguments and check satisfaction. |
| 138 | ExprResult SubstitutedExpression; |
| 139 | { |
| 140 | TemplateDeductionInfo Info(TemplateNameLoc); |
| 141 | Sema::InstantiatingTemplate Inst(S, AtomicExpr->getBeginLoc(), |
| 142 | Sema::InstantiatingTemplate::ConstraintSubstitution{}, Template, |
| 143 | Info, AtomicExpr->getSourceRange()); |
| 144 | if (Inst.isInvalid()) |
| 145 | return ExprError(); |
| 146 | // We do not want error diagnostics escaping here. |
| 147 | Sema::SFINAETrap Trap(S); |
| 148 | SubstitutedExpression = S.SubstExpr(const_cast<Expr *>(AtomicExpr), |
| 149 | MLTAL); |
| 150 | if (SubstitutedExpression.isInvalid() || Trap.hasErrorOccurred()) { |
| 151 | // C++2a [temp.constr.atomic]p1 |
| 152 | // ...If substitution results in an invalid type or expression, the |
| 153 | // constraint is not satisfied. |
| 154 | if (!Trap.hasErrorOccurred()) |
| 155 | // A non-SFINAE error has occured as a result of this |
| 156 | // substitution. |
| 157 | return ExprError(); |
| 158 | |
| 159 | PartialDiagnosticAt SubstDiag{SourceLocation(), |
| 160 | PartialDiagnostic::NullDiagnostic()}; |
| 161 | Info.takeSFINAEDiagnostic(SubstDiag); |
| 162 | // FIXME: Concepts: This is an unfortunate consequence of there |
| 163 | // being no serialization code for PartialDiagnostics and the fact |
| 164 | // that serializing them would likely take a lot more storage than |
| 165 | // just storing them as strings. We would still like, in the |
| 166 | // future, to serialize the proper PartialDiagnostic as serializing |
| 167 | // it as a string defeats the purpose of the diagnostic mechanism. |
| 168 | SmallString<128> DiagString; |
| 169 | DiagString = ": "; |
| 170 | SubstDiag.second.EmitToString(S.getDiagnostics(), DiagString); |
| 171 | Satisfaction.Details.emplace_back( |
| 172 | AtomicExpr, |
| 173 | new (S.Context) ConstraintSatisfaction::SubstitutionDiagnostic{ |
| 174 | SubstDiag.first, |
| 175 | std::string(DiagString.begin(), DiagString.end())}); |
| 176 | Satisfaction.IsSatisfied = false; |
| 177 | return ExprEmpty(); |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | if (!S.CheckConstraintExpression(SubstitutedExpression.get())) |
| 182 | return ExprError(); |
| 183 | |
| 184 | return SubstitutedExpression; |
| 185 | }); |
| 186 | } |
| 187 | |
| 188 | template<typename TemplateDeclT> |
| 189 | static bool CheckConstraintSatisfaction(Sema &S, TemplateDeclT *Template, |
| 190 | ArrayRef<const Expr *> ConstraintExprs, |
| 191 | ArrayRef<TemplateArgument> TemplateArgs, |
| 192 | SourceRange TemplateIDRange, |
| 193 | ConstraintSatisfaction &Satisfaction) { |
| 194 | if (ConstraintExprs.empty()) { |
| 195 | Satisfaction.IsSatisfied = true; |
| 196 | return false; |
| 197 | } |
| 198 | |
| 199 | for (auto& Arg : TemplateArgs) |
| 200 | if (Arg.isInstantiationDependent()) { |
| 201 | // No need to check satisfaction for dependent constraint expressions. |
| 202 | Satisfaction.IsSatisfied = true; |
| 203 | return false; |
| 204 | } |
| 205 | |
| 206 | Sema::InstantiatingTemplate Inst(S, TemplateIDRange.getBegin(), |
| 207 | Sema::InstantiatingTemplate::ConstraintsCheck{}, Template, TemplateArgs, |
| 208 | TemplateIDRange); |
| 209 | if (Inst.isInvalid()) |
| 210 | return true; |
| 211 | |
| 212 | MultiLevelTemplateArgumentList MLTAL; |
| 213 | MLTAL.addOuterTemplateArguments(TemplateArgs); |
| 214 | |
| 215 | for (const Expr *ConstraintExpr : ConstraintExprs) { |
| 216 | if (calculateConstraintSatisfaction(S, Template, TemplateArgs, |
| 217 | TemplateIDRange.getBegin(), MLTAL, |
| 218 | ConstraintExpr, Satisfaction)) |
| 219 | return true; |
| 220 | if (!Satisfaction.IsSatisfied) |
| 221 | // [temp.constr.op] p2 |
| 222 | // [...] To determine if a conjunction is satisfied, the satisfaction |
| 223 | // of the first operand is checked. If that is not satisfied, the |
| 224 | // conjunction is not satisfied. [...] |
| 225 | return false; |
| 226 | } |
| 227 | return false; |
| 228 | } |
| 229 | |
| 230 | bool Sema::CheckConstraintSatisfaction(TemplateDecl *Template, |
| 231 | ArrayRef<const Expr *> ConstraintExprs, |
| 232 | ArrayRef<TemplateArgument> TemplateArgs, |
| 233 | SourceRange TemplateIDRange, |
| 234 | ConstraintSatisfaction &Satisfaction) { |
| 235 | return ::CheckConstraintSatisfaction(*this, Template, ConstraintExprs, |
| 236 | TemplateArgs, TemplateIDRange, |
| 237 | Satisfaction); |
| 238 | } |
| 239 | |
| 240 | bool |
| 241 | Sema::CheckConstraintSatisfaction(ClassTemplatePartialSpecializationDecl* Part, |
| 242 | ArrayRef<const Expr *> ConstraintExprs, |
| 243 | ArrayRef<TemplateArgument> TemplateArgs, |
| 244 | SourceRange TemplateIDRange, |
| 245 | ConstraintSatisfaction &Satisfaction) { |
| 246 | return ::CheckConstraintSatisfaction(*this, Part, ConstraintExprs, |
| 247 | TemplateArgs, TemplateIDRange, |
| 248 | Satisfaction); |
| 249 | } |
| 250 | |
| 251 | bool |
| 252 | Sema::CheckConstraintSatisfaction(VarTemplatePartialSpecializationDecl* Partial, |
| 253 | ArrayRef<const Expr *> ConstraintExprs, |
| 254 | ArrayRef<TemplateArgument> TemplateArgs, |
| 255 | SourceRange TemplateIDRange, |
| 256 | ConstraintSatisfaction &Satisfaction) { |
| 257 | return ::CheckConstraintSatisfaction(*this, Partial, ConstraintExprs, |
| 258 | TemplateArgs, TemplateIDRange, |
| 259 | Satisfaction); |
| 260 | } |
| 261 | |
| 262 | bool Sema::CheckConstraintSatisfaction(const Expr *ConstraintExpr, |
| 263 | ConstraintSatisfaction &Satisfaction) { |
| 264 | return calculateConstraintSatisfaction( |
| 265 | *this, ConstraintExpr, Satisfaction, |
| 266 | [](const Expr *AtomicExpr) -> ExprResult { |
| 267 | return ExprResult(const_cast<Expr *>(AtomicExpr)); |
| 268 | }); |
| 269 | } |
| 270 | |
| 271 | bool Sema::EnsureTemplateArgumentListConstraints( |
| 272 | TemplateDecl *TD, ArrayRef<TemplateArgument> TemplateArgs, |
| 273 | SourceRange TemplateIDRange) { |
| 274 | ConstraintSatisfaction Satisfaction; |
| 275 | llvm::SmallVector<const Expr *, 3> AssociatedConstraints; |
| 276 | TD->getAssociatedConstraints(AssociatedConstraints); |
| 277 | if (CheckConstraintSatisfaction(TD, AssociatedConstraints, TemplateArgs, |
| 278 | TemplateIDRange, Satisfaction)) |
| 279 | return true; |
| 280 | |
| 281 | if (!Satisfaction.IsSatisfied) { |
| 282 | SmallString<128> TemplateArgString; |
| 283 | TemplateArgString = " "; |
| 284 | TemplateArgString += getTemplateArgumentBindingsText( |
| 285 | TD->getTemplateParameters(), TemplateArgs.data(), TemplateArgs.size()); |
| 286 | |
| 287 | Diag(TemplateIDRange.getBegin(), |
| 288 | diag::err_template_arg_list_constraints_not_satisfied) |
| 289 | << (int)getTemplateNameKindForDiagnostics(TemplateName(TD)) << TD |
| 290 | << TemplateArgString << TemplateIDRange; |
| 291 | DiagnoseUnsatisfiedConstraint(Satisfaction); |
| 292 | return true; |
| 293 | } |
| 294 | return false; |
| 295 | } |
| 296 | |
| 297 | static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S, |
| 298 | Expr *SubstExpr, |
| 299 | bool First = true) { |
| 300 | SubstExpr = SubstExpr->IgnoreParenImpCasts(); |
| 301 | if (BinaryOperator *BO = dyn_cast<BinaryOperator>(SubstExpr)) { |
| 302 | switch (BO->getOpcode()) { |
| 303 | // These two cases will in practice only be reached when using fold |
| 304 | // expressions with || and &&, since otherwise the || and && will have been |
| 305 | // broken down into atomic constraints during satisfaction checking. |
| 306 | case BO_LOr: |
| 307 | // Or evaluated to false - meaning both RHS and LHS evaluated to false. |
| 308 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First); |
| 309 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), |
| 310 | /*First=*/false); |
| 311 | return; |
| 312 | case BO_LAnd: |
| 313 | bool LHSSatisfied; |
| 314 | BO->getLHS()->EvaluateAsBooleanCondition(LHSSatisfied, S.Context); |
| 315 | if (LHSSatisfied) { |
| 316 | // LHS is true, so RHS must be false. |
| 317 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), First); |
| 318 | return; |
| 319 | } |
| 320 | // LHS is false |
| 321 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getLHS(), First); |
| 322 | |
| 323 | // RHS might also be false |
| 324 | bool RHSSatisfied; |
| 325 | BO->getRHS()->EvaluateAsBooleanCondition(RHSSatisfied, S.Context); |
| 326 | if (!RHSSatisfied) |
| 327 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, BO->getRHS(), |
| 328 | /*First=*/false); |
| 329 | return; |
| 330 | case BO_GE: |
| 331 | case BO_LE: |
| 332 | case BO_GT: |
| 333 | case BO_LT: |
| 334 | case BO_EQ: |
| 335 | case BO_NE: |
| 336 | if (BO->getLHS()->getType()->isIntegerType() && |
| 337 | BO->getRHS()->getType()->isIntegerType()) { |
| 338 | Expr::EvalResult SimplifiedLHS; |
| 339 | Expr::EvalResult SimplifiedRHS; |
| 340 | BO->getLHS()->EvaluateAsInt(SimplifiedLHS, S.Context); |
| 341 | BO->getRHS()->EvaluateAsInt(SimplifiedRHS, S.Context); |
| 342 | if (!SimplifiedLHS.Diag && ! SimplifiedRHS.Diag) { |
| 343 | S.Diag(SubstExpr->getBeginLoc(), |
| 344 | diag::note_atomic_constraint_evaluated_to_false_elaborated) |
| 345 | << (int)First << SubstExpr |
| 346 | << SimplifiedLHS.Val.getInt().toString(10) |
| 347 | << BinaryOperator::getOpcodeStr(BO->getOpcode()) |
| 348 | << SimplifiedRHS.Val.getInt().toString(10); |
| 349 | return; |
| 350 | } |
| 351 | } |
| 352 | break; |
| 353 | |
| 354 | default: |
| 355 | break; |
| 356 | } |
| 357 | } else if (auto *CSE = dyn_cast<ConceptSpecializationExpr>(SubstExpr)) { |
| 358 | if (CSE->getTemplateArgsAsWritten()->NumTemplateArgs == 1) { |
| 359 | S.Diag( |
| 360 | CSE->getSourceRange().getBegin(), |
| 361 | diag:: |
| 362 | note_single_arg_concept_specialization_constraint_evaluated_to_false) |
| 363 | << (int)First |
| 364 | << CSE->getTemplateArgsAsWritten()->arguments()[0].getArgument() |
| 365 | << CSE->getNamedConcept(); |
| 366 | } else { |
| 367 | S.Diag(SubstExpr->getSourceRange().getBegin(), |
| 368 | diag::note_concept_specialization_constraint_evaluated_to_false) |
| 369 | << (int)First << CSE; |
| 370 | } |
| 371 | S.DiagnoseUnsatisfiedConstraint(CSE->getSatisfaction()); |
| 372 | return; |
| 373 | } |
| 374 | |
| 375 | S.Diag(SubstExpr->getSourceRange().getBegin(), |
| 376 | diag::note_atomic_constraint_evaluated_to_false) |
| 377 | << (int)First << SubstExpr; |
| 378 | } |
| 379 | |
| 380 | template<typename SubstitutionDiagnostic> |
| 381 | static void diagnoseUnsatisfiedConstraintExpr( |
| 382 | Sema &S, const Expr *E, |
| 383 | const llvm::PointerUnion<Expr *, SubstitutionDiagnostic *> &Record, |
| 384 | bool First = true) { |
| 385 | if (auto *Diag = Record.template dyn_cast<SubstitutionDiagnostic *>()){ |
| 386 | S.Diag(Diag->first, diag::note_substituted_constraint_expr_is_ill_formed) |
| 387 | << Diag->second; |
| 388 | return; |
| 389 | } |
| 390 | |
| 391 | diagnoseWellFormedUnsatisfiedConstraintExpr(S, |
| 392 | Record.template get<Expr *>(), First); |
| 393 | } |
| 394 | |
| 395 | void Sema::DiagnoseUnsatisfiedConstraint( |
| 396 | const ConstraintSatisfaction& Satisfaction) { |
| 397 | assert(!Satisfaction.IsSatisfied && |
| 398 | "Attempted to diagnose a satisfied constraint"); |
| 399 | bool First = true; |
| 400 | for (auto &Pair : Satisfaction.Details) { |
| 401 | diagnoseUnsatisfiedConstraintExpr(*this, Pair.first, Pair.second, First); |
| 402 | First = false; |
| 403 | } |
| 404 | } |
| 405 | |
| 406 | void Sema::DiagnoseUnsatisfiedConstraint( |
| 407 | const ASTConstraintSatisfaction &Satisfaction) { |
| 408 | assert(!Satisfaction.IsSatisfied && |
| 409 | "Attempted to diagnose a satisfied constraint"); |
| 410 | bool First = true; |
| 411 | for (auto &Pair : Satisfaction) { |
| 412 | diagnoseUnsatisfiedConstraintExpr(*this, Pair.first, Pair.second, First); |
| 413 | First = false; |
| 414 | } |
Saar Raz | 0330fba | 2019-10-15 18:44:06 +0000 | [diff] [blame] | 415 | } |