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

When are interface requirements enforced for an impl? #4579

Open
josh11b opened this issue Nov 22, 2024 · 5 comments
Open

When are interface requirements enforced for an impl? #4579

josh11b opened this issue Nov 22, 2024 · 5 comments
Labels
leads question A question for the leads team

Comments

@josh11b
Copy link
Contributor

josh11b commented Nov 22, 2024

Summary of issue:

Given an interface with a requirement, for example:

interface I {}
interface J {
  require Self impl I;
}

When do we need to see an implementation of I before allowing an implementation of J? Does it matter if the implementation of I is complete? Is the answer different for a impl declaration of J compared to a definition of J?

Some possible answers:

  • I needs to be implemented at some point in the same file, possibly after J.
  • [4 different possible rules] An implementation of I needs to be {declared or defined} before a {declaration or definition} of an implementation of J.

Details:

For example, we might say an implementation of I needs to be declared before the implementation of J is defined, as in:

// ✅ Forward declarations allowed
impl C as J;

// ❌ Definition of `J` before declaration of `I`
impl C as J { }

// ✅ Forward declarations of `I` enough to define `J`.
impl D as I;
impl D as J { }

Or we could have a more restrictive rule like I needs to be defined before an implementation of J can be declared: as in:

// ❌ Forward declaration of `J` without implementation of `I` being defined
impl C as J;

impl D as I;
//  ❌ Forward declarations of `I` not enough to declare `J`.
impl D as J;

// ✅ Definition of `I` allows declaration of `J`
impl E as I { }
impl E as J;
@josh11b josh11b added the leads question A question for the leads team label Nov 22, 2024
@josh11b
Copy link
Contributor Author

josh11b commented Nov 22, 2024

I personally think:

  • The least restrictive rule ("I needs to be implemented at some point in the same file, possibly after J") is too permissive. It will create work in the compiler implementation, and isn't philosophically aligned with Carbon's information accumulation principle.
  • I think the most restrictive rules that require a definition of I are too restrictive. I think that would be a burden on users, making the order that things may be legally declared into a puzzle, which may be difficult or impossible to solve when there are other dependencies or constraints.
  • That leaves two possible rules: I needs to be declared (though not necessarily defined) as implemented before J is {declared or defined}. Of these two possibilities, I prefer the less restrictive (before J is defined), but I think either is viable.

@chandlerc
Copy link
Contributor

Agree with the reason for excluding the least and most restrictive options.

Agree with declared before defined. It seems nice that the two otherwise independent forward declarations can be independently ordered. And it seems nice that we shouldn't have completely arbitrary restrictions -- they should be motivated in some way. Either better code, clearer code, alignment with some principle, or simplifying the implementation. Until we have such a motivation, we leave off the added restriction. It also seems cheap to revisit this, a generally low-stakes decision here.

@josh11b
Copy link
Contributor Author

josh11b commented Nov 25, 2024

Looking at the implications of allowing a forward declaration that a type implements an interface without first establishing that the type implements it requirements, it raises the question of whether that forward declaration of the requiring interface allows the compiler to assume its requirement will be implemented?

Example:

interface I {
  fn F();
}
interface R {
  // 1
  require Self impls I;
}
class C {}

fn G[T:! I](x: T) { x.F(); }

// 2: Is this allowed?
// Requirement (1) has not been established,
// but this is only a forward declaration.
impl C as R;

fn H(c: C) {
  // 3: Is this allowed? In particular, do (1) & (2)
  // establish `C impls I`?
  G(c);
}

interface E {
  // 4
  extend I;
}

class D {}
// 5: Should be allowed, since we can write
// `impl D as E { ... }` without `impl D as I`.
impl D as E;

fn J(d: D) {
  // 6: Is this allowed? In particular, do (4) & (5)
  // establish `D impls I`?
  G(d);
}

@josh11b
Copy link
Contributor Author

josh11b commented Nov 25, 2024

@zygoloid Points out that if H were instead a generic function:

fn H2[U:! R](x: U) {
  G(x);
}

we would allow H2 to call G and with (2) we could call H2 with C. So this says that if we allow (2), we should allow (3).

We have two choices (Note: we already require interfaces to be complete before an impl declaration for other reasons):

  • The compiler looks at the interfaces in the impl declaration and rejects if any its Self impls interface requirements are not satisfied.
  • The compiler looks at the interfaces in the impl declaration and assumes all of its Self impls interface requirements are satisfied.

The latter of these two options would allow the impl declarations to be in any order. Which is nice, but has this surprising consequence that the compiler would assume that the required interface I will be implemented, but without knowing the values of any associated constants. Furthermore, without a forward declaration of I, it won't accept a definition for R.

@chandlerc
Copy link
Contributor

The latter of these two options would allow the impl declarations to be in any order. Which is nice, but has this surprising consequence that the compiler would assume that the required interface I will be implemented, but without knowing the values of any associated constants. Furthermore, without a forward declaration of I, it won't accept a definition for R.

FWIW, this doesn't seem terribly surprising to me... Maybe I'm missing some bad implication?

I don't feel strongly between these two options though if folks are concerned about the latter option, happy to go with the former.

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