Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,16 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
if a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
hasUserSpecifiedType = true
# Issue #4086: `var f: Foo` for `type Foo[T = int]` auto-expands to
# `Foo[int]` when every generic param has a default. Restricted to
# var/let/const declarations so it does not affect type-level
# computations like `arity(SomeGeneric)` in template/typetraits
# contexts where bare generic-body references are intentional.
if typ != nil and typ.kind == tyGenericBody and typ.sym != nil:
let auto = tryGenericBodyDefaultInvocation(c,
newSymNode(typ.sym, a[^2].info), typ.sym, nil)
if auto != nil:
typ = auto

var typFlags: TTypeAllowedFlags = {}

Expand Down Expand Up @@ -1009,6 +1019,16 @@ proc semConst(c: PContext, n: PNode): PNode =
if a[^2].kind != nkEmpty:
typ = semTypeNode(c, a[^2], nil)
hasUserSpecifiedType = true
# Issue #4086: `var f: Foo` for `type Foo[T = int]` auto-expands to
# `Foo[int]` when every generic param has a default. Restricted to
# var/let/const declarations so it does not affect type-level
# computations like `arity(SomeGeneric)` in template/typetraits
# contexts where bare generic-body references are intentional.
if typ != nil and typ.kind == tyGenericBody and typ.sym != nil:
let auto = tryGenericBodyDefaultInvocation(c,
newSymNode(typ.sym, a[^2].info), typ.sym, nil)
if auto != nil:
typ = auto

var typFlags: TTypeAllowedFlags = {}

Expand Down
58 changes: 58 additions & 0 deletions compiler/semtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1736,6 +1736,33 @@ proc containsGenericInvocationWithForward(n: PNode): bool =
return true
return false

proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType

proc tryGenericBodyDefaultInvocation*(c: PContext, n: PNode, s: PSym,
prev: PType): PType =
## Issue #4086 sub-case: `type Foo[T = int]; var f: Foo` is sugar for
## `var f: Foo[int]` when every generic param has a default. Synthesize
## a `Foo[default1, default2, ...]` bracket node and dispatch to the
## existing `semGeneric` so the default-substitution machinery (added
## for issues #4086 / #9355) handles cascading and parameter-referencing
## defaults uniformly.
result = nil
if s.typ == nil: return
let body = s.typ.skipTypes({tyAlias})
if body.kind != tyGenericBody or body.len < 2: return
for i in 0..<body.len-1:
let p = body[i]
if p.kind != tyGenericParam or p.sym == nil or p.sym.ast == nil:
return
var bracket = newNodeI(nkBracketExpr, n.info)
if n.kind == nkSym:
bracket.add n
else:
bracket.add newSymNode(s, n.info)
for i in 0..<body.len-1:
bracket.add copyTree(body[i].sym.ast)
result = semGeneric(c, bracket, s, prev)

proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
if s.typ == nil:
localError(c.config, n.info, "cannot instantiate the '$1' $2" %
Expand Down Expand Up @@ -1791,8 +1818,35 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
let rType = m.call[0].typ
let mIndex = if rType != nil: rType.len - 1 else: -1
var hasForwardTypeParam = false

# Issues #4086, #9355: when a generic param's default value references
# earlier type params (e.g. `type Foo[T; U = seq[T]]` or `func foo[T](U = T)`)
# substitute those references with already-resolved bindings before
# adding to the invocation. Bindings are accumulated as the loop walks
# left-to-right, so cascade defaults like `[T; U = seq[T]; V = seq[U]]`
# work too (each iteration resolves against the previous ones).
# Skip the bookkeeping entirely when no param has a default — this
# generic body cannot need substitution and the work would just be
# pure overhead (matters for large generic graphs / forward cycles).
var hasAnyDefault = false
for i in 0..<t.len-1:
let p = t[i]
if p.kind == tyGenericParam and p.sym != nil and p.sym.ast != nil:
hasAnyDefault = true
break
var defaultBindings = initLayeredTypeMap()
var hasDefaultBinding = false

for i in 1..<m.call.len:
var typ = m.call[i].typ

if hasDefaultBinding and nfDefaultParam in m.call[i].flags and
containsGenericType(typ):
var cl = initTypeVars(c, defaultBindings, n.info, getCurrOwner(c))
let substituted = replaceTypeVarsT(cl, typ)
if substituted != nil:
typ = substituted

# is this a 'typedesc' *parameter*? If so, use the typedesc type,
# unstripped.
if m.call[i].kind == nkSym and m.call[i].sym.kind == skParam and
Expand All @@ -1807,6 +1861,10 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType =
skip = false
addToResult(typ, skip)

if hasAnyDefault and i - 1 < t.kidsLen and t[i - 1].kind == tyGenericParam:
defaultBindings.put(t[i - 1], typ.skipTypes({tyTypeDesc}))
hasDefaultBinding = true

if typ.kind == tyForward:
hasForwardTypeParam = true

Expand Down
23 changes: 23 additions & 0 deletions compiler/sigmatch.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3115,6 +3115,29 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) =
if nfDefaultRefsParam in formal.ast.flags:
m.call.flags.incl nfDefaultRefsParam
var defaultValue = copyTree(formal.ast)
# Issues #4086, #9355: when the default value is a bare reference
# to an earlier generic type param (e.g. `func foo[T](U: type = T)`
# or its untyped form `func foo[T](U = T)`) — recognised by the
# default's static type being `tyGenericParam` — substitute T
# against the explicit-instantiation bindings via
# `prepareTypesInBody` (which updates both the AST sym and its
# typ) and wrap the result as `tyTypeDesc` so the
# `tfImplicitTypeParam` binding path below treats it like a
# literal type default (`U: type = int`).
# Other defaults (value expressions like `arr.high`, proc-call
# expressions like `newTensor[T](0)` whose result type is a
# `tyGenericInvocation`/`tyGenericInst`, etc.) have a non-
# `tyGenericParam` type and are left untouched, so the existing
# default-handling machinery takes care of them.
if defaultValue.typ != nil and
defaultValue.typ.kind == tyGenericParam and
m.calleeSym != nil:
defaultValue = prepareTypesInBody(c, m.bindings, defaultValue, m.calleeSym)
if formal.typ.kind == tyTypeDesc and defaultValue.typ != nil and
defaultValue.typ.kind != tyTypeDesc:
let typedescTyp = newTypeS(tyTypeDesc, c, defaultValue.typ)
typedescTyp.incl tfCheckedForDestructor
defaultValue.typ = typedescTyp
if defaultValue.kind == nkNilLit:
defaultValue = implicitConv(nkHiddenStdConv, formal.typ, defaultValue, m, c)
# proc foo(x: T = 0.0)
Expand Down
116 changes: 116 additions & 0 deletions tests/generics/tgeneric_param_default_deps.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
discard """
matrix: "; --mm:refc"
"""

import std/deques

# Type-side generic param defaults that reference other type parameters
# (issue #4086).
#
# Currently fails on Nim 2.3.1 devel:
# `Error: invalid type: 'Foo[system.int, seq[T]]' for var`
#
# Other languages (C++, TypeScript, Rust, Scala) all support this pattern.
# Nim already supports T-independent defaults like `[T; U = int]`; this
# extends support to type-side defaults that reference earlier type params
# (e.g. `type Foo[T; U = seq[T]]`). This is needed for binding C++ templates
# like `std::vector<T, allocator<T>>` and `std::unique_ptr<T, default_delete<T>>`.
#
# Note: proc-side brackets-internal defaults (`proc foo[T, U = T]()`) are
# out of scope; the PR's logic targets type-side default substitution.
# A separate change would be needed for proc-side signature defaults.

block: # #4086 type-side: object generic param default `seq[T]`
type Foo[T; U = seq[T]] = object
data: U
var f: Foo[int]
f.data.add 42
doAssert f.data == @[42]

block: # nested reference: U default uses T, V default uses U
type Foo[T; U = seq[T]; V = seq[U]] = object
data: V
var f: Foo[int]
f.data.add @[1, 2]
doAssert f.data == @[@[1, 2]]

block: # type-side direct: U = T
type Foo[T; U = T] = object
a: T
b: U
var f: Foo[int]
f.a = 1
f.b = 2
doAssert f.b is int

block: # type-side compound: U = ref T
type Foo[T; U = ref T] = object
p: U
var f: Foo[int]
f.p = new(int)
f.p[] = 9
doAssert f.p[] == 9

block: # type-side compound: U = array[3, T]
type Foo[T; U = array[3, T]] = object
arr: U
var f: Foo[int]
f.arr[0] = 10
f.arr[2] = 30
doAssert f.arr[0] == 10
doAssert f.arr[2] == 30

block: # #4086 original: all params defaulted, invocation with no args
type Foo[T = int] = object
x: T
var f: Foo
f.x = 42
doAssert f.x == 42

block: # alias of a defaulted instantiation
type Foo[T; U = T] = object
a: T
b: U
type IntFoo = Foo[int]
var f: IntFoo
f.a = 1
f.b = 2
doAssert f.b is int

block: # distinct of a defaulted instantiation
type Foo[T; U = seq[T]] = object
data: U
type DistFoo = distinct Foo[int]
var f: DistFoo
Foo[int](f).data.add 7
doAssert Foo[int](f).data == @[7]

block: # union constraint + default referencing T (review request)
type Foo[T; U: seq[T]|Deque[T] = seq[T]] = object
data: U
var f: Foo[int]
f.data.add 42
doAssert f.data is seq[int]
doAssert f.data == @[42]

block: # typeclass constraint + concrete-type default (review request)
type Foo[T: SomeInteger = int] = object
x: T
var f: Foo
f.x = 7
doAssert f.x is int
var g: Foo[int64]
g.x = 9'i64
doAssert g.x is int64

block: # concept constraint + default (review request)
type HasLen = concept x
x.len is int
type Foo[T: HasLen = string] = object
val: T
var f: Foo
f.val = "hi"
doAssert f.val.len == 2
var g: Foo[seq[int]]
g.val = @[1, 2, 3]
doAssert g.val.len == 3
Loading