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

Implementing multiple interfaces with a single impl definition #4566

Open
josh11b opened this issue Nov 21, 2024 · 1 comment
Open

Implementing multiple interfaces with a single impl definition #4566

josh11b opened this issue Nov 21, 2024 · 1 comment
Labels
leads question A question for the leads team

Comments

@josh11b
Copy link
Contributor

josh11b commented Nov 21, 2024

Summary of issue:

An impl specifies how a type satisfies a facet type, which may include multiple interfaces due to using extend or &. Question: what happens when such an impl is parameterized, and the implementation of a subset of the interfaces is superseded by a more specialized impl?

There are three broad approaches that have been considered in discussion:

  • "All or nothing": An impl is either used entirely or not at all. If there is a specialization for any overlap that is used instead, no other part of the multiple-interface impl is used. I don't think this option works as well as other options under consideration. It creates a lot of uncertainty about when an implementation is applicable, particularly in generic code where it can't be determined if any specializations are applicable. This approach runs into many stumbling blocks since constraints in generic code are not sensitive to specialization questions.
  • "Independent impls": An impl of multiple interfaces is treated as a collection of impls of the individual interfaces. In particular, the definition of a member of one interface can assume that the other interfaces are implemented, but not that the associated types (or other non-function associated constants) have expected values. This is the approach that has currently been assumed.
  • "Constrained impls": An impl of multiple interfaces is treated as a collection of impls of the individual interfaces with the additional constraint that no specialization changes the values of any non-function associated constants of any of the interfaces. This is the approach I suggest we adopt.

Details:

Consider these interface definitions:

interface I {
  let T:! type;
}
interface K {
  fn G();
}
interface L {
  let X:! type;
  fn H() -> X;
}
interface J {
  extend I;
  extend K;
  // `T` here resolves to `Self.(I.T)`.
  extend L where .X = T;
  fn F() -> T;
}

Imagine we have a blanket implementation of J, and a specialization of I:

// Implementing `J` also implements `I`, `K` and `L`.
impl forall [U:! type] U as J where .T = i32 {
  // 1: `F` is a member of `J`, with return type `I.T`.
  fn F() -> i32 { return 0; }
  // 2: `G` is a member of `K`.
  fn G() {}
  // 3: `H` is a member of `L`, with return type
  // `L.X` constrained to equal `I.T`.
  fn H() -> i32 { return F(); }
}

// 4: Specialization that changes `I.T` from `i32`.
// to `(i32, i32)` for `bool`.
impl bool as I where .T = (i32, i32) {}
// 5: Specialization that doesn't change `I.T` from the blanket impl.
impl f64 as I where .T = i32 {}
impl f64 as K { fn G() { Core.Print("Hello World!"); } }

The questions are then: Is this code valid? If not, what part is diagnosed as invalid? If it is, are the interfaces J, K, and L implemented for bool? If so, what signatures do J.F and L.H have? Is the situation any different for f64?

We don't want to be in a situation where generic code thinks the blanket impl applies but has the wrong idea about the return type of F() or H() since it

  • The "all or nothing" approach says this code is valid, and J, K, and L are not implemented for bool and also not implemented for f64, due to the specializations 4 & 5 blocking the blanket impl from applying.
  • The "independent impls" approach says this code is invalid. The implementations of J.F and L.H in the blanket impl can't assume that I.T is i32 since that is an associated constant from another interface, so the functions 1 & 3 fail type checking. There isn't a problem, though, with the implementation (2) of K or K.G.
  • The "constrained impls" approach says this code is valid. J, K, and L are not implemented for bool, since the blanket implementation has a constraint that says it only applies when .T is i32 (like it says right there in the declaration). They are implemented for f64, since the specialized implementation (5) of I for f64 has .T equal to i32. When type checking the blanket implementation, declarations (1) and (3) can assume I.T and L.X are i32, and that I, J, K, and L are all implemented, but nothing about the body of any of the associated functions from other interfaces (which are generally treated as opaque anyway, unless those functions are executed at compile time).
@josh11b josh11b added the leads question A question for the leads team label Nov 21, 2024
@chandlerc
Copy link
Contributor

Agreement on the constrained impls approach. It seems really important to have access to the associated types with certainty, especially in generic code using the impl as you mention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
leads question A question for the leads team
Projects
None yet
Development

No branches or pull requests

2 participants