[PATCH] D45680: [C++2a] Add operator<=> Rewriting - Early Attempt

Richard Smith - zygoloid via Phabricator via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 16 09:49:46 PDT 2018


rsmith added inline comments.


================
Comment at: lib/Sema/SemaOverload.cpp:839
+
+unsigned OverloadCandidate::getTrueArgIndex(unsigned Idx) const {
+  if (getRewrittenKind() != ROC_Synthesized)
----------------
I think this might be clearer if named `getParamIndexForArgIndex` or similar.


================
Comment at: lib/Sema/SemaOverload.cpp:8880-8882
+/// \brief Add the rewritten and synthesized candidates for binary comparison
+//    operators. No additional semantic checking is done to see if the candidate
+//    is well formed.
----------------
No `\brief` here (we use autobrief), and please use `///` for continuation lines.


================
Comment at: lib/Sema/SemaOverload.cpp:8888
+                                          bool PerformADL) {
+  assert(getLangOpts().CPlusPlus2a);
+  auto Opc = BinaryOperator::getOverloadedOpcode(Op);
----------------
Does the implementation below actually rely on this?


================
Comment at: lib/Sema/SemaOverload.cpp:8901
+  // Lookup possible candidates for the rewritten operator.
+  // FIXME:  should this really be done in the current scope?
+  LookupResult Operators(*this, OpName, SourceLocation(),
----------------
No, you shouldn't be doing any lookups here. Instead, what you should do is to change the `LookupOverloadedOperatorName` call in `BuildOverloadedBinOp` to look up both `operator@` and `operator<=>` in the case where `@` is an equality or relational op, and then filter `Fns` by operator name both here and when adding the non-member function candidates.

The reason is that we need to do these lookups in the context of the template definition when an equality or relational expression appears within a template, so we need to look up both the original operator and `<=>` eagerly.


================
Comment at: lib/Sema/SemaOverload.cpp:8936-8938
+  // TODO: We should be able to avoid adding synthesized candidates when LHS and
+  // RHS have the same type, since the synthesized candidates for <=> should be
+  // the same as the rewritten ones. Note: It's still possible for the result
----------------
Type isn't sufficient to conclude this; you'll need to check the other relevant properties of LHS and RHS too (at least value kind, but maybe also bit-field-ness and others?).


================
Comment at: lib/Sema/SemaOverload.cpp:9218-9219
+    // --- F2 is a rewritten candidate ([over.match.oper]) and F1 is not.
+    if (Cand2.getRewrittenKind() && !Cand1.getRewrittenKind())
+      return true;
+    if (Cand1.getRewrittenKind() && Cand2.getRewrittenKind() &&
----------------
You also need to check the reverse condition and return false (the "or if not that" is ... somehow ... supposed to imply that).


================
Comment at: lib/Sema/SemaOverload.cpp:12502-12535
+ExprResult RewrittenCandidateOverloadResolver::BuildRewrittenCandidate(
+    const OverloadCandidate &Ovl) {
+  Expr *RewrittenArgs[2] = {Args[0], Args[1]};
+  bool IsSynthesized = Ovl.getRewrittenKind() == ROC_Synthesized;
+  if (IsSynthesized)
+    std::swap(RewrittenArgs[0], RewrittenArgs[1]);
+
----------------
This results in an inadequate representation for the rewritten `<=>` form. We need to retain the "as-written" form of the operator, for multiple reasons (source fidelity for tooling, expression mangling, pretty-printing, ...). Generally, Clang's philosophy is to desugar the minimum amount that's necessary to capture both the syntactic and semantic forms.

A couple of possible representations come to mind here:

1) A specific `Expr` subclass for a rewritten `<=>` operator, which is just a wrapper around a `(a <=> b) op 0` expression, and knows how to extract the subexpressions itself, or...
2) A general "rewritten operator wrapper" `Expr` subclass, which holds its operands and a rewritten expression, and for which the rewritten expression refers to its operands via `OpaqueValueExpr`s. We already have such a class (`PseudoObjectExpr`).

The former is a smaller and simpler representation, but will likely be more work to implement.


================
Comment at: lib/Sema/SemaOverload.cpp:12537-12565
+/// Rewritten candidates have been added but not checked for validity. They
+/// could still be non-viable if:
+///  (A) The rewritten call (x <=> y) is a builtin, but it will be ill-formed
+///      when built (for example it has narrowing conversions).
+///  (B) The expression (x <=> y) @ 0 is ill-formed for the result of (x <=> y).
+///
+/// If either is the case, this function should be considered non-viable and
----------------
Hmm, this is an interesting approach. It's quite a divergence from our usual approach, which is to perform the viability checks first, before partial ordering, even if we could eliminate some candidates based on partial ordering. And it's observable -- it will result in fewer template instantiations being performed, which will mean we do not diagnose some ill-formed (no diagnostic required) situations which would otherwise fail due to an error in a non-immediate context.

Can you say something about why you chose this approach instead of doing the lookup for operator `@` early and marking the candidate non-viable if the lookup / overload resolution for it fails?

(I'm also vaguely wondering whether we should be pushing back on the C++ committee for making viability of the rewritten candidate depend on validity of the `@` expression, not just the `<=>` expression.)


================
Comment at: lib/Sema/SemaOverload.cpp:12570-12572
+  case OR_Deleted:
+    // FIXME(EricWF): If we've found a deleted rewritten operator, it's
+    // possible we should have never considered it a viable candidate.
----------------
This is a really interesting situation. What does it even mean to consider the `(a <=> b) @ c` expression if lookup for `<=>` finds a deleted function? (It would be novel for the return type of a deleted function to be meaningful, and it would be novel to exclude an overload candidate because it's deleted.) And what if lookup for `@` finds a deleted function?

I think this is good reason to go back to CWG and suggest that we don't check the `@` expression at all until after overload resolution.


================
Comment at: lib/Sema/SemaOverload.cpp:12608-12609
+    };
+    // Sort the candidate functions based on their partial ordering,
+    // and find the first N functions which rank equally.
+    std::sort(Overloads.begin(), Overloads.end(), CmpOverloads);
----------------
This is not correct, because it turns out that `isBetterOverloadCandidate` is not a strict weak order. It's not any kind of order at all, actually, since it lacks the transitivity property. I don't know whether you can do better than marking your chosen candidate non-viable and rerunning the candidate selection algorithm if your presumed-best candidate turns out to be non-viable.


================
Comment at: lib/Sema/SemaOverload.cpp:12634
+    }
+    // If none of the rewritten
+    if (NumViableCandidates > 1)
----------------
I am a vampire and


Repository:
  rC Clang

https://reviews.llvm.org/D45680





More information about the cfe-commits mailing list