Skip to content

Commit 477af31

Browse files
committed
Document generic type argument inference
ponyc PR #5974 adds type argument inference for generic method and constructor calls. The tutorial's generics overview now covers the feature: when it applies, how it interacts with type parameter defaults, and the cases where explicit type arguments are still required.
1 parent 874c45e commit 477af31

4 files changed

Lines changed: 95 additions & 1 deletion

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class Foo[A: Any val]
2+
var _c: A
3+
4+
new create(c: A) =>
5+
_c = c
6+
7+
fun get(): A => _c
8+
9+
fun ref set(c: A) => _c = c
10+
11+
actor Main
12+
new create(env: Env) =>
13+
let a = Foo(42)
14+
env.out.print(a.get().string())
15+
16+
let b = Foo(1.5)
17+
env.out.print(b.get().string())
18+
19+
let c = Foo("Hello")
20+
env.out.print(c.get().string())
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Bar[A: Any box = USize val]
2+
var _c: A
3+
4+
new create(c: A) =>
5+
_c = c
6+
7+
fun get(): A => _c
8+
9+
fun ref set(c: A) => _c = c
10+
11+
actor Main
12+
new create(env: Env) =>
13+
let a = Bar(42) // A is USize because the argument fits the default
14+
let b = Bar(F32(1.5)) // A is F32 because the argument overrides the default
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
primitive Foo
2+
fun bar[A: Stringable val](a: A): String =>
3+
a.string()
4+
5+
actor Main
6+
new create(env: Env) =>
7+
let a = Foo.bar(U32(10))
8+
env.out.print(a.string())
9+
10+
let b = Foo.bar("Hello")
11+
env.out.print(b.string())

docs/generics/index.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ The first thing to note here is that the `Foo` class now takes a type parameter
2525

2626
In this case, the name is `A`, the constraint is `Any` and the reference capability is `val`. `Any` is used to mean that the type can be any type - it is not constrained. The remainder of the class definition replaces `U32` with the type name `A`.
2727

28-
The user of the class must provide a type when referencing the class name. This is done when creating it:
28+
The user of the class provides a type when referencing the class name, or lets the compiler [infer it from the arguments](#type-argument-inference). Here the type is provided explicitly:
2929

3030
```pony
3131
--8<--
@@ -69,3 +69,52 @@ Methods can be generic too. They are defined in the same way as normal methods b
6969
This example shows a constraint other than `Any`. The `Stringable` type is any type with a `string()` method to convert to a `String`.
7070

7171
These examples show the basic idea behind generics and how to use them. Real world usage gets quite a bit more complex and the following sections will dive deeper into how to use them.
72+
73+
## Type Argument Inference
74+
75+
When each type argument can be determined from the arguments at the call site, you can omit the type arguments. The generic methods example above includes explicit type arguments:
76+
77+
```pony
78+
--8<-- "generics-generic-methods.pony:7:7"
79+
```
80+
81+
With inference, `A` resolves to `U32` from the argument `U32(10)`, so the type argument can be omitted:
82+
83+
```pony
84+
--8<-- "generics-type-argument-inference-method.pony:7:7"
85+
```
86+
87+
The argument is `U32(10)` rather than the bare `10` from the explicit example — a bare integer literal defaults to `USize`, so `Foo.bar(10)` would resolve `A` to `USize`.
88+
89+
The same applies to constructor calls. The `Foo` class from earlier can be constructed without writing the type argument:
90+
91+
```pony
92+
--8<--
93+
generics-type-argument-inference-constructor.pony:13:13
94+
generics-type-argument-inference-constructor.pony:16:16
95+
generics-type-argument-inference-constructor.pony:19:19
96+
--8<--
97+
```
98+
99+
### Interaction with defaults
100+
101+
When a type parameter has a default and the argument fits the default type, the default is kept. When the argument does not fit, the inferred type replaces the default:
102+
103+
```pony
104+
--8<-- "generics-type-argument-inference-defaults.pony:13:14"
105+
```
106+
107+
In the first line, `42` fits `USize` (the default), so `A` stays `USize`. In the second line, `F32(1.5)` does not fit `USize`, so `A` becomes `F32`.
108+
109+
### When inference does not apply
110+
111+
At least one argument must determine each type parameter. When inference fails, the compiler reports an error — write the type arguments explicitly to resolve it. Cases where inference does not apply:
112+
113+
- The type parameter does not appear in any parameter type (it only appears in the return type, for example).
114+
- The argument at the determining position is an array literal or lambda whose own type depends on the type parameter being inferred. This includes type parameters that appear only in a lambda's result type.
115+
- The parameter type refers to the type parameter through a type alias.
116+
- The parameter type is a union.
117+
- The argument is passed with the `where` keyword (named-only arguments).
118+
- A generic method is called on a type whose own type parameters use defaults and are written without type arguments — add explicit type arguments to either the type or the method.
119+
120+
You can always write the type arguments explicitly — inference is a convenience, not a requirement.

0 commit comments

Comments
 (0)