meetings/working-groups/roles/roles-2023-01-25.md
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).
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"
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)
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).
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.
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.
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.
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
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.
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.