sem generic proc param types like generic types + static instantiation fixes (#24005)

fixes #4228, fixes #4990, fixes #7006, fixes #7008, fixes #8406, fixes
#8551, fixes #11112, fixes #20027, fixes #22647, refs #23854 and #23855
(remaining issue fixed), refs #8545 (works properly now with
`cast[static[bool]]` changed to `cast[bool]`), refs #22342 and #22607
(disabled tests added), succeeds #23194

Parameter and return type nodes in generic procs now undergo the same
`inGenericContext` treatment that nodes in generic type bodies do. This
allows many of the fixes in #22029 and followups to also apply to
generic proc signatures. Like #23983 however this needs some more
compiler fixes, but this time mostly in `sigmatch` and type
instantiations.

1. `tryReadingGenericParam` no longer treats `tyCompositeTypeClass` like
a concrete type anymore, so expressions like `Foo.T` where `Foo` is a
generic type don't look for a parameter of `Foo` in non-generic code
anymore. It also doesn't generate `tyFromExpr` in non-generic code for
any generic LHS. This is to handle a very specific case in `asyncmacro`
which used `FutureVar.astToStr` where `FutureVar` is generic.
2. The `tryResolvingStaticExpr` call when matching `tyFromExpr` in
sigmatch now doesn't consider call nodes in general unresolved, only
nodes with `tyFromExpr` type, which is emitted on unresolved expressions
by increasing `c.inGenericContext`. `c.inGenericContext == 0` is also
now required to attempt instantiating `tyFromExpr`. So matching against
`tyFromExpr` in proc signatures works in general now, but I'm
speculating it depends on constant folding in `semExpr` for statics to
match against it properly.
3. `paramTypesMatch` now doesn't try to change nodes with `tyFromExpr`
type into `tyStatic` type when fitting to a static type, because it
doesn't need to, they'll be handled the same way (this was a workaround
in place of the static type instantiation changes, only one of the
fields in the #22647 test doesn't work with it).
4. `tyStatic` matching now uses `inferStaticParam` instead of just range
type matching, so `Foo[N div 2]` can infer `N` in the same way `array[N
div 2, int]` can. `inferStaticParam` also disabled itself if the
inferred static param type already had a node, but `makeStaticExpr`
generates static types with unresolved nodes, so we only disable it if
it also doesn't have a binding. This might not work very well but the
static type instantiation changes should really lower the amount of
cases where it's encountered.
5. Static types now undergo type instantiation. Previously the branch
for `tyStatic` in `semtypinst` was a no-op, now it acts similarly to
instantiating any other type with the following differences:
- Other types only need instantiation if `containsGenericType` is true,
static types also get instantiated if their value node isn't a literal
node. Ideally any value node that is "already evaluated" should be
ignored, but I'm not sure of a better way to check this, maybe if
`evalConstExpr` emitted a flag. This is purely for optimization though.
- After instantiation, `semConstExpr` is called on the value node if
`not cl.allowMetaTypes` and the type isn't literally a `static` type.
Then the type of the node is set to the base type of the static type to
deal with `semConstExpr` stripping abstract types.
We need to do this because calls like `foo(N)` where `N` is `static int`
and `foo`'s first parameter is just `int` do not generate `tyFromExpr`,
they are fully typed and so `makeStaticExpr` is called on them, giving a
static type with an unresolved node.
This commit is contained in:
metagn
2024-08-26 07:54:38 +03:00
committed by GitHub
parent 09dcff71c8
commit 69ea1336fb
8 changed files with 355 additions and 32 deletions

View File

@@ -1482,20 +1482,28 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode =
proc tryReadingGenericParam(c: PContext, n: PNode, i: PIdent, t: PType): PNode =
case t.kind
of tyTypeParamsHolders:
of tyGenericInst:
result = readTypeParameter(c, t, i, n.info)
if result == c.graph.emptyNode:
result = semGenericStmt(c, n)
result.typ = makeTypeFromExpr(c, result.copyTree)
if c.inGenericContext > 0:
result = semGenericStmt(c, n)
result.typ = makeTypeFromExpr(c, result.copyTree)
else:
result = nil
of tyUserTypeClasses:
if t.isResolvedUserTypeClass:
result = readTypeParameter(c, t, i, n.info)
else:
elif c.inGenericContext > 0:
result = semGenericStmt(c, n)
result.typ = makeTypeFromExpr(c, copyTree(result))
of tyFromExpr, tyGenericParam, tyAnything:
result = semGenericStmt(c, n)
result.typ = makeTypeFromExpr(c, copyTree(result))
else:
result = nil
elif t.containsGenericType:
if c.inGenericContext > 0:
result = semGenericStmt(c, n)
result.typ = makeTypeFromExpr(c, copyTree(result))
else:
result = nil
else:
result = nil

View File

@@ -1312,6 +1312,9 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
result = newProcType(c, n.info, prev)
var check = initIntSet()
var counter = 0
template isCurrentlyGeneric: bool =
# genericParams might update as implicit generic params are added
genericParams != nil and genericParams.len > 0
for i in 1..<n.len:
var a = n[i]
@@ -1332,7 +1335,10 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
hasDefault = a[^1].kind != nkEmpty
if hasType:
let isGeneric = isCurrentlyGeneric()
inc c.inGenericContext, ord(isGeneric)
typ = semParamType(c, a[^2], constraint)
dec c.inGenericContext, ord(isGeneric)
# TODO: Disallow typed/untyped in procs in the compiler/stdlib
if kind in {skProc, skFunc} and (typ.kind == tyTyped or typ.kind == tyUntyped):
if not isMagic(getCurrOwner(c)):
@@ -1353,7 +1359,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
message(c.config, a.info, warnImplicitDefaultValue, msg)
block determineType:
var defTyp = typ
if genericParams != nil and genericParams.len > 0:
if isCurrentlyGeneric():
defTyp = nil
def = semGenericStmt(c, def)
if hasUnresolvedArgs(c, def):
@@ -1432,11 +1438,12 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
onDef(a[j].info, arg)
a[j] = newSymNode(arg)
var r: PType =
if n[0].kind != nkEmpty:
semTypeNode(c, n[0], nil)
else:
nil
var r: PType = nil
if n[0].kind != nkEmpty:
let isGeneric = isCurrentlyGeneric()
inc c.inGenericContext, ord(isGeneric)
r = semTypeNode(c, n[0], nil)
dec c.inGenericContext, ord(isGeneric)
if r != nil and kind in {skMacro, skTemplate} and r.kind == tyTyped:
# XXX: To implement the proposed change in the warning, just
@@ -1489,7 +1496,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode,
result.flags.excl tfHasMeta
result.n.typ = r
if genericParams != nil and genericParams.len > 0:
if isCurrentlyGeneric():
for n in genericParams:
if {sfUsed, sfAnon} * n.sym.flags == {}:
result.flags.incl tfUnresolved

View File

@@ -678,8 +678,31 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType): PType =
elif t.elementType.kind != tyNone:
result = makeTypeDesc(cl.c, replaceTypeVarsT(cl, t.elementType))
of tyUserTypeClass, tyStatic:
of tyUserTypeClass:
result = t
of tyStatic:
if cl.c.matchedConcept != nil:
# allow concepts to not instantiate statics for now
# they can't always infer them
return
if not containsGenericType(t) and (t.n == nil or t.n.kind in nkLiterals):
# no need to instantiate
return
bailout()
result = instCopyType(cl, t)
cl.localCache[t.itemId] = result
for i in FirstGenericParamAt..<result.kidsLen:
var r = result[i]
if r != nil:
r = replaceTypeVarsT(cl, r)
result[i] = r
propagateToOwner(result, r)
result.n = replaceTypeVarsN(cl, result.n)
if not cl.allowMetaTypes and result.n != nil and
result.base.kind != tyNone:
result.n = cl.c.semConstExpr(cl.c, result.n)
result.n.typ = result.base
of tyGenericInst, tyUserTypeClassInst:
bailout()

View File

@@ -885,6 +885,7 @@ proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
allowUnresolved = false,
allowCalls = false,
expectedType: PType = nil): PNode =
# Consider this example:
# type Value[N: static[int]] = object
@@ -894,7 +895,7 @@ proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
# This proc is used to evaluate such static expressions.
let instantiated = replaceTypesInBody(c.c, c.bindings, n, nil,
allowMetaTypes = allowUnresolved)
if instantiated.kind in nkCallKinds:
if not allowCalls and instantiated.kind in nkCallKinds:
return nil
result = c.c.semExpr(c.c, instantiated)
@@ -966,7 +967,8 @@ proc inferStaticParam*(c: var TCandidate, lhs: PNode, rhs: BiggestInt): bool =
else: discard
elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and lhs.typ.n == nil:
elif lhs.kind == nkSym and lhs.typ.kind == tyStatic and
(lhs.typ.n == nil or idTableGet(c.bindings, lhs.typ) == nil):
var inferred = newTypeS(tyStatic, c.c, lhs.typ.elementType)
inferred.n = newIntNode(nkIntLit, rhs)
put(c, lhs.typ, inferred)
@@ -1877,7 +1879,11 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
elif f.base.kind notin {tyNone, tyGenericParam}:
result = typeRel(c, f.base, a, flags)
if result != isNone and f.n != nil:
if not exprStructuralEquivalent(f.n, aOrig.n):
var r = tryResolvingStaticExpr(c, f.n)
if r == nil: r = f.n
if not exprStructuralEquivalent(r, aOrig.n) and
not (aOrig.n.kind == nkIntLit and
inferStaticParam(c, r, aOrig.n.intVal)):
result = isNone
elif f.base.kind == tyGenericParam:
# Handling things like `type A[T; Y: static T] = object`
@@ -1963,23 +1969,29 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
of tyFromExpr:
# fix the expression, so it contains the already instantiated types
if f.n == nil or f.n.kind == nkEmpty: return isGeneric
let reevaluated = tryResolvingStaticExpr(c, f.n)
if reevaluated == nil:
if c.c.inGenericContext > 0:
# need to delay until instantiation
# also prevent infinite recursion below
return isNone
inc c.c.inGenericContext # to generate tyFromExpr again if unresolved
let reevaluated = tryResolvingStaticExpr(c, f.n, allowCalls = true).typ
dec c.c.inGenericContext
case reevaluated.kind
of tyFromExpr:
# not resolved
result = isNone
return
case reevaluated.typ.kind
of tyTypeDesc:
result = typeRel(c, a, reevaluated.typ.base, flags)
result = typeRel(c, a, reevaluated.base, flags)
of tyStatic:
result = typeRel(c, a, reevaluated.typ.base, flags)
if result != isNone and reevaluated.typ.n != nil:
if not exprStructuralEquivalent(aOrig.n, reevaluated.typ.n):
result = typeRel(c, a, reevaluated.base, flags)
if result != isNone and reevaluated.n != nil:
if not exprStructuralEquivalent(aOrig.n, reevaluated.n):
result = isNone
else:
# bug #14136: other types are just like 'tyStatic' here:
result = typeRel(c, a, reevaluated.typ, flags)
if result != isNone and reevaluated.typ.n != nil:
if not exprStructuralEquivalent(aOrig.n, reevaluated.typ.n):
result = typeRel(c, a, reevaluated, flags)
if result != isNone and reevaluated.n != nil:
if not exprStructuralEquivalent(aOrig.n, reevaluated.n):
result = isNone
of tyNone:
if a.kind == tyNone: result = isEqual
@@ -2188,7 +2200,11 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
a = typ
else:
if m.callee.kind == tyGenericBody:
if f.kind == tyStatic and typeRel(m, f.base, a) != isNone:
# we can't use `makeStaticExpr` if `arg` has a generic type
# because it generates `tyStatic`, which semtypinst doesn't touch
# not sure if checking for `tyFromExpr` is enough
if f.kind == tyStatic and typeRel(m, f.base, a) != isNone and
a.kind != tyFromExpr:
result = makeStaticExpr(m.c, arg)
result.typ.flags.incl tfUnresolved
result.typ.n = arg

View File

@@ -63,7 +63,8 @@ template getBits[bits: static int](x: ptr UncheckedArray[BigInt[bits]]): int = b
proc main() =
let ctx = ECFFT_Descriptor[EC_ShortW_Aff[Fp[BLS12_381]]].new()
when false: echo getBits(ctx.rootsOfUnity2) # doesn't work yet?
doAssert getBits(ctx.rootsOfUnity1) == 255
doAssert getBits(ctx.rootsOfUnity2) == 255
doAssert ctx.rootsOfUnity1[0].limbs.len == wordsRequired(255)
doAssert ctx.rootsOfUnity2[0].limbs.len == wordsRequired(255)

View File

@@ -55,7 +55,7 @@ template getBits[bits: static int](x: ptr UncheckedArray[BigInt[bits]]): int = b
proc main() =
let ctx = ECFFT_Descriptor[EC_ShortW_Aff[Fp[BLS12_381]]].new()
when false: echo getBits(ctx.rootsOfUnity) # doesn't work yet?
doAssert getBits(ctx.rootsOfUnity) == 255
doAssert ctx.rootsOfUnity[0].limbs.len == wordsRequired(255)
main()

View File

@@ -239,3 +239,65 @@ block: # version of #23432 with `typed`, don't delay instantiation
proc f(x: X) = discard
var v: Future[void].Raising([ValueError])
f(v)
block: # issue #22647
proc c0(n: static int): int = 8
proc c1(n: static int): int = n div 2
proc c2(n: static int): int = n * 2
proc c3(n: static int, n2: int): int = n * n2
proc `**`(n: static int, n2: int): int = n * n2
proc c4(n: int, n2: int): int = n * n2
type
a[N: static int] = object
f0 : array[N, int]
b[N: static int] = object
f0 : a[c0(N)] # does not work
f1 : a[c1(N)] # does not work
f2 : a[c2(N)] # does not work
f3 : a[N * 2] # does not work
f4 : a[N] # works
f5: a[c3(N, 2)]
f6: a[N ** 2]
f7: a[2 * N]
f8: a[c4(N, 2)]
proc p[N: static int](x : a[N]) = discard x.f0[0]
template check(x, s: untyped) =
p(x)
doAssert x is a[s]
doAssert x.N == s
doAssert typeof(x).N == s
doAssert x.f0 == default(array[s, int])
doAssert x.f0.len == s
proc p2[N: static int](y : a[N]) {.gensym.} =
doAssert y is a[s]
doAssert y.N == s
doAssert typeof(y).N == s
doAssert y.f0 == default(array[s, int])
doAssert y.f0.len == s
p2(x)
proc p3(z: typeof(x)) {.gensym.} = discard
p3(default(a[s]))
proc p[N: static int](x : b[N]) =
x.f0.check(8)
x.f1.check(2)
x.f2.check(8)
x.f3.check(8)
x.f4.check(4)
x.f5.check(8)
x.f6.check(8)
x.f7.check(8)
x.f8.check(8)
var x: b[4]
x.p()
when false: # issue #22342, type section version of #22607
type GenAlias[isInt: static bool] = (
when isInt:
int
else:
float
)

View File

@@ -0,0 +1,206 @@
block: # issue #4228
template seqType(t: typedesc): typedesc =
when t is int:
seq[int]
else:
seq[string]
proc mkSeq[T: int|string](v: T): seqType(T) =
result = newSeq[T](1)
result[0] = v
doAssert mkSeq("a") == @["a"]
doAssert mkSeq(1) == @[1]
block: # expanded version of t8545
template bar(a: static[bool]): untyped =
when a:
int
else:
float
proc main() =
proc foo1(a: static[bool]): auto = 1
doAssert foo1(true) == 1
proc foo2(a: static[bool]): bar(a) = 1
doAssert foo2(true) == 1
doAssert foo2(true) is int
doAssert foo2(false) == 1.0
doAssert foo2(false) is float
proc foo3(a: static[bool]): bar(cast[bool](a)) = 1
doAssert foo3(true) == 1
doAssert foo3(true) is int
doAssert foo3(false) == 1.0
doAssert foo3(false) is float
proc foo4(a: static[bool]): bar(static(a)) = 1
doAssert foo4(true) == 1
doAssert foo4(true) is int
doAssert foo4(false) == 1.0
doAssert foo4(false) is float
static: main()
main()
block: # issue #8406
macro f(x: static[int]): untyped = discard
proc g[X: static[int]](v: f(X)) = discard
import macros
block: # issue #8551
macro distinctBase2(T: typedesc): untyped =
let typeNode = getTypeImpl(T)
expectKind(typeNode, nnkBracketExpr)
if typeNode[0].typeKind != ntyTypeDesc:
error "expected typeDesc, got " & $typeNode[0]
var typeSym = typeNode[1]
typeSym = getTypeImpl(typeSym)
if typeSym.typeKind != ntyDistinct:
error "type is not distinct: " & $typeSym.typeKind
typeSym = typeSym[0]
typeSym
func distinctBase[T](a: T): distinctBase2(T) = distinctBase2(T)(a)
type T = distinct int
doAssert distinctBase(T(0)) is int
block:
type Foo[T] = object
x: T
proc foo(x: Foo): Foo[x.T] =
doAssert typeof(result) is typeof(x)
var a: Foo[int]
let b: Foo[int] = foo(a)
doAssert b.x is int
block:
type Foo[T: static int] = object
x: array[T, int]
proc double(x: int): int = x * 2
proc foo[T: static int](x: Foo[T]): Foo[T.double] =
doAssert typeof(result).T == double(typeof(x).T)
var a: Foo[3]
let b: Foo[6] = foo(a)
doAssert $typeof(foo(a)) == "Foo[6]"
block:
type Foo[T: static int] = object
x: array[T, int]
proc foo(x: Foo): Foo[x.T] =
doAssert typeof(result).T == typeof(x).T
doAssert typeof(result) is typeof(x)
var a: Foo[3]
let b: Foo[3] = foo(a)
doAssert $typeof(foo(a)) == "Foo[3]"
block: # issue #7006
type
Node[T] = object
val: T
next: ref Node[T]
HHSet[T, Key] = object
data: seq[Node[T]]
proc rawGet(hhs:HHSet; key: hhs.Key): ptr Node[hhs.T] =
return nil # body doesn't matter
var hhs: HHSet[string, cstring]
discard hhs.rawGet("hello".cstring)
block: # issue #7008
type Node[T] = object
val: T
# Compiles fine
proc concreteProc(s: Node[cstring]; key: s.T) = discard
# Also fine
proc implicitGenericProc1(s: Node; key: s.T) = discard
# still fine
proc explicitGenericProc1[T](s: Node[T]; key: T) = discard
# Internal Compiler Error!
proc explicitGenericProc2[T](s: Node[T]; key: s.T) = discard
let n = Node[int](val: 5)
implicitGenericProc1(n, 5) # works
explicitGenericProc1(n, 5) # works
explicitGenericProc2(n, 5) # doesn't
block: # issue #20027
block:
type Test[T] = object
proc run(self: Test): self.T = discard
discard run(Test[int]())
block:
type Test[T] = object
proc run[T](self: Test[T]): self.T = discard
discard run(Test[int]())
block:
type Test[T] = object
proc run(self: Test[auto]): self.T = discard
discard run(Test[int]())
block: # issue #11112
proc foo[A, B]: type(A.default + B.default) =
discard
doAssert foo[int, int]() is int
block: # tyStatic and tyFromExpr instantiation mid-match
proc bar(x: int): int = x * 3
proc bar2(x: static int): int = x * 4
type Foo[T: static int] = distinct array[T, int]
proc foo[T: static int](x: Foo[T], y: Foo[bar(T)]) = discard
proc foo2[T: static int](x: Foo[T], y: Foo[bar2(T)]) = discard
foo(Foo[1]([1]), Foo[3]([1, 2, 3]))
foo2(Foo[1]([1]), Foo[4]([1, 2, 3, 4]))
block: # issue #4990
type Foo[I: static[int], A: static[array[I, int]]] = object
curIndex: int
proc next[I: static[int], A: static[array[I, int]]](f: Foo[I, A]): string =
discard
const arr = [1, 2, 3]
var f: Foo[arr.len, arr]
discard next(f)
block: # issue #4990 comment
type
Foo[A: static[int], B: static[int], TokenType: enum, EofToken: static[TokenType]] = object
curIndex: int
MyEnum = enum
meA, meB
Bar = Foo[2, 3, MyEnum, meA]
proc next[A: static[int], B: static[int], TokenType: enum,
EofToken: static[TokenType]](f: Foo[A, B, TokenType, EofToken],
a: static[(array[A, int], array[B, int])]): TokenType =
TokenType(a[0][f.curIndex])
const
a = [1, 2]
b = [3, 4, 5]
template next(bar: Bar): MyEnum =
next(Foo[2, 3, MyEnum, meA](bar), (a, b))
let bar = Bar(curIndex: 0)
doAssert bar.next() == meB
when false: # issue #22607, needs nkWhenStmt to be handled like nkRecWhen
proc test[x: static bool](
t: (
when x:
int
else:
float
)
) = discard
test[true](1.int)
test[false](1.0)
doAssert not compiles(test[])