Back to Csharplang

Roles WG 2023-01-25

meetings/working-groups/roles/roles-2023-01-25.md

latest4.4 KB
Original Source

Roles WG 2023-01-25

Language for relationships

We'll use "augments" for relationship to underlying type (comparable to "inherits" for relationship to base type).
We'll use "inherits" for relationship to inherited roles (comparable to "implements" for relationship to implemented interfaces).

csharp
struct U { }
role X : U { }
role Y : U, X, X1 { }

"Y has underlying type U"
"Y augments U"
"Y inherits X and X1"
"Derived role Y inherits members from inherited roles X and X1"

Syntax for role underlying type list

We should assume multiple inheritance for now. Will update syntax to allow multiple inherited roles.
In that syntax, the first type will be the underlying type.
role R : I { } // First type is underlying type (not an implemented interface)

Type parameters in extensions

The purpose of requiring all extension type parameters to appear in the underlying type is to help find a unique substitution when looking for compatible extensions.

For example, if you have extension X<T> : U<T> and then need to decide whether it is compatible with the U<int> type, you need to find a unique compatible substituted extension type X<int>.

This rule does not apply to roles (assuming that we don't let consumers turn a role into an extension after the fact).

Constraints

Will need to spell out rules for all kinds of constraints, such as struct, class, etc.

where T : Extension, where T : Role
We'll disallow roles/extensions in type constraints for now. But this is assuming that casting makes the experience acceptable and doesn't cause an issue with struct.

Definition of "role type"

Interfaces don't have a base type, but have base interfaces.
Similarly, roles don't have a base type, but have base roles.

role R<T> : T where T : I1, I2 { } role R<T> : T where T : INumber<T> { } A role may be a value or reference type, and this may not be known at compile-time.

Emit strategy

This will need more discussion, and we prefer to separate it from language-level questions.
System.Role<T> is harder to use for T being pointer, ref struct. Also implies runtime support.
We don't want a breaking change when we reach phase C and you add an interface to an existing role.
We'd rather limit amount of runtime work in phase B, if possible.

A typeref is preferrable to some string.
We brainstormed using a modopt for encoding the underlying type in a way that would support pointers and ref structs. Maybe something like System.Role<modopt(pointer) void> or ref struct S : modopt(pointer) System.Role.
But there is a deep restriction in compiler codebase. Internal/public API expose custom modifiers as named types.
This restriction stems from the published version of 335, so if we take a dependency on being able to put Types in a modopt and not just TypeRefs, the runtime will want to doc the spec change in their 335 augments doc.

Another option for this position (underlying type) would be to use an attribute. This would not be sprawling.

Extension type members

We should allow new and warn that you should use new when shadowing. Shadowing includes underlying type and inherited roles.

class U { public void M() { } }
role R : U { /*new*/ public void M() { } } // wins when dealing with an R
class U { public void M() { } }
extension X : U { /*new*/ public void M() { } } // ignored in some cases, but extension is a role so rule should apply anyways
U u;
u.M(); // U.M (ignored X.M)
X x;
x.M(); // X.M
class U { }
role R : U { public void M() { } }
role R2 : U, R { /*new*/ public void M() { } } // wins when dealing with an R2

Accessing various members, qualifying names

Will need to spec or disallow base. syntax. Casting seems an adequate solution to access hidden members: ((R)r2).M().
We may not need a syntax like base(R). which was brainstormed for some other features.

We want to ensure that both of these are possible:
From an extension, need to access a hidden thing.
From an underlying type, still need to access the extension member when extension loses.

Conversions

Should allow conversion operators. Extension conversion is useful. Example: from int to string (done by StringExtension).
But we should disallow user-defined conversions from/to underlying type or inherited roles, because a conversion already exists.
Conversion to interface still disallowed.