fixes #4086, #9355; allow generic param defaults to reference other type params#25799
fixes #4086, #9355; allow generic param defaults to reference other type params#25799puffball1567 wants to merge 2 commits into
Conversation
…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
dade56f to
18f250e
Compare
|
The Azure Pipelines 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. |
|
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.
|
@mratsim Thanks. Pushed
All three pass on On HKT (RFCs#5): the patch is scoped to default-completion only. The guards ( |
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:
type Foo[T; U = seq[T]](also
U = ref T,U = array[3, T], alias, distinct, cascade)type Foo[T; U = T]var f: Foofortype Foo[T = int]func foo[T](U = T): Uandfunc foo[T](U: type = T): UImplementation
compiler/semtypes.nimsemGeneric: accumulate already-resolved generic params into alayered 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 onhasAnyDefaultso generic types with no defaults pay no overhead.semProcTypeNode: treat a bare generic-param default (U = T) asan unbound typedesc parameter, equivalent to
U: type = T, so theexisting typedesc-param machinery handles it.
tryGenericBodyDefaultInvocation: helper that synthesizes aFoo[default1, default2, ...]invocation when every generic paramhas a default, and dispatches to
semGeneric.compiler/semstmts.nimsemVarOrLet/semConst: after type resolution, if the resultingtype is a
tyGenericBodywhose every param has a default, expandit via
tryGenericBodyDefaultInvocation. Restricted to var/let/const so type-level computations like
arity(SomeGeneric)intemplate/typetraits contexts keep treating bare generic-body
references as intended.
compiler/sigmatch.nimmatchesdefault-completion: when the default value of a typedescparam references generic params, substitute via
prepareTypesInBodyand wrap the resolved type as
tyTypeDescso the implicittfImplicitTypeParambinding path treats it like a literal typedefault. Skipped when
nfDefaultRefsParamis set so defaults thatreference value parameters (e.g.
idx = arr.high) keep usingthe existing param-substitution machinery.
Tests
tests/generics/tgeneric_param_default_deps.nimcovers all foursub-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 failuresacross 663 tests (one pre-existing
tforwardcycletimeoutreTimeoutreproduces on clean upstream/devel and is unrelated).
closes #4086
closes #9355