-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New Syntax for Capture Variables and Explicit Capture Polymorphism v3 #23063
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
base: main
Are you sure you want to change the base?
Conversation
9e1754b
to
ff07e02
Compare
@odersky making these behave analogously to higher-kinded type parameters makes me wonder if
|
If the lower bound of a capture variable is temprarily |
It's not impossible to add the lower bound (& upper bound) during parsing. But if the mental model is "this is another kind", then the proposed scheme will be irregular for someone who is familiar with the syntax- and subtyping-rules for higher-kinded types in Scala. |
I don't think we should make a capture variable a real "kind"... |
* Refrain from using a flag and in favor of an attachment. * Rule out context bounds entirely for capture vars.
@noti0na1 |
We cannot post-process the typedef in typedTypeDef in the typer, because adaptation of the rhs prematurely sets the symbol info to the old bounds pre-expansion, which will be caught by checkBounds in posttyper. It is easier to mark the rhs as well and do the expansion in typedTypeBoundsTree.
@odersky We cannot post-process in typedTypeDef in the typer, because adaptation of the unprocessed rhs prematurely sets the |
I don't like there are so many special handling for capture sets when parsing a type dound/def. Can we simplily parse a capture set as a "Type"? ParserWhen we parse
If a type def has a hat, we add some attachment and use for latter. We don't care the rhs when parsing. TyperWhen completing a symbol for a type def:
type C1^ // => capture type C1 >: CapSet <: CapSet^
type C2^ <: {a} // => capture type C2 >: CapSet <: CapSet^{a}
type C3 <: {a} // => regular type C3 >: Nothing <: CapSet^{a}
type C4 >: {} <: {a} // => capture type C4 >: CapSet <: CapSet^{a}
type C5 = {a} // => capture type C5 >: CapSet^{a} <: CapSet^{a}
type C6^ >: {} <: {a} // => capture type, but redundant hat
type C7^ = {a} // => capture type, but redundant hat
type C8 >: C1 <: C2 // => capture type C8 >: C1<: C2
def f[C^] = ???
f[{a}]
type S = {a}
f[{S}] // ok
f[S] // ok, just looks like a substitution |
Right now, it's tailored to exactly the usage contexts where we expect to be able to write literal capture sets. Making this a new case for general types would also permit expressions like |
I'd say a Since we don't have any value for |
type C3 <: {a} // => regular type C3 >: Nothing <: CapSet^{a} Well, hence my comment on making it truly like a new kind. If we upper-bound f[{a}]
type S = {a}
f[{S}] // ok
f[S] // ok, just looks like a substitution I'm pretty sure these all work right now. No? |
Well, if we really want to relax the rule for My example just want to show allowing a set should be equal to allowing a capture variable in bound. |
After discussing with @odersky, we are not going to deviate from the current scheme and will permit the sets only in those specific circumstances. While the general approach and code structure will remain as is, I appreciate feedback on specific details and possible simplifications - just not the broader simplification that was proposed, which we feel is too general for this case. |
OK, I will add another point to my proposal: if we want to support capture set intersection in the future, for example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only minor changes needed. Generally looks good!
NamesAndTypes ::= NameAndType {‘,’ NameAndType} | ||
NameAndType ::= id ':' Type | ||
CaptureSet ::= ‘{’ CaptureRef {‘,’ CaptureRef} ‘}’ -- under captureChecking | ||
CaptureRef ::= { SimpleRef ‘.’ } SimpleRef [‘*’] [‘.’ ‘rd’] -- under captureChecking | ||
| [ { SimpleRef ‘.’ } SimpleRef ‘.’ ] id -- under captureChecking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| [ { SimpleRef ‘.’ } SimpleRef ‘.’ ] id -- under captureChecking |
This line is now redundant. We needed it only to support the final ^
which is now gone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see it was already removed in Parser.
inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are | ||
currently in development. | ||
|
||
Finally, analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This are probably quite exotic use cases. My concern is we muddle the message how capabilties would usually be applied, and we scare people away. Suggestion: Let's make a separate page (only linked from here, not sidebar.yml) on "advanced use cases of capabilties".
Since `caps.cap` is the top element for subcapturing, we could have also left out the | ||
upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. | ||
|
||
We conclude with a more advanced example, showing how capability members and paths to these members can prevent leakage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here. Let's move this to the adavnced use cases page.
*/ | ||
def argTypes(namedOK: Boolean, wildOK: Boolean, tupleOK: Boolean): List[Tree] = | ||
def argType() = | ||
val t = typ() | ||
inline def wildCardCheck(inline gen: Tree): Tree = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gen does does not have to be inline. It's evaluated before anything else anyway. I think wildcardCheck does not have to be inline either. The JVM will inline this anyway. Inlining is useful if it prevents boxing or closure creation. Here neither happens.
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.enablesNamedTuples then | ||
commaSeparated(() => namedElem()) | ||
commaSeparated(() => nameAndType()) // TODO: can capture-set variables occur here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think they can.
if (in.token == tok) { in.nextToken(); toplevelTyp() } | ||
if (in.token == tok) then | ||
in.nextToken() | ||
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if Feature.ccEnabled && (in.token == LBRACE && !isDclIntroNext) then | |
if Feature.ccEnabled && in.token == LBRACE && !isDclIntroNext then |
if isLowerBound && refs.isEmpty then // lower bounds with empty capture sets become a pure CapSet | ||
Select(scalaDot(nme.caps), tpnme.CapSet) | ||
else | ||
makeRetaining(Select(scalaDot(nme.caps), tpnme.CapSet), refs, tpnme.retains) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can re-use concreteCapsType
here.
import caps.* | ||
|
||
trait Abstract[X^]: | ||
type C >: X <: CapSet^ | ||
type C^ >: {X} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would C >: {X}
also work here? If yes, let's test that it does.
@@ -5,14 +5,14 @@ class IO | |||
case class File(io: IO^) | |||
|
|||
def test(io1: IO^, io2: IO^) = | |||
def f[C >: CapSet^{io1} <: CapSet^](file: File^{C^}) = ??? | |||
def f[C^ >: {io1}](file: File^{C}) = ??? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would C >: {io1}
also work here? If yes, let's test that it does.
@@ -715,6 +715,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer | |||
&& ctx.owner.owner.unforcedDecls.lookup(tree.name).exists | |||
then // we are in the arguments of a this(...) constructor call | |||
errorTree(tree, em"$tree is not accessible from constructor arguments") | |||
else if name.isTermName && ctx.mode.is(Mode.InCaptureSet) then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this will be changed later by Yaoyu's commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so, I inherited this change from his earlier PR removing ^
in for capture refs to capture variables.
Closes #22490
Builds on #22725
Supersedes #22902
This syntax for capture variables and members caters to the view that "they are another kind", and most conventions we know from higher-kinded type parameters carry over.
A postfix
^
(analogous to[_]
) indicates that a type parameter/member ranges over capture setsThese variables can be bounded by capture-set literals:
Just as with higher-kinded type parameters, the kind is "contagious", and we can often avoid the hat if the bounds are concrete capture sets or other capture variables:
Capture variables will be erased if capture checking is turned off. In non-capture-checked code, they are essentially type variables within the interval
>: caps.CapSet <: caps.CapSet
. With capture checking turned on and without explicit bounds, the interval corresponds to>: caps.CapSet^{} <: caps.CapSet^{caps.cap}
.Tasks