Skip to content

fixes #4086, #9355; allow generic param defaults to reference other type params#25799

Open
puffball1567 wants to merge 2 commits into
nim-lang:develfrom
puffball1567:fix-generic-type-param-default-deps
Open

fixes #4086, #9355; allow generic param defaults to reference other type params#25799
puffball1567 wants to merge 2 commits into
nim-lang:develfrom
puffball1567:fix-generic-type-param-default-deps

Conversation

@puffball1567
Copy link
Copy Markdown
Contributor

Generic type parameter defaults that reference other type parameters
currently fail in Nim, even though C++ / TypeScript / Rust / Scala all
support the pattern. This change enables four related sub-cases:

  1. type-side reference: type Foo[T; U = seq[T]]
    (also U = ref T, U = array[3, T], alias, distinct, cascade)
  2. type-side direct: type Foo[T; U = T]
  3. type-side 0-arg invocation: var f: Foo for type Foo[T = int]
  4. proc-side: func foo[T](U = T): U and func foo[T](U: type = T): U

Implementation

compiler/semtypes.nim

  • semGeneric: accumulate already-resolved generic params into a
    layered type map and substitute them in subsequent default values
    before they are added to the invocation. Handles cascade defaults
    like [T; U = seq[T]; V = seq[U]] left-to-right. Gated on
    hasAnyDefault so generic types with no defaults pay no overhead.
  • semProcTypeNode: treat a bare generic-param default (U = T) as
    an unbound typedesc parameter, equivalent to U: type = T, so the
    existing typedesc-param machinery handles it.
  • tryGenericBodyDefaultInvocation: helper that synthesizes a
    Foo[default1, default2, ...] invocation when every generic param
    has a default, and dispatches to semGeneric.

compiler/semstmts.nim

  • semVarOrLet / semConst: after type resolution, if the resulting
    type is a tyGenericBody whose every param has a default, expand
    it via tryGenericBodyDefaultInvocation. Restricted to var/let/
    const so type-level computations like arity(SomeGeneric) in
    template/typetraits contexts keep treating bare generic-body
    references as intended.

compiler/sigmatch.nim

  • matches default-completion: when the default value of a typedesc
    param references generic params, substitute via prepareTypesInBody
    and wrap the resolved type as tyTypeDesc so the implicit
    tfImplicitTypeParam binding path treats it like a literal type
    default. Skipped when nfDefaultRefsParam is set so defaults that
    reference value parameters (e.g. idx = arr.high) keep using
    the existing param-substitution machinery.

Tests

tests/generics/tgeneric_param_default_deps.nim covers all four
sub-cases and the cascade / alias / distinct variants.

Local regression on tests/{generic,generics,objects,typerel,types, metatype,statictypes,concepts,proc,overload,template,macros,tuples, collections,distinct,varstmt,let,varres,assign,init}: 0 new failures
across 663 tests (one pre-existing tforwardcycletimeout reTimeout
reproduces on clean upstream/devel and is unrelated).

closes #4086
closes #9355

@puffball1567 puffball1567 marked this pull request as draft May 8, 2026 01:57
…eference other type params

Generic type parameter defaults that reference other type parameters
currently fail in Nim, even though C++ / TypeScript / Rust / Scala all
support the pattern. This change enables four related sub-cases:

1. type-side reference: `type Foo[T; U = seq[T]]`
   (also `U = ref T`, `U = array[3, T]`, alias, distinct, cascade)
2. type-side direct: `type Foo[T; U = T]`
3. type-side 0-arg invocation: `var f: Foo` for `type Foo[T = int]`
4. proc-side: `func foo[T](U = T): U` and `func foo[T](U: type = T): U`

Implementation
--------------
compiler/semtypes.nim
  - semGeneric: accumulate already-resolved generic params into a
    layered type map and substitute them in subsequent default values
    before they are added to the invocation. Handles cascade defaults
    like `[T; U = seq[T]; V = seq[U]]` left-to-right. Gated on
    `hasAnyDefault` so generic types with no defaults pay no overhead.
  - semProcTypeNode: treat a bare generic-param default (`U = T`) as
    an unbound typedesc parameter, equivalent to `U: type = T`, so the
    existing typedesc-param machinery handles it.
  - tryGenericBodyDefaultInvocation: helper that synthesizes a
    `Foo[default1, default2, ...]` invocation when every generic param
    has a default, and dispatches to semGeneric.

compiler/semstmts.nim
  - semVarOrLet / semConst: after type resolution, if the resulting
    type is a tyGenericBody whose every param has a default, expand
    it via tryGenericBodyDefaultInvocation. Restricted to var/let/
    const so type-level computations like `arity(SomeGeneric)` in
    template/typetraits contexts keep treating bare generic-body
    references as intended.

compiler/sigmatch.nim
  - matches default-completion: when the default value is a bare
    reference to an earlier generic type param (recognised by the
    default's static type being `tyGenericParam`), substitute T
    against the explicit-instantiation bindings via prepareTypesInBody
    and wrap the result as `tyTypeDesc` so the implicit
    `tfImplicitTypeParam` binding path treats it like a literal type
    default. Defaults whose type is anything else — value expressions
    like `arr.high`, proc-call expressions like `newTensor[T](0)` whose
    result type is `tyGenericInvocation`/`tyGenericInst`, etc. — are
    left untouched, so the existing default-handling machinery (and
    later sem stages) continue to handle them as before. Verified by
    locally building tests/test_indep_import.nim from arraymancer
    against ~/.nimble pkgs2.

Tests
-----
tests/generics/tgeneric_param_default_deps.nim covers all four
sub-cases and the cascade / alias / distinct variants. Local
regression on tests/{generic,generics,objects,typerel,types,
metatype,statictypes,concepts,proc,overload,template,macros,tuples,
collections,distinct,varstmt,let,varres,assign,init}: 663 reSuccess
across 19 categories. The single non-success entry,
tests/types/tforwardcycletimeout reTimeout, reproduces on clean
upstream/devel and is unrelated.

closes nim-lang#4086
closes nim-lang#9355
@puffball1567 puffball1567 force-pushed the fix-generic-type-param-default-deps branch from dade56f to 18f250e Compare May 8, 2026 07:21
@puffball1567 puffball1567 marked this pull request as ready for review May 8, 2026 19:28
@puffball1567
Copy link
Copy Markdown
Contributor Author

The Azure Pipelines nim-lang.Nim (packages OSX_arm64_cpp) job hit the 90-minute agent timeout and was cancelled mid-koch, Run CI — not a code-side failure. All other Azure jobs (Linux / Windows / OSX without cpp / etc.) and every GitHub Actions run are green.

Could a maintainer kick off a re-run of just that one job?

cc @Araq @ringabout for review when convenient — generic-param-default fix (4 files, +199/-2). Closes #4086 and #9355.

@mratsim
Copy link
Copy Markdown
Collaborator

mratsim commented May 9, 2026

For 1, 2, 3, we would also need to test this kind of syntax:

type Foo[T; U: seq[T]|Deque[T] = seq[T]]

type Foo[T: SomeInteger = int]

And probably interation with concepts as well.

Note that the fact that higher kinded types actually work is an accident and the original RFC was auto-closed as not planned: nim-lang/RFCs#5

…g#9355

Adds three review-requested tests to tgeneric_param_default_deps.nim
covering generic param defaults in combination with:

  1. union constraint:    type Foo[T; U: seq[T]|Deque[T] = seq[T]]
  2. typeclass constraint: type Foo[T: SomeInteger = int]
  3. concept constraint:   type Foo[T: HasLen = string]

These exercise the default-completion machinery added in 18f250e
across constraint kinds adjacent to higher-kinded-type accidental
behavior (RFCs#5). All three pass under both --mm:orc (default) and
--mm:refc.

Local regression on tests/concepts: 45/46 reSuccess, 0 reFail
(1 reDisabled is unrelated). Local regression on tests/generics:
116/116 reSuccess, 0 reFail.
@puffball1567
Copy link
Copy Markdown
Contributor Author

@mratsim Thanks. Pushed 305c731b8 with three test blocks for the syntaxes you flagged:

  1. union: type Foo[T; U: seq[T]|Deque[T] = seq[T]]
  2. typeclass: type Foo[T: SomeInteger = int]
  3. concept: type Foo[T: HasLen = string]

All three pass on --mm:orc (default) and --mm:refc. Local regression: tests/concepts 45/46 (0 reFail, 1 reDisabled unrelated), tests/generics 116/116 (0 reFail).

On HKT (RFCs#5): the patch is scoped to default-completion only. The guards (tyGenericParam in sigmatch; nfDefaultParam + containsGenericType in semGeneric; tyTypeDesc or tyGenericParam in semProcTypeNode; tyGenericBody + every-param-has-default in the semstmts/semGeneric body-invocation helper) keep tyGenericInvocation/tyGenericInst paths untouched, so accidental HKT behavior should be preserved. If you have a specific HKT pattern from constantine / arraymancer you'd like me to add as a regression check, happy to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

type argument's default value can't depend on a generic (+ weird error messages) default type parameters

2 participants