-
Notifications
You must be signed in to change notification settings - Fork 51
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
Add eager inference annotation for polymorphic types #106
base: master
Are you sure you want to change the base?
Changes from all commits
213ec9f
cb82f7a
bf354a1
8f1c0d9
8a24f5b
9df20a0
e25643f
1943be7
1d36401
1c8d217
2ed0187
b7c2800
b47e000
29384af
f5c5609
b995140
5308d81
c9328c7
27d8c66
cbc02bd
9fa24be
96f8aaa
b83c473
1fee95b
458dcb6
8bb92ba
7d20d52
2463447
6264249
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Eager Inference Annotations for Polymorphic Types | ||
|
||
## Summary | ||
|
||
The RFC introduces a feature to annotate polymorphic function types to express that the first bind to `T` will be the one that sticks. | ||
|
||
## Motivation | ||
|
||
The purpose of this feature is to develop syntax to prevent polymorphic types from widening into (e.g., number | string) when a function is implicitly instantiated with different argument types. E.g., `test(1, "a")`. In the following code, Luau's Type Inference Engine V2 infers `T` to be of a union type: | ||
|
||
```luau | ||
function test<T>(a: T, b: T): T | ||
return a | ||
end | ||
|
||
local result = test(1, "string") -- inferred type `T`: number | string" | ||
``` | ||
|
||
This behaviour can be useful in some cases but is undesirable when a polymorphic type is intended to constrain the subsequent input types to be identical to the first usage. | ||
|
||
## Design | ||
|
||
We propose adding some symbol as a suffix (or prefix) that annotates the "eager" inference behaviour for a polymorphic type. | ||
Subsequent uses of a polymorphic type `T` where `T` is "eager" will be inferred as the precise type of the first occurrence. | ||
|
||
### New Syntax | ||
|
||
The `!` syntax modifier would would enforce an eager inference behaviour for `T!`: | ||
|
||
```luau | ||
function test<T!>(a: T, b: T): T | ||
return a | ||
end | ||
|
||
test(1, "string") -- TypeError: Expected `number`, got `string` | ||
``` | ||
|
||
## Drawbacks | ||
|
||
- Introduces a new syntax modifier `!`, which may lead to a symbol soup. However, `T!` doesn't seem too shabby next to `T?`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this is actually a reasonably strong argument against this syntax: people might confuse it as having something to do with optionals or There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cc @Ukendio There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah might lean onto using keywords or putting the annotation as a prefix instead |
||
- Introduces a simple change to luau's parser, marginally increasing parsing complexity. | ||
|
||
## Alternatives | ||
### Per-usage-bindings | ||
Flip the relationship being declarared per-type-parameter to per-usage which provides more control in expressing the inference, and could allow both instantiation behaviours of polymorphic types under a uniform syntax. | ||
|
||
A polymorphic typed marked with type `T!` will not contribute to the instantiation of type `T` in the function. Instead, `T` should be inferred on the arguments without the annotation: | ||
|
||
```luau | ||
function test<T>(first: T, second: T, third: T!): T | ||
return first | ||
end | ||
|
||
test(1, "string", true) -- TypeError: Type `boolean` could not be converted into `number | string` | ||
``` | ||
|
||
This has the added drawback that the `!` syntax modifier would need to be barred from return types, as the return type holds no relevance to implicit instantiation. | ||
Also has the major drawback of symbol complexity. E.g., type aliases with `T!?` are entirely possible under this model. | ||
|
||
### noinfer\<T\> | ||
Same as above, except we introduce no new syntax, symbol usage, etc. into the language. Create a binding similar or equivalent to typescript's noinfer<T>: | ||
|
||
```luau | ||
function test<T>(first: T, second: T, third: noinfer<T>): T | ||
return first | ||
end | ||
|
||
test(1, "string", true) -- TypeError: Type `boolean` could not be converted into `number | string` | ||
``` | ||
|
||
### Keywords | ||
Something like `<greedy T>` or `<strict T>` should also be considered if we want to reduce symbols. |
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.
What would be the behavior when a type references
T
more than once, such as an object?Additionally, what about when the object type is aliased (i.e there's no specific "first occurence")
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.
It would behave in the same way as the old type solver which is taking the first definition that binds to
T
.Similar answer to the last point which is mirroring the old behaviour but in this case would be to go in the order of the fields. I don't know the exact heuristics for that is but I feel most of this could be answered with just saying it should do whatever the type solver did.
I will note that I do think something like
noinfer<T>
is a pretty good solution in these cases but I haven't personally prepared to make a case for that proposal.