diff --git a/changelog.md b/changelog.md index af91b49773b8d..a9cefb9bf7736 100644 --- a/changelog.md +++ b/changelog.md @@ -72,6 +72,8 @@ parameter and result types, not just their source-level shape. Use - `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. diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index aa0489cd225f1..3712109bcb818 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 397c08bf67c57..f5bf97c58019a 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -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.incl tfNonConstExpr diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 8009f7293c61e..6ad90f186b2a2 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -2063,6 +2063,57 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType = result.rawAddSon(base) result.incl tfHasStatic +proc semTypeOfImpl(c: PContext; n: PNode): PNode = + var m = BiggestInt 1 # typeOfIter + var modifierMode = BiggestInt 0 # typeOfModCompatible + 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: + # typeOfModCompatible + typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent}) + elif modifierMode == 1: + # typeOfModRemoveModifier + typExpr.typ = typExpr.typ.skipTypes({tyVar, tyLent, tySink}) + + result = typExpr + proc semTypeOf(c: PContext; n: PNode; prev: PType): PType = openScope(c) inc c.inTypeofContext @@ -2083,16 +2134,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: diff --git a/lib/system.nim b/lib/system.nim index 26232971bd96e..6ca8f1f34ad53 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -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 {. + TypeOfModifierMode* = enum ## Modes 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. + +proc typeof*(x: untyped; mode = typeOfIter; modifierMode = typeOfModCompatible): 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 = typeOfModRemoveModifier); + z: typeof(x, modifierMode = typeOfModKeepModifier)) = 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. diff --git a/tests/system/ttypeof.nim b/tests/system/ttypeof.nim new file mode 100644 index 0000000000000..c941a5915f1fc --- /dev/null +++ b/tests/system/ttypeof.nim @@ -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, typeOfModCompatible); + e: typeof(a, typeOfIter, typeOfModRemoveModifier); + f: typeof(a, typeOfIter, typeOfModKeepModifier); + g: typeof(a, modifierMode = typeOfModCompatible); + h: typeof(a, modifierMode = typeOfModRemoveModifier); + i: typeof(a, modifierMode = typeOfModKeepModifier); + ) = + 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) + +# `typeOfModCompatible` and `typeOfModRemoveModifier` 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 = typeOfModCompatible); + d: typeof(a, modifierMode = typeOfModRemoveModifier); + e: typeof(a, modifierMode = typeOfModKeepModifier)) = + 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 = typeOfModCompatible) = 0 +static: doAssert testRet2 is proc (a: var int): int {.nimcall.} +proc testRet3(a: var int): typeof(a, modifierMode = typeOfModRemoveModifier) = 0 +static: doAssert testRet3 is proc (a: var int): int {.nimcall.} + +proc fooSink1(a: sink string; + b: typeof(a); + c: typeof(a, modifierMode = typeOfModCompatible); + d: typeof(a, modifierMode = typeOfModRemoveModifier); + e: typeof(a, modifierMode = typeOfModKeepModifier)) = 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 = typeOfModCompatible) = a[0] +proc testLentRetRemo(a: seq[string]): typeof(fooLentRet(a), modifierMode = typeOfModRemoveModifier) = a[0] +proc testLentRetKeep(a: seq[string]): typeof(fooLentRet(a), modifierMode = typeOfModKeepModifier) = 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