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

Reject duplicate interface conformances #2935

Closed
SupunS opened this issue Nov 9, 2023 · 15 comments
Closed

Reject duplicate interface conformances #2935

SupunS opened this issue Nov 9, 2023 · 15 comments

Comments

@SupunS
Copy link
Member

SupunS commented Nov 9, 2023

Issue to be solved

Suggestion from @bluesign (please feel free to update, add more info)

If Vault conforms to Receiver
i.e:

resource interface Vault: Receiver {}

And an implementation/or another interface conforms to both Receiver and Vault
i.e:

resource VaultImpl: Vault, Receiver

Then report an error, since Vault already conforms to Receiver, and thus, VaultImpl explicitly conforming to Receiver again makes it a duplicate conformance.
i.e:

resource VaultImpl: Vault, Receiver    // should be an error because `VaultImpl` conforms to `Receiver` twice
                                       // - one implicitly, and one explicitly

Suggested Solution

See above

@bluesign
Copy link
Contributor

bluesign commented Nov 9, 2023

thanks @SupunS , the reason for this suggestion is, if we don't allow resource VaultImpl: Vault, Receiver case, then we can allow in interface conformances to override default implementations.

something like this:

resource interface Receiver {
       fun deposit(from: @FT){
           // original default implementation
       }
}

resource interface Vault: Receiver {
       fun deposit(from: @FT){
           // new default implementation
       }
}

resource VaultImpl: Vault {
    
}

@turbolent
Copy link
Member

Re-stating all interfaces in the type declaration's conformance list that the concrete type conforms to, is currently allowed and not required.

  • This issue / @bluesign is proposing to forbid re-stating interfaces the type conforms to.
  • However, previously there have been suggestions from @joshuahannan and @dete to require the concrete type to re-state all interfaces, especially interfaces the type conforms to implicitly through interface inheritance.

For the latter, the following pros/cons come to mind:

  • Pros: Increases readability. It is immediately visible what interfaces the type conforms to, there is no need to determine the full set manually or through tooling.
  • Cons:
    • Verbosity
    • Friction for downstream code: If an upstream interface conformance list is updated, all downstream concrete types which conform to the type must be updated, too

The sentiment in the team is that the current behaviour is a good middle ground.

If there are strong opinions for changing the behaviour to either forbidding or requiring, we can discuss this further in an upcoming LDM and propose the change through a FLIP.

@joshuahannan
Copy link
Member

I'm still leaning towards requiring it. I don't have an issue with a little more verbosity, and the issue about upstream updates updating and breaking downstream code doesn't seem like a real issue to me, because if upstream code adds new interface conformances, they're probably going to break downstream code anyway because the downstream code will still have to conform to the new interface.

I don't feel super strongly about it though, so if everyone else wants to just keep the current behavior, I can accept that

@bjartek
Copy link
Contributor

bjartek commented Nov 21, 2023

For me what is important here is that I can override a default implementation defined in something i extend. So lets say i want to compose on ViewResolver to change what the default views returned are. Currently i cannot do this, and @bluesign mentioned to me this issue would fix that.

If we do not allow that, anybody that creates interfaces has to do extra work since they cannot have any default implementations in the interface they require.

@joshuahannan
Copy link
Member

@bjartek correct me if I am wrong, but I think that is a separate issue. I think this issue is just about the conformance list, not the default implementations

@bjartek
Copy link
Contributor

bjartek commented Nov 21, 2023

Ok, then I might be confusing things.

If you publish an interface you can always add a new method and thus breaking everything relying on you regardless.

@bluesign
Copy link
Contributor

@joshuahannan this is a requirement for that one actually

Consider:

resource interface Receiver {
       fun deposit(from: @FT){
           // original default implementation
       }
}

resource interface Vault: Receiver {
       fun deposit(from: @FT){
           // new default implementation
       }
}

if I declare my resource as:

resource VaultImpl: Vault, Receiver {
 }

there is a conflict with Vault and Receiver default implementations, it is not easy to decide which one to use, so we reject.

But if we declare it as:

resource VaultImpl: Vault {
 }

Then we can use the Vault implementation safely.

@bjartek
Copy link
Contributor

bjartek commented Nov 22, 2023

So in your example @bluesign it is not possible to say that since Vault is defined before Receiver its implementation will superseed Receivers?

@bluesign
Copy link
Contributor

bluesign commented Nov 22, 2023

So in your example @bluesign it is not possible to say that since Vault is defined before Receiver its implementation will superseed Receivers?

It should be possible but I think order of interfaces makes it little confusing. But totally viable option. I think optimum solution is to look for distance even resource VaultImpl: Vault, Receiver and resource VaultImpl: Receiver, Vault both should get from Vault. ( I think Java does like this http://tpcg.io/_AQVEJS )

@joshuahannan
Copy link
Member

I see the issue now. yeah, if possible, it seems like we could say that since Vault inherits from Receiver, then we can just use the default implementation from Vault. but I could imagine that there are corner cases that might be more complicated than that

@bluesign
Copy link
Contributor

Btw this is not urgent at all, we can always say that we will implement something like java later and select interface defaults by distance. My suggestion was just disallowing now, and maybe later release when we have the complicated implementation in place, I don't have too hard opinion on this.

@j1010001 j1010001 removed this from the Cadence 1.0 - New scope milestone Feb 13, 2024
@j1010001
Copy link
Member

j1010001 commented Mar 5, 2024

potentially braking change for 1.0

@turbolent
Copy link
Member

I guess this change won't go into Cadence 1.0, so we can maybe remove it from #2642, and maybe even close it? @onflow/cadence wdyt?

@bluesign
Copy link
Contributor

I am in favour of closing, it is not that important considering the current usage of interfaces actually.

@j1010001
Copy link
Member

This would require a breaking change and we don't have consensus, closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants