adds modifierMode parameter to typeof (#25815)

This PR adds 3 modes to `typeof` to specify how to handle type modifiers
`var`, `sink` and `lent`.

- typeOfModCompatible
Remove or keep type modifiers in the same way as old typeof. That means
keep `sink` but remove `var` and `lent`.
- typeOfModRemoveModifier
  Remove type modifiers.
- typeOfModKeepModifier
  Keep type modifiers.

Related to https://github.com/nim-lang/Nim/pull/25779
https://github.com/nim-lang/Nim/issues/25786

(cherry picked from commit 48621c217f)
This commit is contained in:
Tomohiro
2026-06-10 03:55:30 +09:00
committed by narimiran
parent 848188512c
commit db595b397c
6 changed files with 161 additions and 22 deletions

View File

@@ -70,6 +70,10 @@ parameter and result types, not just their source-level shape. Use
Modes include `Nim` (default, fully compatible) and two new experimental modes:
`Lax` and `Gnu` for different option parsing behaviors.
- `std/nre2` is added to replace deprecated NRE.
- `system.typeof` adds a new parameter `modifierMode` to specify how type modifiers are handled.
[//]: # "Changes:"
- `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type.

View File

@@ -106,7 +106,9 @@ proc semExprWithType(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType
proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
result = semExprCheck(c, n, flags)
if result.typ == nil:
if result.typ == nil and efInTypeof in flags:
result.typ = c.voidType
elif result.typ == nil:
localError(c.config, n.info, errExprXHasNoType %
renderTree(result, {renderNoComments}))
result.typ() = errorType(c)

View File

@@ -43,17 +43,8 @@ proc semAddr(c: PContext; n: PNode): PNode =
result.typ() = makePtrType(c, x.typ.skipTypes({tySink}))
proc semTypeOf(c: PContext; n: PNode): PNode =
var m = BiggestInt 1 # typeOfIter
if n.len == 3:
let mode = semConstExpr(c, n[2])
if mode.kind != nkIntLit:
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
else:
m = mode.intVal
let typExpr = semTypeOfImpl(c, n)
result = newNodeI(nkTypeOfExpr, n.info)
inc c.inTypeofContext
defer: dec c.inTypeofContext # compiles can raise an exception
let typExpr = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
result.add typExpr
if typExpr.typ.kind == tyFromExpr:
typExpr.typ.flags.incl tfNonConstExpr

View File

@@ -1949,6 +1949,57 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType =
result.rawAddSon(base)
result.flags.incl tfHasStatic
proc semTypeOfImpl(c: PContext; n: PNode): PNode =
var m = BiggestInt 1 # typeOfIter
var modifierMode = BiggestInt 0 # CompatibleTypeModifiers
type
TypeOfParams = enum
topMode
topModifier
if n.len in 3 .. 4:
for i in 2 ..< n.len:
var argKind = topMode
var arg: PNode = nil
if n[i].kind == nkExprEqExpr and n[i][0].kind == nkIdent:
# named param
case n[i][0].ident.s
of "mode": argKind = topMode
of "modifierMode": argKind = topModifier
else:
localError(c.config, n.info, "typeof: got unknown parameter name")
arg = n[i][1]
else:
if i == 2:
argKind = topMode
else:
argKind = topModifier
arg = n[i]
case argKind
of topMode:
let mode = semConstExpr(c, arg)
if mode.kind != nkIntLit:
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
else:
m = mode.intVal
of topModifier:
let modMode = semConstExpr(c, arg)
if modMode.kind != nkIntLit:
localError(c.config, n.info, "typeof: cannot evaluate 'modifierMode' parameter at compile-time")
else:
modifierMode = modMode.intVal
inc c.inTypeofContext
defer: dec c.inTypeofContext # compiles can raise an exception
var typExpr = semExprNoDeref(c, n[1], if m == 1: {efInTypeof} else: {})
if modifierMode == 0:
# CompatibleTypeModifiers
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent})
elif modifierMode == 1:
# RemoveTypeModifiers
typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent, tySink})
result = typExpr
proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
openScope(c)
inc c.inTypeofContext
@@ -1969,16 +2020,7 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
openScope(c)
var m = BiggestInt 1 # typeOfIter
if n.len == 3:
let mode = semConstExpr(c, n[2])
if mode.kind != nkIntLit:
localError(c.config, n.info, "typeof: cannot evaluate 'mode' parameter at compile-time")
else:
m = mode.intVal
inc c.inTypeofContext
defer: dec c.inTypeofContext # compiles can raise an exception
let ex = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {})
let ex = semTypeOfImpl(c, n)
closeScope(c)
result = ex.typ
if result.kind == tyFromExpr:

View File

@@ -54,7 +54,12 @@ type
typeOfProc, ## Prefer the interpretation that means `x` is a proc call.
typeOfIter ## Prefer the interpretation that means `x` is an iterator call.
proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
TypeOfModifiers* = enum ## Modes to handle type modifiers `var`, `sink` and `lent`.
CompatibleTypeModifiers, ## Remove or keep type modifiers in the same way as old typeof. That means keep `sink` but remove `var` and `lent`.
RemoveTypeModifiers, ## Remove type modifiers.
KeepTypeModifiers, ## Keep type modifiers.
proc typeof*(x: untyped; mode = typeOfIter; modifierMode = CompatibleTypeModifiers): typedesc {.
magic: "TypeOf", noSideEffect, compileTime.} =
## Builtin `typeof` operation for accessing the type of an expression.
## Since version 0.20.0.
@@ -76,6 +81,11 @@ proc typeof*(x: untyped; mode = typeOfIter): typedesc {.
# since `typeOfProc` expects a typed expression and `myFoo2()` can
# only be used in a `for` context.
proc varParam(x: var int;
y: typeof(x, modifierMode = RemoveTypeModifiers);
z: typeof(x, modifierMode = KeepTypeModifiers)) = discard
doAssert varParam is proc (x: var int; y: int; z: var int) {.nimcall.}
proc `or`*(a, b: typedesc): typedesc {.magic: "TypeTrait", noSideEffect.}
## Constructs an `or` meta class.

90
tests/system/ttypeof.nim Normal file
View File

@@ -0,0 +1,90 @@
static: doAssert typeof(1) is int
func isVar[T](x: var T): bool = true
func isVar[T](x: T): bool = false
proc testVarParams1(a: var int;
b: typeof(a);
c: typeof(a, typeOfIter);
d: typeof(a, typeOfIter, CompatibleTypeModifiers);
e: typeof(a, typeOfIter, RemoveTypeModifiers);
f: typeof(a, typeOfIter, KeepTypeModifiers);
g: typeof(a, modifierMode = CompatibleTypeModifiers);
h: typeof(a, modifierMode = RemoveTypeModifiers);
i: typeof(a, modifierMode = KeepTypeModifiers);
) =
doAssert not isVar(b)
doAssert not isVar(c)
doAssert not isVar(d)
doAssert not isVar(e)
doAssert isVar(f)
doAssert not isVar(g)
doAssert not isVar(h)
doAssert isVar(i)
static: doAssert testVarParams1 is proc (a: var int; b: int; c: int; d: int; e: int; f: var int; g: int; h: int; i: var int) {.nimcall.}
block:
var a, f, i: int
testVarParams1(a, 0, 0, 0, 0, f, 0, 0, i)
# `CompatibleTypeModifiers` and `RemoveTypeModifiers` remove only top `var`, not `var` inside proc type
proc testVarParams2(a: var proc(x: var int): var int;
b: typeof(a);
c: typeof(a, modifierMode = CompatibleTypeModifiers);
d: typeof(a, modifierMode = RemoveTypeModifiers);
e: typeof(a, modifierMode = KeepTypeModifiers)) =
doAssert not isVar(b)
doAssert not isVar(c)
doAssert not isVar(d)
doAssert isVar(e)
static: doAssert testVarParams2 is proc (a: var proc(x: var int): var int;
b: proc(x: var int): var int;
c: proc(x: var int): var int;
d: proc(x: var int): var int;
e: var proc(x: var int): var int) {.nimcall.}
block:
var a, e: proc(x: var int): var int = nil
let b, c, d: proc(x: var int): var int = nil
testVarParams2(a, b, c, d, e)
proc testRet(a: var int): typeof(a) = 0
static: doAssert testRet is proc (a: var int): int {.nimcall.}
proc testRet2(a: var int): typeof(a, modifierMode = CompatibleTypeModifiers) = 0
static: doAssert testRet2 is proc (a: var int): int {.nimcall.}
proc testRet3(a: var int): typeof(a, modifierMode = RemoveTypeModifiers) = 0
static: doAssert testRet3 is proc (a: var int): int {.nimcall.}
proc fooSink1(a: sink string;
b: typeof(a);
c: typeof(a, modifierMode = CompatibleTypeModifiers);
d: typeof(a, modifierMode = RemoveTypeModifiers);
e: typeof(a, modifierMode = KeepTypeModifiers)) = discard
static: doAssert fooSink1 is proc (a: sink string; b: sink string; c: sink string; d: string; e: sink string) {.nimcall.}
proc fooLentRet(a: seq[string]): lent string = a[0]
proc testLentRetComp(a: seq[string]): typeof(fooLentRet(a), modifierMode = CompatibleTypeModifiers) = a[0]
proc testLentRetRemo(a: seq[string]): typeof(fooLentRet(a), modifierMode = RemoveTypeModifiers) = a[0]
proc testLentRetKeep(a: seq[string]): typeof(fooLentRet(a), modifierMode = KeepTypeModifiers) = a[0]
# workaround # issue 25830
proc dummyLentProc(a: seq[string]): lent string = a[0]
static:
doAssert testLentRetComp is proc (a: seq[string]): string {.nimcall.}
doAssert testLentRetRemo is proc (a: seq[string]): string {.nimcall.}
doAssert testLentRetKeep is typeof(dummyLentProc)
proc voidProc() = discard
static:
doAssert typeof(voidProc()) is void
type
Foo = typeof(Bar)
Bar = int
static:
doAssert Foo is int