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

Proposal: Ability to seal trees of classes #8726

Closed
DerpMcDerp opened this issue Feb 16, 2016 · 12 comments
Closed

Proposal: Ability to seal trees of classes #8726

DerpMcDerp opened this issue Feb 16, 2016 · 12 comments

Comments

@DerpMcDerp
Copy link

If you have a tree of classes, e.g.

Object
    Atom
    Cons
    Number
        Integer
            FixInt
            BigInt
        Rational
            BigRat
            Approx
                Float
                Double
    Lambda

C# only lets you seal the leaves of the hiearchy, i.e. Atom, Cons, FixInt, BigInt, BigRat Float, Double, Lambda.

Number, Integer, Rational, and Approx are (currently) required to be unsealed. It would be better if there was a way to make them sealed as well. Something like the following could let you do that:

[UnsealedTo(Integer, Rational)]
public sealed class Number {
    ...
}

[UnsealedTo(FixInt, BigInt)]
public sealed class Integer {
    ...
}

[UnsealedTo(BigRat, Approx)]
public sealed class Rational {
    ...
}

[UnsealedTo(Float, Double)]
public sealed class Approx {
    ...
}

UnsealedToAttribute lets you list which classes the compiler allows you to treat as if it were unsealed. This gives us the ability to seal trees of classes rather than just the leaves.

The compiler should verify that:

  1. the class that UnsealedTo is applied on is an immediate parent of all the classes listed
  2. the classes listed are all sealed

There should also be a Type.IsSealedLeaf property to distinguish between sealed leaves and uses of the sealed keyword (i.e. Type.IsSealedLeaf should be a subset of Type.IsSealed).

@orthoxerox
Copy link
Contributor

Dup of #188, #8455

@DerpMcDerp
Copy link
Author

I don't believe this proposal is a duplicate of those other proposals. Those other proposals are geared towards making sure that the compiler can prove that the matches:

switch (foo) {
    match pattern_1:
        ...
    match pattern_2:
        ...
}

are exhaustive (i.e. it's basically a convoluted way of getting discriminated union support into the CLR). My proposal wasn't really intended to be geared towards that (admittedly, my simplistic lisp example made it look like that's what I was proposing) e.g.

  1. Proposal: Add completeness checking to pattern matching draft specification #188 has weird restrictions on what the ancestor classes have to look like. This proposal lets ancestor classes look like anything.
  2. It's not clear how classes across assemblies work with those other proposals. With my proposal, cross assembly support falls out automatically.
  3. non-breaking CLR changes aren't a goal for me.
  4. those other proposals either allow unrestricted free-form derivation e.g.
switch class Foo {}
case class Bar : Foo {}
case class Car : Foo {}
// how to constrain this?

or restricted closed-form derivation:

switch class Foo {
    case class Bar {}
    case class Car {}
// how to extend this?
}

My proposal is basically the C++ friend keyword but for sealed instead of private.

@alrz
Copy link
Member

alrz commented Feb 16, 2016

I think this is covered in #8729.

@HaloFour
Copy link

This would be more a CLR proposal than a Roslyn proposal. Without support from the CLR to encode and enforce these restrictions it wouldn't really provide any benefit. I'd recommend opening an issue at CoreCLR.

@bondsbw
Copy link

bondsbw commented Feb 16, 2016

Is there any reason this must be limited to classes? This could probably be extended to virtual properties and methods too.

@bondsbw
Copy link

bondsbw commented Feb 16, 2016

I like this idea but I think I prefer the implementation in #8729. Why? I don't like attributes that influence language-level semantics. It's probably just a personal preference.

@HaloFour
Copy link

C# frequently uses attributes in place of new keywords or language constructs in order to express behaviors or metadata, even when the CLR doesn't actually use those attributes. SerializedAttribute, StructLayoutAttribute and DllImportAttribute are good examples. I think that the behavior described by this proposal maps well to the same concepts achieved by the InternalsVisibleToAttribute in that it's an approach to easing restrictions enforced by the CLR.

I think that the pros of a new keyword is less typing and potentially better developer experience if the white-listed types are determined at compile-time, which solves the chicken/egg problem of listing derived classes to a class that doesn't yet exist. But the cons are that it encodes the concept permanently into the language when really it could be considered more of a runtime-provided feature.

@bondsbw
Copy link

bondsbw commented Feb 16, 2016

@HaloFour I know. I don't like those either. 😛

I do agree with attributes for runtime features. I don't really like that they represent both compile-time and runtime features and it's not clear to the user which is which. (That statement unfortunately also applies to some non-attribute syntax as well.)

But the cons are that it encodes the concept permanently into the language

That would happen anyway. If this were supported in C# 8, C# 9+ couldn't remove it without breaking BC.

@HaloFour
Copy link

@bondsbw

But as an attribute it could be excluded from versions of the run-time that will not support it without affecting the language. For example, CoreCLR does not support SerializableAttribute, which is a pseudo-attribute. If there was a serializable keyword rather than a pseudo-attribute it might be bizarre to have code that uses the keyword happen to fail when targeting the different framework.

@DerpMcDerp
Copy link
Author

@bondsbw: I'm not tied to attribute syntax. it can look like:

public sealed(Bar, Car) class Foo {
}

or

public sealed class Foo {
    sealed friend Bar, Car;
}

which ultimately uses attributes underneath for all I care. One thing that might be useful is to have an UnsealedFromAttribute to mimic type forwarding's strategy of symmetric annotations.

Also, I guess the attribute can be applied to sealed methods/properties as well, e.g.

public class Bar : Foo {
    [UnsealedTo(Car)]
    sealed override void foo() { }
}

public class Car : Bar {
    // compiler verifies that this method sealed
    // The restriction that Bar must be an immediate parent of Car
    // can be relaxed when [UnsealedTo] is applied to methods/properties
    sealed override void foo() { }
}

@gafter
Copy link
Member

gafter commented Mar 27, 2017

We are now taking language feature discussion in other repositories:

Features that are under active design or development, or which are "championed" by someone on the language design team, have already been moved either as issues or as checked-in design documents. For example, the proposal in this repo "Proposal: Partial interface implementation a.k.a. Traits" (issue 16139 and a few other issues that request the same thing) are now tracked by the language team at issue 52 in https:/dotnet/csharplang/issues, and there is a draft spec at https:/dotnet/csharplang/blob/master/proposals/default-interface-methods.md and further discussion at issue 288 in https:/dotnet/csharplang/issues. Prototyping of the compiler portion of language features is still tracked here; see, for example, https:/dotnet/roslyn/tree/features/DefaultInterfaceImplementation and issue 17952.

In order to facilitate that transition, we have started closing language design discussions from the roslyn repo with a note briefly explaining why. When we are aware of an existing discussion for the feature already in the new repo, we are adding a link to that. But we're not adding new issues to the new repos for existing discussions in this repo that the language design team does not currently envision taking on. Our intent is to eventually close the language design issues in the Roslyn repo and encourage discussion in one of the new repos instead.

Our intent is not to shut down discussion on language design - you can still continue discussion on the closed issues if you want - but rather we would like to encourage people to move discussion to where we are more likely to be paying attention (the new repo), or to abandon discussions that are no longer of interest to you.

If you happen to notice that one of the closed issues has a relevant issue in the new repo, and we have not added a link to the new issue, we would appreciate you providing a link from the old to the new discussion. That way people who are still interested in the discussion can start paying attention to the new issue.

Also, we'd welcome any ideas you might have on how we could better manage the transition. Comments and discussion about closing and/or moving issues should be directed to #18002. Comments and discussion about this issue can take place here or on an issue in the relevant repo.


There are a number of issues discussing closed hierarchies of types, in some cases with help from the compiler when used in switch statements. I have sympathy with those requests, and expect that we will do something along those lines either at the same time or sometime after records. In any case, I don't think this issue is our likely starting point for the design.

@gafter gafter closed this as completed Mar 27, 2017
@gafter
Copy link
Member

gafter commented Mar 27, 2017

See dotnet/csharplang#113 for where we are tracking the set of related issues.

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

7 participants