diff --git a/changelog.md b/changelog.md index fbf7e46ad593a..fcaba5d1d382d 100644 --- a/changelog.md +++ b/changelog.md @@ -192,9 +192,33 @@ - Full command syntax and block arguments i.e. `foo a, b: c` are now allowed for the right-hand side of type definitions in type sections. Previously they would error with "invalid indentation". -- `defined` now accepts identifiers separated by dots, i.e. `defined(a.b.c)`. - In the command line, this is defined as `-d:a.b.c`. Older versions can - use accents as in ``defined(`a.b.c`)`` to access such defines. + +- Compile-time define changes: + - `defined` now accepts identifiers separated by dots, i.e. `defined(a.b.c)`. + In the command line, this is defined as `-d:a.b.c`. Older versions can + use accents as in ``defined(`a.b.c`)`` to access such defines. + - [Define pragmas for constants](https://nim-lang.github.io/Nim/manual.html#implementation-specific-pragmas-compileminustime-define-pragmas) + now support a string argument for qualified define names. + + ```nim + # -d:package.FooBar=42 + const FooBar {.intdefine: "package.FooBar".}: int = 5 + echo FooBar # 42 + ``` + + This was added to help disambiguate similar define names for different packages. + In older versions, this could only be achieved with something like the following: + + ```nim + const FooBar = block: + const `package.FooBar` {.intdefine.}: int = 5 + `package.FooBar` + ``` + - A generic `define` pragma for constants has been added that interprets + the value of the define based on the type of the constant value. + See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#generic-define-pragma) + for a list of supported types. + - [Macro pragmas](https://nim-lang.github.io/Nim/manual.html#userminusdefined-pragmas-macro-pragmas) changes: - Templates now accept macro pragmas. - Macro pragmas for var/let/const sections have been redesigned in a way that works diff --git a/compiler/ast.nim b/compiler/ast.nim index 9679c10c12791..01126323086e2 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -712,7 +712,7 @@ type mEqIdent, mEqNimrodNode, mSameNodeType, mGetImpl, mNGenSym, mNHint, mNWarning, mNError, mInstantiationInfo, mGetTypeInfo, mGetTypeInfoV2, - mNimvm, mIntDefine, mStrDefine, mBoolDefine, mRunnableExamples, + mNimvm, mIntDefine, mStrDefine, mBoolDefine, mGenericDefine, mRunnableExamples, mException, mBuiltinType, mSymOwner, mUncheckedArray, mGetImplTransf, mSymIsInstantiationOf, mNodeId, mPrivateAccess, mZeroDefault diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 182e49bf8c1b9..41b4c83aaa7e0 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -149,3 +149,5 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasOutParams") defineSymbol("nimHasSystemRaisesDefect") defineSymbol("nimHasWarnUnnamedBreak") + defineSymbol("nimHasGenericDefine") + defineSymbol("nimHasDefineAliases") diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index b60331b2993cc..993e6edce203d 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -81,7 +81,8 @@ const wGuard, wGoto, wCursor, wNoalias, wAlign} constPragmas* = declPragmas + {wHeader, wMagic, wGensym, wInject, - wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore} + wIntDefine, wStrDefine, wBoolDefine, wDefine, + wCompilerProc, wCore} paramPragmas* = {wNoalias, wInject, wGensym} letPragmas* = varPragmas procTypePragmas* = {FirstCallConv..LastCallConv, wVarargs, wNoSideEffect, @@ -476,8 +477,16 @@ proc processPop(c: PContext, n: PNode) = when defined(debugOptions): echo c.config $ n.info, " POP config is now ", c.config.options -proc processDefine(c: PContext, n: PNode) = - if (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): +proc processDefineConst(c: PContext, n: PNode, sym: PSym, kind: TMagic) = + sym.magic = kind + if n.kind in nkPragmaCallKinds and n.len == 2: + # could also use TLib + n[1] = getStrLitNode(c, n) + +proc processDefine(c: PContext, n: PNode, sym: PSym) = + if sym != nil and sym.kind == skConst: + processDefineConst(c, n, sym, mGenericDefine) + elif (n.kind in nkPragmaCallKinds and n.len == 2) and (n[1].kind == nkIdent): defineSymbol(c.config.symbols, n[1].ident.s) else: invalidPragma(c, n) @@ -1066,7 +1075,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, recordPragma(c, it, "error", s) localError(c.config, it.info, errUser, s) of wFatal: fatal(c.config, it.info, expectStrLit(c, it)) - of wDefine: processDefine(c, it) + of wDefine: processDefine(c, it, sym) of wUndef: processUndef(c, it) of wCompile: processCompile(c, it) of wLink: processLink(c, it) @@ -1213,11 +1222,11 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, noVal(c, it) sym.flags.incl sfBase of wIntDefine: - sym.magic = mIntDefine + processDefineConst(c, n, sym, mIntDefine) of wStrDefine: - sym.magic = mStrDefine + processDefineConst(c, n, sym, mStrDefine) of wBoolDefine: - sym.magic = mBoolDefine + processDefineConst(c, n, sym, mBoolDefine) of wUsed: noVal(c, it) if sym == nil: invalidPragma(c, it) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 9e45e3b2d2742..7c57eb3706719 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -13,7 +13,7 @@ import strutils, options, ast, trees, nimsets, platform, math, msgs, idents, renderer, types, - commands, magicsys, modulegraphs, strtabs, lineinfos + commands, magicsys, modulegraphs, strtabs, lineinfos, wordrecg from system/memory import nimCStrLen @@ -371,6 +371,11 @@ proc rangeCheck(n: PNode, value: Int128; g: ModuleGraph) = localError(g.config, n.info, "cannot convert " & $value & " to " & typeToString(n.typ)) +proc floatRangeCheck(n: PNode, value: BiggestFloat; g: ModuleGraph) = + if value < firstFloat(n.typ) or value > lastFloat(n.typ): + localError(g.config, n.info, "cannot convert " & $value & + " to " & typeToString(n.typ)) + proc foldConv(n, a: PNode; idgen: IdGenerator; g: ModuleGraph; check = false): PNode = let dstTyp = skipTypes(n.typ, abstractRange - {tyTypeDesc}) let srcTyp = skipTypes(a.typ, abstractRange - {tyTypeDesc}) @@ -490,6 +495,81 @@ proc newSymNodeTypeDesc*(s: PSym; idgen: IdGenerator; info: TLineInfo): PNode = else: result.typ = s.typ +proc foldDefine(m, s: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = + var name = s.name.s + let prag = extractPragma(s) + if prag != nil: + for it in prag: + if it.kind in nkPragmaCallKinds and it.len == 2 and it[0].kind == nkIdent: + let word = whichKeyword(it[0].ident) + if word in {wStrDefine, wIntDefine, wBoolDefine, wDefine}: + # should be processed in pragmas.nim already + if it[1].kind in {nkStrLit, nkRStrLit, nkTripleStrLit}: + name = it[1].strVal + if isDefined(g.config, name): + let str = g.config.symbols[name] + case s.magic + of mIntDefine: + try: + result = newIntNodeT(toInt128(str.parseInt), n, idgen, g) + except ValueError: + localError(g.config, s.info, + "{.intdefine.} const was set to an invalid integer: '" & + str & "'") + of mStrDefine: + result = newStrNodeT(str, n, g) + of mBoolDefine: + try: + result = newIntNodeT(toInt128(str.parseBool.int), n, idgen, g) + except ValueError: + localError(g.config, s.info, + "{.booldefine.} const was set to an invalid bool: '" & + str & "'") + of mGenericDefine: + let rawTyp = s.typ + # pretend we don't support distinct types + let typ = rawTyp.skipTypes(abstractVarRange-{tyDistinct}) + try: + template intNode(value): PNode = + let val = toInt128(value) + rangeCheck(n, val, g) + newIntNodeT(val, n, idgen, g) + case typ.kind + of tyString, tyCstring: + result = newStrNodeT(str, n, g) + of tyInt..tyInt64: + result = intNode(str.parseBiggestInt) + of tyUInt..tyUInt64: + result = intNode(str.parseBiggestUInt) + of tyBool: + result = intNode(str.parseBool.int) + of tyEnum: + # compile time parseEnum + let ident = getIdent(g.cache, str) + for e in typ.n: + if e.kind != nkSym: internalError(g.config, "foldDefine for enum") + let es = e.sym + let match = + if es.ast.isNil: + es.name.id == ident.id + else: + es.ast.strVal == str + if match: + result = intNode(es.position) + break + if result.isNil: + raise newException(ValueError, "invalid enum value: " & str) + else: + localError(g.config, s.info, "unsupported type $1 for define '$2'" % + [name, typeToString(rawTyp)]) + except ValueError as e: + localError(g.config, s.info, + "could not process define '$1' of type $2; $3" % + [name, typeToString(rawTyp), e.msg]) + else: result = copyTree(s.astdef) # unreachable + else: + result = copyTree(s.astdef) + proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = result = nil case n.kind @@ -509,31 +589,8 @@ proc getConstExpr(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode of mBuildOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.hostOS].name), n, g) of mBuildCPU: result = newStrNodeT(platform.CPU[g.config.target.hostCPU].name.toLowerAscii, n, g) of mAppType: result = getAppType(n, g) - of mIntDefine: - if isDefined(g.config, s.name.s): - try: - result = newIntNodeT(toInt128(g.config.symbols[s.name.s].parseInt), n, idgen, g) - except ValueError: - localError(g.config, s.info, - "{.intdefine.} const was set to an invalid integer: '" & - g.config.symbols[s.name.s] & "'") - else: - result = copyTree(s.astdef) - of mStrDefine: - if isDefined(g.config, s.name.s): - result = newStrNodeT(g.config.symbols[s.name.s], n, g) - else: - result = copyTree(s.astdef) - of mBoolDefine: - if isDefined(g.config, s.name.s): - try: - result = newIntNodeT(toInt128(g.config.symbols[s.name.s].parseBool.int), n, idgen, g) - except ValueError: - localError(g.config, s.info, - "{.booldefine.} const was set to an invalid bool: '" & - g.config.symbols[s.name.s] & "'") - else: - result = copyTree(s.astdef) + of mIntDefine, mStrDefine, mBoolDefine, mGenericDefine: + result = foldDefine(m, s, n, idgen, g) else: result = copyTree(s.astdef) of skProc, skFunc, skMethod: diff --git a/doc/manual.md b/doc/manual.md index a4f864eaa956b..b5749505e06c2 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -8032,6 +8032,24 @@ used. To see if a value was provided, `defined(FooBar)` can be used. The syntax `-d:flag`:option: is actually just a shortcut for `-d:flag=true`:option:. +These pragmas also accept an optional string argument for qualified +define names. + + ```nim + const FooBar {.intdefine: "package.FooBar".}: int = 5 + echo FooBar + ``` + + ```cmd + nim c -d:package.FooBar=42 foobar.nim + ``` + +This helps disambiguate define names in different packages. + +See also the [generic `define` pragma](manual_experimental.html#generic-define-pragma) +for a version of these pragmas that detects the type of the define based on +the constant value. + User-defined pragmas ==================== diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index 0611f55a73c94..608f1755a90d6 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -65,6 +65,29 @@ However, a `void` type cannot be inferred in generic code: The `void` type is only valid for parameters and return types; other symbols cannot have the type `void`. +Generic `define` pragma +======================= + +Aside the [typed define pragmas for constants](manual.html#implementation-specific-pragmas-compileminustime-define-pragmas), +there is a generic `{.define.}` pragma that interprets the value of the define +based on the type of the constant value. + + ```nim + const foo {.define: "package.foo".} = 123 + const bar {.define: "package.bar".} = false + ``` + + ```cmd + nim c -d:package.foo=456 -d:package.bar foobar.nim + ``` + +The following types are supported: + +* `string` and `cstring` +* Signed and unsigned integer types +* `bool` +* Enums + Top-down type inference ======================= diff --git a/tests/misc/tdefine.nim b/tests/misc/tdefine.nim index c4d11c941c91f..f3fa4711f2c34 100644 --- a/tests/misc/tdefine.nim +++ b/tests/misc/tdefine.nim @@ -1,12 +1,23 @@ discard """ joinable: false -cmd: "nim c -d:booldef -d:booldef2=false -d:intdef=2 -d:strdef=foobar -d:namespaced.define=false -d:double.namespaced.define -r $file" +cmd: "nim c $options -d:booldef -d:booldef2=false -d:intdef=2 -d:strdef=foobar -d:namespaced.define=false -d:double.namespaced.define -r $file" +matrix: "; -d:useGenericDefine" """ -const booldef {.booldefine.} = false -const booldef2 {.booldefine.} = true -const intdef {.intdefine.} = 0 -const strdef {.strdefine.} = "" +when defined(useGenericDefine): + {.pragma: booldefine2, define.} + {.pragma: intdefine2, define.} + {.pragma: strdefine2, define.} +else: + + {.pragma: booldefine2, booldefine.} + {.pragma: intdefine2, intdefine.} + {.pragma: strdefine2, strdefine.} + +const booldef {.booldefine2.} = false +const booldef2 {.booldefine2.} = true +const intdef {.intdefine2.} = 0 +const strdef {.strdefine2.} = "" doAssert defined(booldef) doAssert defined(booldef2) @@ -17,17 +28,28 @@ doAssert not booldef2 doAssert intdef == 2 doAssert strdef == "foobar" +when defined(useGenericDefine): + block: + const uintdef {.define: "intdef".}: uint = 17 + doAssert intdef == int(uintdef) + const cstrdef {.define: "strdef".}: cstring = "not strdef" + doAssert $cstrdef == strdef + type FooBar = enum foo, bar, foobar + const enumdef {.define: "strdef".} = foo + doAssert $enumdef == strdef + doAssert enumdef == foobar + # Intentionally not defined from command line -const booldef3 {.booldefine.} = true -const intdef2 {.intdefine.} = 1 -const strdef2 {.strdefine.} = "abc" +const booldef3 {.booldefine2.} = true +const intdef2 {.intdefine2.} = 1 +const strdef2 {.strdefine2.} = "abc" type T = object - when booldef3: - field1: int - when intdef2 == 1: - field2: int - when strdef2 == "abc": - field3: int + when booldef3: + field1: int + when intdef2 == 1: + field2: int + when strdef2 == "abc": + field3: int doAssert not defined(booldef3) doAssert not defined(intdef2) @@ -35,11 +57,21 @@ doAssert not defined(strdef2) discard T(field1: 1, field2: 2, field3: 3) doAssert defined(namespaced.define) -const `namespaced.define` {.booldefine.} = true +const `namespaced.define` {.booldefine2.} = true doAssert not `namespaced.define` +when defined(useGenericDefine): + const aliasToNamespacedDefine {.define: "namespaced.define".} = not `namespaced.define` +else: + const aliasToNamespacedDefine {.booldefine: "namespaced.define".} = not `namespaced.define` +doAssert aliasToNamespacedDefine == `namespaced.define` doAssert defined(double.namespaced.define) -const `double.namespaced.define` {.booldefine.} = false +const `double.namespaced.define` {.booldefine2.} = false doAssert `double.namespaced.define` +when defined(useGenericDefine): + const aliasToDoubleNamespacedDefine {.define: "double.namespaced.define".} = not `double.namespaced.define` +else: + const aliasToDoubleNamespacedDefine {.booldefine: "double.namespaced.define".} = not `double.namespaced.define` +doAssert aliasToDoubleNamespacedDefine == `double.namespaced.define` doAssert not defined(namespaced.butnotdefined)