Skip to content
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

Always == not = in where clauses #2070

Closed
wants to merge 9 commits into from
Closed

Conversation

josh11b
Copy link
Contributor

@josh11b josh11b commented Aug 19, 2022

Instead of having two different operators and semantics when constraining an associated type to be equal to another type variable versus a concrete type, unify the semantics and consistently use just the == operator in where clauses.

@josh11b josh11b added proposal A proposal proposal draft Proposal in draft, not ready for review labels Aug 19, 2022
@josh11b josh11b requested a review from a team August 19, 2022 01:51
@josh11b josh11b marked this pull request as ready for review August 19, 2022 04:58
@github-actions github-actions bot added proposal rfc Proposal with request-for-comment sent out and removed proposal draft Proposal in draft, not ready for review labels Aug 19, 2022
@jonmeow
Copy link
Contributor

jonmeow commented Aug 19, 2022

Seems like a good change to me. I might remark = feels like assignment, but where feels more like a condition. It might particularly be worth noting that in examples like let PointT:! NSpacePoint where .N = 2;, let supports assignment following so using == to make it read less like an assignment might improve readability.

@josh11b
Copy link
Contributor Author

josh11b commented Aug 19, 2022

It might particularly be worth noting that in examples like let PointT:! NSpacePoint where .N = 2;, let supports assignment following so using == to make it read less like an assignment might improve readability.

I added this rationale to the alternatives considered: fefcce6

Copy link
Contributor

@zygoloid zygoloid left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Comment on lines 2516 to 2517
Two generic types could be constrained to be equal to each other using `==`,
without specifying what that type is. For example, we could make the
Copy link
Contributor

Choose a reason for hiding this comment

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

Aside: do we have rules on how the types of the expressions on either side of == should correspond? What I implemented in explorer was: there must either be an implicit conversion from LHS to RHS or from RHS to LHS. I suppose we should probably use the same unification rules that if ... then ... else uses?

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 what the question is. There will be lots of situations where you will write T:! C where .A == U.B and there won't be any correspondence between T.A and U.B, including no implicit conversions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Here's an example:

interface C { let A:! Type; }
interface D { let B:! i32; }
fn F[U:! D, T:! C where .A == U.B]();

I would expect that we want to reject this, because the types of T.A and U.B (Type and i32) are unrelated. But if the type of T.A is some type-of-type and the type of U.B is some other type-of-type, I think we want to accept. And similarly if the type of T.A is i32 and the type of U.B is i64, I think we probably want to accept.

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 see. I didn't understand since the wording of this sentence and the title of this section restricts it to type variables, but I agree this document should address that point.

Should we require the compiler to detect cases where the constraints on two equal type variables are incompatible, as in:

interface D { let A:! Type; }
interface E { let B:! D where .A == i32; }
interface F { let C:! D where .A == bool; }
fn F[T:! E, U:! F where .C == T.B](x: T, y: U);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What I implemented in explorer was: there must either be an implicit conversion from LHS to RHS or from RHS to LHS. I suppose we should probably use the same unification rules that if ... then ... else uses?

It seems like we should use the == operator? It does have concerns:

  • Not clear if the user's operator== code can be executed at compile-time.
  • Not clear if the compiler can reason about what arbitrary user == code means.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, I'm concerned that using == isn't sufficient. That would give us a predicate by which we can tell whether the two associated constants are equal, but we also want to be able to use one in the place of the other, which I think we can't do if there's an arbitrary == comparing them. Here's an example of the kind of thing I'm worried about:

class A {
  var a: i32;
  external impl as ImplicitAs(i32) { fn Convert[me: Self]() -> i32 { return 0; } }
}
class B {
  var b: i32;
  external impl as ImplicitAs(i32) { fn Convert[me: Self]() -> i32 { return 1; } }
}
external impl A as EqWith(B) {
  fn Op[me: Self](b: B) -> bool { return me.a == b.b; }
}
class X(n:! i32) {}
interface Q {
  let AV:! A;
  let BV:! B;
  let N:! i32;
}
fn F[QT: Q where .AV == .BV]() {
  var v: X(QT.AV) = {};
  var w: X(QT.BV) = v;
}

We want to know if X(QT.AV) and X(QT.BV) are the same type. We have an equality in scope: where QT.AV == QT.BV. But the fact that the EqWith operation between those values returns true isn't enough for us to conclude that X(QT.AV) is the same as X(QT.BV), because in general == returning true doesn't mean that the values are substitutable for each other.

I don't have a real answer here. It seems like conversions that are not fully value-preserving and round-trippable shouldn't be allowed on the operands of == if we want to allow these kinds of semantic substitutions, but I do want to allow A:! Q where .N == 5, and we will presumably not be able to convert from the type of N to the type of 5, and I want X(A.N) and X(5) to be considered to be the same type in that context, so some amount of difference in type should be OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I use the "there must either be an implicit conversion from LHS to RHS or from RHS to LHS" rule you implemented in explorer?

Copy link
Contributor

@zygoloid zygoloid Sep 3, 2022

Choose a reason for hiding this comment

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

I don't know what rule is best. I think these cases are unproblematic and important:

  • Both sides are types (.MyType == Other.YourType).
  • One side is a constant and it converts to the type of the other side (.N == 5).

Can we say for now that we allow those cases and no others? (With a TODO to revisit this once we know more about what we want here.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated: e1c2e0e

docs/design/generics/details.md Outdated Show resolved Hide resolved

- it is shorter,
- it is arguably more natural when defining an implementation, and
- we found ourselves reaching for `=` more often in examples.
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if it's worth mentioning the case of a where appearing within an impl here. That's one case where I think = rather than == is a bit less surprising, because it does feel a bit more like you're setting the value of an associated constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added edf74bf

@josh11b
Copy link
Contributor Author

josh11b commented Sep 21, 2022

I'm going to withdraw this proposal in favor of #2173 .

@josh11b josh11b closed this Sep 21, 2022
@josh11b josh11b deleted the eq branch September 21, 2022 23:32
@josh11b josh11b added proposal declined Decision made, proposal declined and removed proposal rfc Proposal with request-for-comment sent out labels Sep 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal declined Decision made, proposal declined proposal A proposal
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants