Skip to content

Revert "[Clang][C++26] Implement "Ordering of constraints involving fold expressions" #99007

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 1 commit into from
Jul 16, 2024

Conversation

cor3ntin
Copy link
Contributor

Reverts #98160

Breaks CI on some architectures

@cor3ntin cor3ntin requested a review from Endilll as a code owner July 16, 2024 09:00
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 16, 2024
@cor3ntin cor3ntin merged commit 762a478 into main Jul 16, 2024
6 of 9 checks passed
@cor3ntin cor3ntin deleted the revert-98160-corentin/fold_expressions_main branch July 16, 2024 09:00
@llvmbot
Copy link
Member

llvmbot commented Jul 16, 2024

@llvm/pr-subscribers-clang

Author: cor3ntin (cor3ntin)

Changes

Reverts llvm/llvm-project#98160

Breaks CI on some architectures


Patch is 53.87 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/99007.diff

7 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (-3)
  • (modified) clang/include/clang/Sema/Sema.h (-5)
  • (modified) clang/include/clang/Sema/SemaConcept.h (+22-149)
  • (modified) clang/lib/Sema/SemaConcept.cpp (+200-403)
  • (modified) clang/lib/Sema/SemaTemplateVariadic.cpp (-4)
  • (removed) clang/test/SemaCXX/cxx2c-fold-exprs.cpp (-277)
  • (modified) clang/www/cxx_status.html (+1-1)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cb35825b71e3e..969856a8f978c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -278,9 +278,6 @@ C++2c Feature Support
 
 - Implemented `P3144R2 Deleting a Pointer to an Incomplete Type Should be Ill-formed <https://wg21.link/P3144R2>`_.
 
-- Implemented `P2963R3 Ordering of constraints involving fold expressions <https://wg21.link/P2963R3>`_.
-
-
 Resolutions to C++ Defect Reports
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 - Substitute template parameter pack, when it is not explicitly specified
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3cb1aa935fe46..48dff1b76cc57 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14078,11 +14078,6 @@ class Sema final : public SemaBase {
       const DeclarationNameInfo &NameInfo,
       SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
 
-  /// Collect the set of unexpanded parameter packs within the given
-  /// expression.
-  static void collectUnexpandedParameterPacks(
-      Expr *E, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
-
   /// Invoked when parsing a template argument followed by an
   /// ellipsis, which creates a pack expansion.
   ///
diff --git a/clang/include/clang/Sema/SemaConcept.h b/clang/include/clang/Sema/SemaConcept.h
index 8fb7dd6838e57..711443505174f 100644
--- a/clang/include/clang/Sema/SemaConcept.h
+++ b/clang/include/clang/Sema/SemaConcept.h
@@ -75,26 +75,6 @@ struct AtomicConstraint {
   }
 };
 
-struct FoldExpandedConstraint;
-
-using NormalFormConstraint =
-    llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *>;
-struct NormalizedConstraint;
-using NormalForm =
-    llvm::SmallVector<llvm::SmallVector<NormalFormConstraint, 2>, 4>;
-
-// A constraint is in conjunctive normal form when it is a conjunction of
-// clauses where each clause is a disjunction of atomic constraints. For atomic
-// constraints A, B, and C, the constraint A  ∧ (B  ∨ C) is in conjunctive
-// normal form.
-NormalForm makeCNF(const NormalizedConstraint &Normalized);
-
-// A constraint is in disjunctive normal form when it is a disjunction of
-// clauses where each clause is a conjunction of atomic constraints. For atomic
-// constraints A, B, and C, the disjunctive normal form of the constraint A
-//  ∧ (B  ∨ C) is (A  ∧ B)  ∨ (A  ∧ C).
-NormalForm makeDNF(const NormalizedConstraint &Normalized);
-
 /// \brief A normalized constraint, as defined in C++ [temp.constr.normal], is
 /// either an atomic constraint, a conjunction of normalized constraints or a
 /// disjunction of normalized constraints.
@@ -107,17 +87,26 @@ struct NormalizedConstraint {
       std::pair<NormalizedConstraint, NormalizedConstraint> *, 1,
       CompoundConstraintKind>;
 
-  llvm::PointerUnion<AtomicConstraint *, FoldExpandedConstraint *,
-                     CompoundConstraint>
-      Constraint;
+  llvm::PointerUnion<AtomicConstraint *, CompoundConstraint> Constraint;
 
   NormalizedConstraint(AtomicConstraint *C): Constraint{C} { };
-  NormalizedConstraint(FoldExpandedConstraint *C) : Constraint{C} {};
-
   NormalizedConstraint(ASTContext &C, NormalizedConstraint LHS,
-                       NormalizedConstraint RHS, CompoundConstraintKind Kind);
-
-  NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other);
+                       NormalizedConstraint RHS, CompoundConstraintKind Kind)
+      : Constraint{CompoundConstraint{
+            new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+                std::move(LHS), std::move(RHS)}, Kind}} { };
+
+  NormalizedConstraint(ASTContext &C, const NormalizedConstraint &Other) {
+    if (Other.isAtomic()) {
+      Constraint = new (C) AtomicConstraint(*Other.getAtomicConstraint());
+    } else {
+      Constraint = CompoundConstraint(
+          new (C) std::pair<NormalizedConstraint, NormalizedConstraint>{
+              NormalizedConstraint(C, Other.getLHS()),
+              NormalizedConstraint(C, Other.getRHS())},
+              Other.getCompoundKind());
+    }
+  }
   NormalizedConstraint(NormalizedConstraint &&Other):
       Constraint(Other.Constraint) {
     Other.Constraint = nullptr;
@@ -131,24 +120,20 @@ struct NormalizedConstraint {
     return *this;
   }
 
-  bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
-  bool isFoldExpanded() const {
-    return Constraint.is<FoldExpandedConstraint *>();
-  }
-  bool isCompound() const { return Constraint.is<CompoundConstraint>(); }
-
   CompoundConstraintKind getCompoundKind() const {
-    assert(isCompound() && "getCompoundKind on a non-compound constraint..");
+    assert(!isAtomic() && "getCompoundKind called on atomic constraint.");
     return Constraint.get<CompoundConstraint>().getInt();
   }
 
+  bool isAtomic() const { return Constraint.is<AtomicConstraint *>(); }
+
   NormalizedConstraint &getLHS() const {
-    assert(isCompound() && "getLHS called on a non-compound constraint.");
+    assert(!isAtomic() && "getLHS called on atomic constraint.");
     return Constraint.get<CompoundConstraint>().getPointer()->first;
   }
 
   NormalizedConstraint &getRHS() const {
-    assert(isCompound() && "getRHS called on a non-compound constraint.");
+    assert(!isAtomic() && "getRHS called on atomic constraint.");
     return Constraint.get<CompoundConstraint>().getPointer()->second;
   }
 
@@ -158,12 +143,6 @@ struct NormalizedConstraint {
     return Constraint.get<AtomicConstraint *>();
   }
 
-  FoldExpandedConstraint *getFoldExpandedConstraint() const {
-    assert(isFoldExpanded() &&
-           "getFoldExpandedConstraint called on non-fold-expanded constraint.");
-    return Constraint.get<FoldExpandedConstraint *>();
-  }
-
 private:
   static std::optional<NormalizedConstraint>
   fromConstraintExprs(Sema &S, NamedDecl *D, ArrayRef<const Expr *> E);
@@ -171,112 +150,6 @@ struct NormalizedConstraint {
   fromConstraintExpr(Sema &S, NamedDecl *D, const Expr *E);
 };
 
-struct FoldExpandedConstraint {
-  enum class FoldOperatorKind { And, Or } Kind;
-  NormalizedConstraint Constraint;
-  const Expr *Pattern;
-
-  FoldExpandedConstraint(FoldOperatorKind K, NormalizedConstraint C,
-                         const Expr *Pattern)
-      : Kind(K), Constraint(std::move(C)), Pattern(Pattern) {};
-
-  template <typename AtomicSubsumptionEvaluator>
-  bool subsumes(const FoldExpandedConstraint &Other,
-                const AtomicSubsumptionEvaluator &E) const;
-
-  static bool AreCompatibleForSubsumption(const FoldExpandedConstraint &A,
-                                          const FoldExpandedConstraint &B);
-};
-
-const NormalizedConstraint *getNormalizedAssociatedConstraints(
-    Sema &S, NamedDecl *ConstrainedDecl,
-    ArrayRef<const Expr *> AssociatedConstraints);
-
-template <typename AtomicSubsumptionEvaluator>
-bool subsumes(const NormalForm &PDNF, const NormalForm &QCNF,
-              const AtomicSubsumptionEvaluator &E) {
-  // C++ [temp.constr.order] p2
-  //   Then, P subsumes Q if and only if, for every disjunctive clause Pi in the
-  //   disjunctive normal form of P, Pi subsumes every conjunctive clause Qj in
-  //   the conjuctive normal form of Q, where [...]
-  for (const auto &Pi : PDNF) {
-    for (const auto &Qj : QCNF) {
-      // C++ [temp.constr.order] p2
-      //   - [...] a disjunctive clause Pi subsumes a conjunctive clause Qj if
-      //     and only if there exists an atomic constraint Pia in Pi for which
-      //     there exists an atomic constraint, Qjb, in Qj such that Pia
-      //     subsumes Qjb.
-      bool Found = false;
-      for (NormalFormConstraint Pia : Pi) {
-        for (NormalFormConstraint Qjb : Qj) {
-          if (Pia.is<FoldExpandedConstraint *>() &&
-              Qjb.is<FoldExpandedConstraint *>()) {
-            if (Pia.get<FoldExpandedConstraint *>()->subsumes(
-                    *Qjb.get<FoldExpandedConstraint *>(), E)) {
-              Found = true;
-              break;
-            }
-          } else if (Pia.is<AtomicConstraint *>() &&
-                     Qjb.is<AtomicConstraint *>()) {
-            if (E(*Pia.get<AtomicConstraint *>(),
-                  *Qjb.get<AtomicConstraint *>())) {
-              Found = true;
-              break;
-            }
-          }
-        }
-        if (Found)
-          break;
-      }
-      if (!Found)
-        return false;
-    }
-  }
-  return true;
-}
-
-template <typename AtomicSubsumptionEvaluator>
-bool subsumes(Sema &S, NamedDecl *DP, ArrayRef<const Expr *> P, NamedDecl *DQ,
-              ArrayRef<const Expr *> Q, bool &Subsumes,
-              const AtomicSubsumptionEvaluator &E) {
-  // C++ [temp.constr.order] p2
-  //   In order to determine if a constraint P subsumes a constraint Q, P is
-  //   transformed into disjunctive normal form, and Q is transformed into
-  //   conjunctive normal form. [...]
-  const NormalizedConstraint *PNormalized =
-      getNormalizedAssociatedConstraints(S, DP, P);
-  if (!PNormalized)
-    return true;
-  NormalForm PDNF = makeDNF(*PNormalized);
-
-  const NormalizedConstraint *QNormalized =
-      getNormalizedAssociatedConstraints(S, DQ, Q);
-  if (!QNormalized)
-    return true;
-  NormalForm QCNF = makeCNF(*QNormalized);
-
-  Subsumes = subsumes(PDNF, QCNF, E);
-  return false;
-}
-
-template <typename AtomicSubsumptionEvaluator>
-bool FoldExpandedConstraint::subsumes(
-    const FoldExpandedConstraint &Other,
-    const AtomicSubsumptionEvaluator &E) const {
-
-  // [C++26] [temp.constr.order]
-  // a fold expanded constraint A subsumes another fold expanded constraint B if
-  // they are compatible for subsumption, have the same fold-operator, and the
-  // constraint of A subsumes that of B
-
-  if (Kind != Other.Kind || !AreCompatibleForSubsumption(*this, Other))
-    return false;
-
-  NormalForm PDNF = makeDNF(this->Constraint);
-  NormalForm QCNF = makeCNF(Other.Constraint);
-  return clang::subsumes(PDNF, QCNF, E);
-}
-
 } // clang
 
 #endif // LLVM_CLANG_SEMA_SEMACONCEPT_H
diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 70562a327dcaa..54891150da20f 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -65,7 +65,6 @@ class LogicalBinOp {
 
   const Expr *getLHS() const { return LHS; }
   const Expr *getRHS() const { return RHS; }
-  OverloadedOperatorKind getOp() const { return Op; }
 
   ExprResult recreateBinOp(Sema &SemaRef, ExprResult LHS) const {
     return recreateBinOp(SemaRef, LHS, const_cast<Expr *>(getRHS()));
@@ -178,177 +177,77 @@ struct SatisfactionStackRAII {
 };
 } // namespace
 
-template <typename ConstraintEvaluator>
+template <typename AtomicEvaluator>
 static ExprResult
 calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
                                 ConstraintSatisfaction &Satisfaction,
-                                const ConstraintEvaluator &Evaluator);
-
-template <typename ConstraintEvaluator>
-static ExprResult
-calculateConstraintSatisfaction(Sema &S, const Expr *LHS,
-                                OverloadedOperatorKind Op, const Expr *RHS,
-                                ConstraintSatisfaction &Satisfaction,
-                                const ConstraintEvaluator &Evaluator) {
-  size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
-
-  ExprResult LHSRes =
-      calculateConstraintSatisfaction(S, LHS, Satisfaction, Evaluator);
+                                AtomicEvaluator &&Evaluator) {
+  ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
 
-  if (LHSRes.isInvalid())
-    return ExprError();
+  if (LogicalBinOp BO = ConstraintExpr) {
+    size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
+    ExprResult LHSRes = calculateConstraintSatisfaction(
+        S, BO.getLHS(), Satisfaction, Evaluator);
 
-  bool IsLHSSatisfied = Satisfaction.IsSatisfied;
-
-  if (Op == clang::OO_PipePipe && IsLHSSatisfied)
-    // [temp.constr.op] p3
-    //    A disjunction is a constraint taking two operands. To determine if
-    //    a disjunction is satisfied, the satisfaction of the first operand
-    //    is checked. If that is satisfied, the disjunction is satisfied.
-    //    Otherwise, the disjunction is satisfied if and only if the second
-    //    operand is satisfied.
-    // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
-    return LHSRes;
-
-  if (Op == clang::OO_AmpAmp && !IsLHSSatisfied)
-    // [temp.constr.op] p2
-    //    A conjunction is a constraint taking two operands. To determine if
-    //    a conjunction is satisfied, the satisfaction of the first operand
-    //    is checked. If that is not satisfied, the conjunction is not
-    //    satisfied. Otherwise, the conjunction is satisfied if and only if
-    //    the second operand is satisfied.
-    // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
-    return LHSRes;
-
-  ExprResult RHSRes =
-      calculateConstraintSatisfaction(S, RHS, Satisfaction, Evaluator);
-  if (RHSRes.isInvalid())
-    return ExprError();
-
-  bool IsRHSSatisfied = Satisfaction.IsSatisfied;
-  // Current implementation adds diagnostic information about the falsity
-  // of each false atomic constraint expression when it evaluates them.
-  // When the evaluation results to `false || true`, the information
-  // generated during the evaluation of left-hand side is meaningless
-  // because the whole expression evaluates to true.
-  // The following code removes the irrelevant diagnostic information.
-  // FIXME: We should probably delay the addition of diagnostic information
-  // until we know the entire expression is false.
-  if (Op == clang::OO_PipePipe && IsRHSSatisfied) {
-    auto EffectiveDetailEnd = Satisfaction.Details.begin();
-    std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
-    Satisfaction.Details.erase(EffectiveDetailEnd, Satisfaction.Details.end());
-  }
+    if (LHSRes.isInvalid())
+      return ExprError();
 
-  if (!LHSRes.isUsable() || !RHSRes.isUsable())
-    return ExprEmpty();
+    bool IsLHSSatisfied = Satisfaction.IsSatisfied;
 
-  return BinaryOperator::Create(S.Context, LHSRes.get(), RHSRes.get(),
-                                BinaryOperator::getOverloadedOpcode(Op),
-                                S.Context.BoolTy, VK_PRValue, OK_Ordinary,
-                                LHS->getBeginLoc(), FPOptionsOverride{});
-}
+    if (BO.isOr() && IsLHSSatisfied)
+      // [temp.constr.op] p3
+      //    A disjunction is a constraint taking two operands. To determine if
+      //    a disjunction is satisfied, the satisfaction of the first operand
+      //    is checked. If that is satisfied, the disjunction is satisfied.
+      //    Otherwise, the disjunction is satisfied if and only if the second
+      //    operand is satisfied.
+      // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+      return LHSRes;
 
-template <typename ConstraintEvaluator>
-static ExprResult
-calculateConstraintSatisfaction(Sema &S, const CXXFoldExpr *FE,
-                                ConstraintSatisfaction &Satisfaction,
-                                const ConstraintEvaluator &Evaluator) {
-  bool Conjunction = FE->getOperator() == BinaryOperatorKind::BO_LAnd;
-  size_t EffectiveDetailEndIndex = Satisfaction.Details.size();
-
-  ExprResult Out;
-  if (FE->isLeftFold() && FE->getInit()) {
-    Out = calculateConstraintSatisfaction(S, FE->getInit(), Satisfaction,
-                                          Evaluator);
-    if (Out.isInvalid())
+    if (BO.isAnd() && !IsLHSSatisfied)
+      // [temp.constr.op] p2
+      //    A conjunction is a constraint taking two operands. To determine if
+      //    a conjunction is satisfied, the satisfaction of the first operand
+      //    is checked. If that is not satisfied, the conjunction is not
+      //    satisfied. Otherwise, the conjunction is satisfied if and only if
+      //    the second operand is satisfied.
+      // LHS is instantiated while RHS is not. Skip creating invalid BinaryOp.
+      return LHSRes;
+
+    ExprResult RHSRes = calculateConstraintSatisfaction(
+        S, BO.getRHS(), Satisfaction, std::forward<AtomicEvaluator>(Evaluator));
+    if (RHSRes.isInvalid())
       return ExprError();
 
-    // If the first clause of a conjunction is not satisfied,
-    // or if the first clause of a disjection is satisfied,
-    // we have established satisfaction of the whole constraint
-    // and we should not continue further.
-    if (Conjunction != Satisfaction.IsSatisfied)
-      return Out;
-  }
-  std::optional<unsigned> NumExpansions =
-      Evaluator.EvaluateFoldExpandedConstraintSize(FE);
-  if (!NumExpansions)
-    return ExprError();
-  for (unsigned I = 0; I < *NumExpansions; I++) {
-    Sema::ArgumentPackSubstitutionIndexRAII SubstIndex(S, I);
-    ExprResult Res = calculateConstraintSatisfaction(S, FE->getPattern(),
-                                                     Satisfaction, Evaluator);
-    if (Res.isInvalid())
-      return ExprError();
     bool IsRHSSatisfied = Satisfaction.IsSatisfied;
-    if (!Conjunction && IsRHSSatisfied) {
+    // Current implementation adds diagnostic information about the falsity
+    // of each false atomic constraint expression when it evaluates them.
+    // When the evaluation results to `false || true`, the information
+    // generated during the evaluation of left-hand side is meaningless
+    // because the whole expression evaluates to true.
+    // The following code removes the irrelevant diagnostic information.
+    // FIXME: We should probably delay the addition of diagnostic information
+    // until we know the entire expression is false.
+    if (BO.isOr() && IsRHSSatisfied) {
       auto EffectiveDetailEnd = Satisfaction.Details.begin();
       std::advance(EffectiveDetailEnd, EffectiveDetailEndIndex);
       Satisfaction.Details.erase(EffectiveDetailEnd,
                                  Satisfaction.Details.end());
     }
-    if (Out.isUnset())
-      Out = Res;
-    else if (!Res.isUnset()) {
-      Out = BinaryOperator::Create(
-          S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
-          VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
-    }
-    if (Conjunction != IsRHSSatisfied)
-      return Out;
-  }
-
-  if (FE->isRightFold() && FE->getInit()) {
-    ExprResult Res = calculateConstraintSatisfaction(S, FE->getInit(),
-                                                     Satisfaction, Evaluator);
-    if (Out.isInvalid())
-      return ExprError();
-
-    if (Out.isUnset())
-      Out = Res;
-    else if (!Res.isUnset()) {
-      Out = BinaryOperator::Create(
-          S.Context, Out.get(), Res.get(), FE->getOperator(), S.Context.BoolTy,
-          VK_PRValue, OK_Ordinary, FE->getBeginLoc(), FPOptionsOverride{});
-    }
-  }
 
-  if (Out.isUnset()) {
-    Satisfaction.IsSatisfied = Conjunction;
-    Out = S.BuildEmptyCXXFoldExpr(FE->getBeginLoc(), FE->getOperator());
+    return BO.recreateBinOp(S, LHSRes, RHSRes);
   }
-  return Out;
-}
-
-template <typename ConstraintEvaluator>
-static ExprResult
-calculateConstraintSatisfaction(Sema &S, const Expr *ConstraintExpr,
-                                ConstraintSatisfaction &Satisfaction,
-                                const ConstraintEvaluator &Evaluator) {
-  ConstraintExpr = ConstraintExpr->IgnoreParenImpCasts();
-
-  if (LogicalBinOp BO = ConstraintExpr)
-    return calculateConstraintSatisfaction(
-        S, BO.getLHS(), BO.getOp(), BO.getRHS(), Satisfaction, Evaluator);
 
   if (auto *C = dyn_cast<ExprWithCleanups>(ConstraintExpr)) {
     // These aren't evaluated, so we don't care about cleanups, so we can just
     // evaluate these as if the cleanups didn't exist.
-    return calculateConstraintSatisfaction(S, C->getSubExpr(), Satisfaction,
-                                           Evaluator);
-  }
-
-  if (auto *FE = dyn_cast<CXXFoldExpr>(ConstraintExpr);
-      FE && S.getLangOpts().CPlusPlus26 &&
-      (FE->getOperator() == BinaryOperatorKind::BO_LAnd ||
-       FE->getOperator() == BinaryOperatorKind::BO_LOr)) {
-    return calculateConstraintSatisfaction(S, FE, Satisfaction, Evaluator);
+    return calculateConstraintSatisfaction(
+        S, C->getSubExpr(), Satisfaction,
+        std::forward<AtomicEvaluator>(Evaluator));
 ...
[truncated]

yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
…old expressions" (#99007)

Summary:
Reverts #98160

Breaks CI on some architectures

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251756
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants