-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add experimental inferGenericTypes switch #22317
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
Changes from 30 commits
008ffa4
bc08ff2
257fd33
0c6d8bb
d5cbc7e
f2ed402
cf36fa8
a235f14
e331214
851fbcf
04f7e08
d607d26
f915036
165673b
9e42bbc
e66af09
444c169
b554162
1cad79f
189c9e1
6d85aeb
b006c4e
347eb20
2a8353c
dccee38
be98387
fe2b42e
524a289
d926772
fbde75b
970fafc
18465fa
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 |
|---|---|---|
|
|
@@ -562,8 +562,62 @@ proc getCallLineInfo(n: PNode): TLineInfo = | |
| discard | ||
| result = n.info | ||
|
|
||
| proc inheritBindings(c: PContext, x: TCandidate, expectedType: PType): TIdTable = | ||
| ## Helper proc to inherit bound generic parameters from expectedType into a new TIdTable. | ||
| ## Returns existing bindings if 'inferGenericTypes' isn't in c.features | ||
| result = x.bindings | ||
| if inferGenericTypes notin c.features: return | ||
| if expectedType == nil or x.callee[0] == nil: return # required for inference | ||
|
|
||
| var | ||
| flatUnbound: seq[PType] | ||
| flatBound: seq[PType] | ||
| # seq[(result type, expected type)] | ||
| var typeStack = newSeq[(PType, PType)]() | ||
|
Member
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. What are the effects of this computation on the overall compile-times?
Contributor
Author
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. Not sure, asked the same before without receiving a response. Will copy and paste a million lines or so to test (on that note it seems that hints/errors are all on the same line after line of uint16's max is reached)
Contributor
Author
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. Using this code to generate two files, they both take about the same amount of time (immeasurable, they go back and forth. If you know of a better way to test please tell me). The cache was cleared before each compile. Memory usage in this specific test seems to be slightly better for the inferred version, but for other tests the one without inference uses less memory. The version of the nim compiler I used had the check for Codeconst copies = 10_000
block:
var code = """{.experimental: "inferGenericTypes".}
type
TestType[A, B, C, D, E, F, G, H, I, J] = object
proc giveData[A, B, C, D, E, F, G, H, I, J](): TestType[A, B, C, D, seq[E], F, G, H, I, seq[seq[J]]] = discard
var x: TestType[int, float, int, char, seq[byte], int16, uint32, seq[float], int, seq[seq[int]]] = giveData()
"""
for i in 0 ..< copies:
code.add("x = giveData()" & "\n")
writeFile("out1.nim", code)
block:
var code = """type
TestType[A, B, C, D, E, F, G, H, I, J] = object
proc giveData[A, B, C, D, E, F, G, H, I, J](): TestType[A, B, C, D, seq[E], F, G, H, I, seq[seq[J]]] = discard
var x = giveData[int, float, int, char, seq[byte], int16, uint32, seq[float], int, seq[seq[int]]]()
"""
for i in 0 ..< copies:
code.add("x = giveData[int, float, int, char, seq[byte], int16, uint32, seq[float], int, seq[seq[int]]]()" & "\n")
writeFile("out2.nim", code) |
||
|
|
||
| template stackPut(a, b) = | ||
| ## skips types and puts the skipped version on stack | ||
| # It might make sense to skip here one by one. It's not part of the main | ||
| # type reduction because the right side normally won't be skipped | ||
| const toSkip = { tyVar, tyLent, tyStatic, tyCompositeTypeClass } | ||
| let | ||
| x = a.skipTypes(toSkip) | ||
| y = if a.kind notin toSkip: b | ||
| else: b.skipTypes(toSkip) | ||
| typeStack.add((x, y)) | ||
|
|
||
| stackPut(x.callee[0], expectedType) | ||
|
|
||
| while typeStack.len() > 0: | ||
| let (t, u) = typeStack.pop() | ||
| if t == u or t == nil or u == nil or t.kind == tyAnything or u.kind == tyAnything: | ||
| continue | ||
| case t.kind | ||
| of ConcreteTypes, tyGenericInvocation, tyUncheckedArray: | ||
| # nested, add all the types to stack | ||
| let | ||
| startIdx = if u.kind in ConcreteTypes: 0 else: 1 | ||
| endIdx = min(u.sons.len() - startIdx, t.sons.len()) | ||
|
|
||
| for i in startIdx ..< endIdx: | ||
| # early exit with current impl | ||
| if t[i] == nil or u[i] == nil: return | ||
| stackPut(t[i], u[i]) | ||
| of tyGenericParam: | ||
| if result.idTableGet(t) != nil: return | ||
|
|
||
| # fully reduced generic param, bind it | ||
| if t notin flatUnbound: | ||
| flatUnbound.add(t) | ||
| flatBound.add(u) | ||
| else: | ||
| discard | ||
| for i in 0 ..< flatUnbound.len(): | ||
| result.idTablePut(flatUnbound[i], flatBound[i]) | ||
|
|
||
| proc semResolvedCall(c: PContext, x: TCandidate, | ||
| n: PNode, flags: TExprFlags): PNode = | ||
| n: PNode, flags: TExprFlags; | ||
| expectedType: PType = nil): PNode = | ||
| assert x.state == csMatch | ||
| var finalCallee = x.calleeSym | ||
| let info = getCallLineInfo(n) | ||
|
|
@@ -583,11 +637,11 @@ proc semResolvedCall(c: PContext, x: TCandidate, | |
| if x.calleeSym.magic in {mArrGet, mArrPut}: | ||
| finalCallee = x.calleeSym | ||
| else: | ||
| finalCallee = generateInstance(c, x.calleeSym, x.bindings, n.info) | ||
| finalCallee = generateInstance(c, x.calleeSym, c.inheritBindings(x, expectedType), n.info) | ||
| else: | ||
| # For macros and templates, the resolved generic params | ||
| # are added as normal params. | ||
| for s in instantiateGenericParamList(c, gp, x.bindings): | ||
| for s in instantiateGenericParamList(c, gp, c.inheritBindings(x, expectedType)): | ||
| case s.kind | ||
| of skConst: | ||
| if not s.astdef.isNil: | ||
|
|
@@ -615,7 +669,8 @@ proc tryDeref(n: PNode): PNode = | |
| result.add n | ||
|
|
||
| proc semOverloadedCall(c: PContext, n, nOrig: PNode, | ||
| filter: TSymKinds, flags: TExprFlags): PNode = | ||
| filter: TSymKinds, flags: TExprFlags; | ||
| expectedType: PType = nil): PNode = | ||
| var errors: CandidateErrors = @[] # if efExplain in flags: @[] else: nil | ||
| var r = resolveOverloads(c, n, nOrig, filter, flags, errors, efExplain in flags) | ||
| if r.state == csMatch: | ||
|
|
@@ -625,7 +680,7 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, | |
| message(c.config, n.info, hintUserRaw, | ||
| "Non-matching candidates for " & renderTree(n) & "\n" & | ||
| candidates) | ||
| result = semResolvedCall(c, r, n, flags) | ||
| result = semResolvedCall(c, r, n, flags, expectedType) | ||
| else: | ||
| if efDetermineType in flags and c.inGenericContext > 0 and c.matchedConcept == nil: | ||
| result = semGenericStmt(c, n) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
|
|
||
| {.experimental: "inferGenericTypes".} | ||
|
|
||
| import std/tables | ||
|
|
||
| block: | ||
| type | ||
| MyOption[T, Z] = object | ||
| x: T | ||
| y: Z | ||
|
|
||
| proc none[T, Z](): MyOption[T, Z] = | ||
| when T is int: | ||
| result.x = 22 | ||
| when Z is float: | ||
| result.y = 12.0 | ||
|
|
||
| proc myGenericProc[T, Z](): MyOption[T, Z] = | ||
| none() # implied by return type | ||
|
|
||
| let a = myGenericProc[int, float]() | ||
| doAssert a.x == 22 | ||
| doAssert a.y == 12.0 | ||
|
|
||
| let b: MyOption[int, float] = none() # implied by type of b | ||
| doAssert b.x == 22 | ||
| doAssert b.y == 12.0 | ||
|
|
||
| # Simple template based result with inferred type for errors | ||
| block: | ||
| type | ||
| ResultKind {.pure.} = enum | ||
| Ok | ||
| Err | ||
|
|
||
| Result[T] = object | ||
| case kind: ResultKind | ||
| of Ok: | ||
| data: T | ||
| of Err: | ||
| errmsg: cstring | ||
|
|
||
| template err[T](msg: static cstring): Result[T] = | ||
| Result[T](kind : ResultKind.Err, errmsg : msg) | ||
|
|
||
| proc testproc(): Result[int] = | ||
| err("Inferred error!") # implied by proc return | ||
| let r = testproc() | ||
| doAssert r.kind == ResultKind.Err | ||
| doAssert r.errmsg == "Inferred error!" | ||
|
|
||
| # Builtin seq | ||
| block: | ||
| let x: seq[int] = newSeq(1) | ||
| doAssert x is seq[int] | ||
| doAssert x.len() == 1 | ||
|
|
||
| type | ||
| MyType[T, Z] = object | ||
| x: T | ||
| y: Z | ||
|
|
||
| let y: seq[MyType[int, float]] = newSeq(2) | ||
| doAssert y is seq[MyType[int, float]] | ||
| doAssert y.len() == 2 | ||
|
|
||
| let z = MyType[seq[float], string]( | ||
| x : newSeq(3), | ||
| y : "test" | ||
| ) | ||
| doAssert z.x is seq[float] | ||
| doAssert z.x.len() == 3 | ||
| doAssert z.y is string | ||
| doAssert z.y == "test" | ||
|
|
||
| # array | ||
| block: | ||
| proc giveArray[N, T](): array[N, T] = | ||
| for i in 0 .. N.high: | ||
| result[i] = i | ||
| var x: array[2, int] = giveArray() | ||
| doAssert x == [0, 1] | ||
|
|
||
| # tuples | ||
| block: | ||
| proc giveTuple[T, Z]: (T, Z, T) = discard | ||
| let x: (int, float, int) = giveTuple() | ||
| doAssert x is (int, float, int) | ||
| doAssert x == (0, 0.0, 0) | ||
|
|
||
| proc giveNamedTuple[T, Z]: tuple[a: T, b: Z] = discard | ||
| let y: tuple[a: int, b: float] = giveNamedTuple() | ||
| doAssert y is (int, float) | ||
| doAssert y is tuple[a: int, b: float] | ||
| doAssert y == (0, 0.0) | ||
|
|
||
| proc giveNestedTuple[T, Z]: ((T, Z), Z) = discard | ||
| let z: ((int, float), float) = giveNestedTuple() | ||
| doAssert z is ((int, float), float) | ||
| doAssert z == ((0, 0.0), 0.0) | ||
|
|
||
| # nesting inside a generic type | ||
| type MyType[T] = object | ||
| x: T | ||
| let a = MyType[(int, MyType[float])](x : giveNamedTuple()) | ||
| doAssert a.x is (int, MyType[float]) | ||
|
|
||
|
|
||
| # basic constructors | ||
| block: | ||
| type MyType[T] = object | ||
| x: T | ||
|
|
||
| proc giveValue[T](): T = | ||
| when T is int: | ||
| 12 | ||
| else: | ||
| default(T) | ||
|
|
||
| let x = MyType[int](x : giveValue()) | ||
| doAssert x.x is int | ||
| doAssert x.x == 12 | ||
|
|
||
| let y = MyType[MyType[float]](x : MyType[float](x : giveValue())) | ||
| doAssert y.x is MyType[float] | ||
| doAssert y.x.x is float | ||
| doAssert y.x.x == 0.0 | ||
|
|
||
| # 'MyType[float]' is bound to 'T' directly | ||
| # instead of mapping 'T' to 'float' | ||
| let z = MyType[MyType[float]](x : giveValue()) | ||
| doAssert z.x is MyType[float] | ||
| doAssert z.x.x == 0.0 | ||
|
|
||
| type Foo = object | ||
| x: Table[int, float] | ||
|
|
||
| let a = Foo(x: initTable()) | ||
| doAssert a.x is Table[int, float] |
Uh oh!
There was an error while loading. Please reload this page.