From 4aff12408ce8d22f64572371f96e04b4a6c73707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bartoletti?= Date: Thu, 9 Jan 2025 09:07:59 +0100 Subject: [PATCH 01/48] math: Add cumprod and cumproded (#23416) This pull request adds the `cumproded` function along with its in-place equivalent, `cumprod`, to the math library. These functions provide functionality similar to `cumsum` and `cumsummed`, allowing users to calculate the cumulative sum of elements. The `cumprod` function computes the cumulative product of elements in-place, while `cumproded` additionally returns the prod seq. --- lib/pure/math.nim | 32 ++++++++++++++++++++++++++++++++ tests/stdlib/tmath.nim | 20 ++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index d51751a274..e304f5c01b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -1145,6 +1145,37 @@ func prod*[T](x: openArray[T]): T = result = T(1) for i in items(x): result = result * i +func cumprod*[T](x: var openArray[T]) = + ## Transforms ``x`` in-place (must be declared as `var`) into its + ## product. + ## + ## See also: + ## * `prod proc <#sum,openArray[T]>`_ + ## * `cumproded proc <#cumproded,openArray[T]>`_ for a version which + ## returns cumproded sequence + runnableExamples: + var a = [1, 2, 3, 4] + cumprod(a) + doAssert a == @[1, 2, 6, 24] + for i in 1 ..< x.len: x[i] = x[i-1] * x[i] + +func cumproded*[T](x: openArray[T]): seq[T] = + ## Return cumulative (aka prefix) product of ``x``. + ## + ## See also: + ## * `prod proc <#prod,openArray[T]>`_ + ## * `cumprod proc <#cumprod,openArray[T]>`_ for the in-place version + runnableExamples: + let a = [1, 2, 3, 4] + doAssert cumproded(a) == @[1, 2, 6, 24] + result = @[] + let xLen = x.len + if xLen == 0: + return @[] + result.setLen(xLen) + result[0] = x[0] + for i in 1 ..< xLen: result[i] = result[i-1] * x[i] + func cumsummed*[T](x: openArray[T]): seq[T] = ## Returns the cumulative (aka prefix) summation of `x`. ## @@ -1353,3 +1384,4 @@ func lcm*[T](x: openArray[T]): T {.since: (1, 1).} = result = x[0] for i in 1 ..< x.len: result = lcm(result, x[i]) + diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index b28ec41a31..69534b16e2 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -245,6 +245,25 @@ template main() = empty.cumsum doAssert empty == @[] + block: # cumprod + block: #cumprod int seq return + let counts = [ 1, 2, 3, 4 ] + doAssert counts.cumproded == [ 1, 2, 6, 24 ] + + block: # cumprod float seq return + let counts = [ 1.0, 2.0, 3.0, 4.0 ] + doAssert counts.cumproded == [ 1.0, 2.0, 6.0, 24.0 ] + + block: # cumprod int in-place + var counts = [ 1, 2, 3, 4 ] + counts.cumprod + doAssert counts == [ 1, 2, 6, 24 ] + + block: # cumprod float in-place + var counts = [ 1.0, 2.0, 3.0, 4.0 ] + counts.cumprod + doAssert counts == [ 1.0, 2.0, 6.0, 24.0 ] + block: # ^ compiles for valid types doAssert: compiles(5 ^ 2) doAssert: compiles(5.5 ^ 2) @@ -525,3 +544,4 @@ when not defined(js) and not defined(danger): doAssertRaises(OverflowDefect): discard sum(x) + From 26ed46999638ed1e9bd31c476294c3365119c2b4 Mon Sep 17 00:00:00 2001 From: Bilog WEB3 <155262265+Bilogweb3@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:00:11 +0100 Subject: [PATCH 02/48] Update changelog_1_2_0.md (#24607) --- changelogs/changelog_1_2_0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/changelogs/changelog_1_2_0.md b/changelogs/changelog_1_2_0.md index 1f76df0b49..11390fee17 100644 --- a/changelogs/changelog_1_2_0.md +++ b/changelogs/changelog_1_2_0.md @@ -169,7 +169,7 @@ echo f - The Nim compiler now supports a new pragma called ``.localPassc`` to pass specific compiler options to the C(++) backend for the C(++) file that was produced from the current Nim module. -- The compiler now inferes "sink parameters". To disable this for a specific routine, +- The compiler now infers "sink parameters". To disable this for a specific routine, annotate it with `.nosinks`. To disable it for a section of code, use `{.push sinkInference: off.}`...`{.pop.}`. - The compiler now supports a new switch `--panics:on` that turns runtime @@ -261,7 +261,7 @@ echo f ([#12812](https://github.com/nim-lang/Nim/issues/12812)) - Fixed "Produce static/const initializations for variables when possible" ([#12216](https://github.com/nim-lang/Nim/issues/12216)) -- Fixed "Assigning descriminator field leads to internal assert with --gc:destructors" +- Fixed "Assigning discriminator field leads to internal assert with --gc:destructors" ([#12821](https://github.com/nim-lang/Nim/issues/12821)) - Fixed "nimsuggest `use` command does not return all instances of symbol" ([#12832](https://github.com/nim-lang/Nim/issues/12832)) From 41c447b5f47e1b7cc798d1a0efb172a970fc0db7 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:52:03 +0800 Subject: [PATCH 03/48] ci: update to ubuntu 22.04 (#24608) --- .github/workflows/ci_docs.yml | 2 +- .github/workflows/ci_packages.yml | 2 +- .github/workflows/ci_publish.yml | 17 +++-------------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci_docs.yml b/.github/workflows/ci_docs.yml index 7d754bfedd..8461fb5432 100644 --- a/.github/workflows/ci_docs.yml +++ b/.github/workflows/ci_docs.yml @@ -41,7 +41,7 @@ jobs: target: [linux, windows, osx] include: - target: linux - os: ubuntu-20.04 + os: ubuntu-22.04 - target: windows os: windows-2019 - target: osx diff --git a/.github/workflows/ci_packages.yml b/.github/workflows/ci_packages.yml index 7dcfdd418a..fec634966b 100644 --- a/.github/workflows/ci_packages.yml +++ b/.github/workflows/ci_packages.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04, macos-13] + os: [ubuntu-22.04, macos-13] cpu: [amd64] batch: ["allowed_failures", "0_3", "1_3", "2_3"] # list of `index_num` name: '${{ matrix.os }} (batch: ${{ matrix.batch }})' diff --git a/.github/workflows/ci_publish.yml b/.github/workflows/ci_publish.yml index decfe953ec..39fae32fea 100644 --- a/.github/workflows/ci_publish.yml +++ b/.github/workflows/ci_publish.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-20.04] + os: [ubuntu-22.04] cpu: [amd64] name: '${{ matrix.os }}' runs-on: ${{ matrix.os }} @@ -21,10 +21,10 @@ jobs: with: fetch-depth: 2 - - name: 'Install node.js 20.x' + - name: 'Install node.js' uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '' - name: 'Install dependencies (Linux amd64)' if: runner.os == 'Linux' && matrix.cpu == 'amd64' @@ -34,17 +34,6 @@ jobs: sudo apt-fast install --no-install-recommends -yq \ libcurl4-openssl-dev libgc-dev libsdl1.2-dev libsfml-dev \ valgrind libc6-dbg libblas-dev xorg-dev - - name: 'Install dependencies (macOS)' - if: runner.os == 'macOS' - run: brew install boehmgc make sfml gtk+3 - - name: 'Install dependencies (Windows)' - if: runner.os == 'Windows' - shell: bash - run: | - set -e - . ci/funs.sh - nimInternalInstallDepsWindows - echo_run echo "${{ github.workspace }}/dist/mingw64/bin" >> "${GITHUB_PATH}" - name: 'Add build binaries to PATH' shell: bash From d83ff81695096fac8fa230b91a254c4287041173 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 13 Jan 2025 12:10:05 +0300 Subject: [PATCH 04/48] disable sfml test on osx (#24615) Tried installing sfml 2 in #24614 but didn't work --- tests/niminaction/Chapter8/sfml/sfml_test.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/niminaction/Chapter8/sfml/sfml_test.nim b/tests/niminaction/Chapter8/sfml/sfml_test.nim index e71060cb43..99c5195a47 100644 --- a/tests/niminaction/Chapter8/sfml/sfml_test.nim +++ b/tests/niminaction/Chapter8/sfml/sfml_test.nim @@ -1,6 +1,7 @@ discard """ action: compile disabled: "windows" +disabled: osx """ import sfml, os From 8d0e853e0afc7d0c4830cb7e03dec88c5c814aef Mon Sep 17 00:00:00 2001 From: metagn Date: Wed, 15 Jan 2025 22:01:56 +0300 Subject: [PATCH 05/48] ignore match errors to expected types of tuple constructor elements (#24611) fixes #24609 A tuple may have an incompatible expected type if there is a converter match to it. So the compiler should not error when trying to match the individual elements in the constructor to the elements of the expected tuple type, this will be checked when the tuple is entirely constructed anyway. --- compiler/semexprs.nim | 5 ++++- tests/tuples/ttupleconverter.nim | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/tuples/ttupleconverter.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 916cedab6a..e2b2076530 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2929,7 +2929,10 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType # hasEmpty/nil check is to not break existing code like # `const foo = [(1, {}), (2, {false})]`, # `const foo = if true: (0, nil) else: (1, new(int))` - n[i][1] = fitNode(c, expectedElemType, n[i][1], n[i][1].info) + let conversion = indexTypesMatch(c, expectedElemType, n[i][1].typ, n[i][1]) + # ignore matching error, full tuple will be matched later which may call converter, see #24609 + if conversion != nil: + n[i][1] = conversion if n[i][1].typ.kind == tyTypeDesc: localError(c.config, n[i][1].info, "typedesc not allowed as tuple field.") diff --git a/tests/tuples/ttupleconverter.nim b/tests/tuples/ttupleconverter.nim new file mode 100644 index 0000000000..2f615dc91e --- /dev/null +++ b/tests/tuples/ttupleconverter.nim @@ -0,0 +1,18 @@ +# issue #24609 + +import std/options + +type + Config* = object + bits*: tuple[r, g, b, a: Option[int32]] + +# works on 2.0.8 +# +# results in error on 2.2.0 +# type mismatch: got 'int literal(8)' for '8' but expected 'Option[system.int32]' +# +converter toInt32Tuple*(t: tuple[r,g,b,a: int]): tuple[r,g,b,a: Option[int32]] = + (some(t.r.int32), some(t.g.int32), some(t.b.int32), some(t.a.int32)) + +var cfg: Config +cfg.bits = (r: 8, g: 8, b: 8, a: 16) From 70d057fcc6f76f016671d4775fec879f2762811f Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 16 Jan 2025 22:43:18 +0800 Subject: [PATCH 06/48] fixes `compile` crashes with one parameter (#24618) `{.compile("foo.c").}` makes Nim compiler crash --- compiler/pragmas.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 098ce36d58..e488412af7 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -590,7 +590,7 @@ proc processCompile(c: PContext, n: PNode) = var customArgs = "" if n.kind in nkCallKinds: s = getStrLit(c, n, 1) - if n.len <= 3: + if n.len == 3: customArgs = getStrLit(c, n, 2) else: localError(c.config, n.info, "'.compile' pragma takes up 2 arguments") From 2af9ddc286b4f0c6d467fb7a95a60a1e10437744 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 17 Jan 2025 23:08:47 +0800 Subject: [PATCH 07/48] revert `strictDefs` as the default (#24620) revert https://github.com/nim-lang/Nim/pull/24225 see also https://forum.nim-lang.org/t/12646 --- compiler/condsyms.nim | 1 - compiler/nim.cfg | 2 +- compiler/options.nim | 2 -- compiler/semexprs.nim | 5 ++--- compiler/sempass2.nim | 6 +++--- compiler/semstmts.nim | 2 +- compiler/sigmatch.nim | 2 +- config/config.nims | 1 - tests/msgs/twarningaserror.nim | 1 - 9 files changed, 8 insertions(+), 14 deletions(-) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index f9db67d4bb..adef5f364a 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -172,4 +172,3 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasDefaultFloatRoundtrip") defineSymbol("nimHasXorSet") - defineSymbol("nimHasLegacyNoStrictDefs") diff --git a/compiler/nim.cfg b/compiler/nim.cfg index c0e4491503..21faf37836 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -49,7 +49,7 @@ define:useStdoutAsStdmsg @if nimUseStrictDefs: - experimental:strictDefs # deadcode + experimental:strictDefs warningAsError[Uninit]:on warningAsError[ProveInit]:on @end diff --git a/compiler/options.nim b/compiler/options.nim index b456651c82..ea75a68487 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -248,8 +248,6 @@ type ## Useful for libraries that rely on local passC jsNoLambdaLifting ## Old transformation for closures in JS backend - noStrictDefs - ## disable "strictdefs" SymbolFilesOption* = enum disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index e2b2076530..f959783225 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -878,7 +878,7 @@ proc newHiddenAddrTaken(c: PContext, n: PNode, isOutParam: bool): PNode = if aa notin {arLValue, arLocalLValue}: if aa == arDiscriminant and c.inUncheckedAssignSection > 0: discard "allow access within a cast(unsafeAssign) section" - elif noStrictDefs notin c.config.legacyFeatures and aa == arAddressableConst and + elif strictDefs in c.features and aa == arAddressableConst and sym != nil and sym.kind == skLet and isOutParam: discard "allow let varaibles to be passed to out parameters" else: @@ -2068,8 +2068,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = let root = getRoot(a) let useStrictDefLet = root != nil and root.kind == skLet and assignable == arAddressableConst and - noStrictDefs notin c.config.legacyFeatures and - isLocalSym(root) + strictDefs in c.features and isLocalSym(root) if le == nil: localError(c.config, a.info, "expression has no type") elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind notin {tyVar} and diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4ff50efde0..b3fb3d91b2 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -221,7 +221,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = if volatileCheck: makeVolatile(a, s) for x in a.init: if x == s.id: - if noStrictDefs notin a.c.config.legacyFeatures and s.kind == skLet: + if strictDefs in a.c.features and s.kind == skLet: localError(a.config, n.info, errXCannotBeAssignedTo % renderTree(n, {renderNoComments} )) @@ -379,7 +379,7 @@ proc useVar(a: PEffects, n: PNode) = if s.typ.requiresInit: message(a.config, n.info, warnProveInit, s.name.s) elif a.leftPartOfAsgn <= 0: - if noStrictDefs notin a.c.config.legacyFeatures: + if strictDefs in a.c.features: if s.kind == skLet: localError(a.config, n.info, errLetNeedsInit) else: @@ -1664,7 +1664,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) = if not isEmptyType(s.typ.returnType) and (s.typ.returnType.requiresInit or s.typ.returnType.skipTypes(abstractInst).kind == tyVar or - noStrictDefs notin c.config.legacyFeatures) and + strictDefs in c.features) and s.kind in {skProc, skFunc, skConverter, skMethod} and s.magic == mNone and sfNoInit notin s.flags: var res = s.ast[resultPos].sym # get result symbol diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 083fb3cf16..75b327afb4 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -975,7 +975,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = else: checkNilable(c, v) # allow let to not be initialised if imported from C: - if v.kind == skLet and sfImportc notin v.flags and (noStrictDefs in c.config.legacyFeatures or not isLocalSym(v)): + if v.kind == skLet and sfImportc notin v.flags and (strictDefs notin c.features or not isLocalSym(v)): localError(c.config, a.info, errLetNeedsInit) if sfCompileTime in v.flags: var x = newNodeI(result.kind, v.info) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 01eed9df4d..950ebe5196 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2251,7 +2251,7 @@ proc isLValue(c: PContext; n: PNode, isOutParam = false): bool {.inline.} = result = c.inUncheckedAssignSection > 0 of arAddressableConst: let sym = getRoot(n) - result = noStrictDefs notin c.config.legacyFeatures and sym != nil and sym.kind == skLet and isOutParam + result = strictDefs in c.features and sym != nil and sym.kind == skLet and isOutParam else: result = false diff --git a/config/config.nims b/config/config.nims index 45c6ec58cd..b8979e8e31 100644 --- a/config/config.nims +++ b/config/config.nims @@ -21,4 +21,3 @@ when defined(nimStrictMode): # future work: XDeclaredButNotUsed switch("define", "nimVersion:" & NimVersion) # deadcode -switch("experimental", "strictDefs") diff --git a/tests/msgs/twarningaserror.nim b/tests/msgs/twarningaserror.nim index 22b0e9332a..6f7b760956 100644 --- a/tests/msgs/twarningaserror.nim +++ b/tests/msgs/twarningaserror.nim @@ -1,5 +1,4 @@ discard """ - matrix: "--legacy:nostrictdefs" joinable: false """ From 6481482e0e973bb1ed0b39b640753404d9d333ea Mon Sep 17 00:00:00 2001 From: Antonis Geralis <43617260+planetis-m@users.noreply.github.com> Date: Sun, 19 Jan 2025 15:20:54 +0200 Subject: [PATCH 08/48] Optimize storing into uninit locations for arrays and seqs. (#24619) --- lib/system.nim | 12 +++++++----- lib/system/seqs_v2.nim | 7 +++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/system.nim b/lib/system.nim index cb89098dbb..e8d8a8c513 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2965,14 +2965,16 @@ when notJSnotNims and not defined(nimSeqsV2): assert y == "abcgh" discard -proc arrayWith*[T](y: T, size: static int): array[size, T] {.raises: [].} = +proc arrayWith*[T](y: T, size: static int): array[size, T] {.noinit, nodestroy, raises: [].} = ## Creates a new array filled with `y`. - result = zeroDefault(array[size, T]) for i in 0..size-1: - result[i] = y + when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1): + result[i] = `=dup`(y) + else: + wasMoved(result[i]) + `=copy`(result[i], y) -proc arrayWithDefault*[T](size: static int): array[size, T] {.raises: [].} = +proc arrayWithDefault*[T](size: static int): array[size, T] {.noinit, nodestroy, raises: [].} = ## Creates a new array filled with `default(T)`. - result = zeroDefault(array[size, T]) for i in 0..size-1: result[i] = default(T) diff --git a/lib/system/seqs_v2.nim b/lib/system/seqs_v2.nim index 572e77408f..6ace66afea 100644 --- a/lib/system/seqs_v2.nim +++ b/lib/system/seqs_v2.nim @@ -144,8 +144,11 @@ proc grow*[T](x: var seq[T]; newLen: Natural; value: T) {.nodestroy.} = xu.p = cast[typeof(xu.p)](prepareSeqAddUninit(oldLen, xu.p, newLen - oldLen, sizeof(T), alignof(T))) xu.len = newLen for i in oldLen .. newLen-1: - wasMoved(xu.p.data[i]) - `=copy`(xu.p.data[i], value) + when (NimMajor, NimMinor, NimPatch) >= (2, 3, 1): + xu.p.data[i] = `=dup`(value) + else: + wasMoved(xu.p.data[i]) + `=copy`(xu.p.data[i], value) proc add*[T](x: var seq[T]; y: sink T) {.magic: "AppendSeqElem", noSideEffect, nodestroy.} = ## Generic proc for adding a data item `y` to a container `x`. From 793baf34ff72cb8c5485ce209af086e27f656853 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 20 Jan 2025 12:12:38 +0300 Subject: [PATCH 09/48] generate destructor in nodestroy proc for explicit destructor call (#24627) fixes #24626 `createTypeboundOps` in sempass2 is called when generating destructors for types including for explicit destructor calls, however it blocks destructors from getting generated in a `nodestroy` proc. This causes issues when a destructor is explicitly called in a `nodestroy` proc. To fix this, allow destructors to get generated only for explicit destructor calls in nodestroy procs. --- compiler/sempass2.nim | 7 ++++--- tests/arc/tnodestroyexplicithook.nim | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/arc/tnodestroyexplicithook.nim diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index b3fb3d91b2..4122ec2fd6 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -124,10 +124,11 @@ proc collectObjectTree(graph: ModuleGraph, n: PNode) = else: graph.objectTree[root].add (depthLevel, typ) -proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) = - if typ == nil or sfGeneratedOp in tracked.owner.flags: +proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit = false) = + if typ == nil or (sfGeneratedOp in tracked.owner.flags and not explicit): # don't create type bound ops for anything in a function with a `nodestroy` pragma # bug #21987 + # unless this is an explicit call, bug #24626 return when false: let realType = typ.skipTypes(abstractInst) @@ -1072,7 +1073,7 @@ proc trackCall(tracked: PEffects; n: PNode) = # rebind type bounds operations after createTypeBoundOps call let t = n[1].typ.skipTypes({tyAlias, tyVar}) if a.sym != getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)): - createTypeBoundOps(tracked, t, n.info) + createTypeBoundOps(tracked, t, n.info, explicit = true) let op = getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)) if op != nil: n[0].sym = op diff --git a/tests/arc/tnodestroyexplicithook.nim b/tests/arc/tnodestroyexplicithook.nim new file mode 100644 index 0000000000..99dd0c6332 --- /dev/null +++ b/tests/arc/tnodestroyexplicithook.nim @@ -0,0 +1,24 @@ +discard """ + ccodecheck: "'Result[(i - 0)] = eqdup'" +""" + +# issue #24626 + +proc arrayWith2[T](y: T, size: static int): array[size, T] {.noinit, nodestroy, raises: [].} = + ## Creates a new array filled with `y`. + for i in 0..size-1: + when defined(nimHasDup): + result[i] = `=dup`(y) + else: + wasMoved(result[i]) + `=copy`(result[i], y) + +proc useArray(x: seq[int]) = + var a = arrayWith2(x, 2) + +proc main = + let x = newSeq[int](100) + for i in 0..5: + useArray(x) + +main() From 2f402fcb82ababdd2f6285117929d24dd4d990a5 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 22 Jan 2025 21:05:57 +0800 Subject: [PATCH 10/48] fixes #24630; static openArray backed by seq cannot be passed to another function (#24638) fixes #24630 --- compiler/vmgen.nim | 2 +- tests/vm/topenarrays.nim | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index d3d216fde5..0db0e93f9f 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -2040,7 +2040,7 @@ proc genArrayConstr(c: PCtx, n: PNode, dest: var TDest) = c.gABx(n, opcLdNull, dest, c.genType(n.typ)) let intType = getSysType(c.graph, n.info, tyInt) - let seqType = n.typ.skipTypes(abstractVar-{tyTypeDesc}) + let seqType = n.typ.skipTypes(abstractVar+{tyStatic}-{tyTypeDesc}) if seqType.kind == tySequence: var tmp = c.getTemp(intType) c.gABx(n, opcLdImmInt, tmp, n.len) diff --git a/tests/vm/topenarrays.nim b/tests/vm/topenarrays.nim index 375d2523d3..472f902103 100644 --- a/tests/vm/topenarrays.nim +++ b/tests/vm/topenarrays.nim @@ -87,3 +87,12 @@ block: # bug #22095 z = fn() doAssert z.limbs[0] == 10 + +block: # bug #24630 + func f(a: static openArray[int]): int = + 12 + + func g(a: static openArray[int]) = + const b = f(a) + + g(@[1,2,3]) From 6d59680217cfbd9314cf62d1d07adc8e6e552d53 Mon Sep 17 00:00:00 2001 From: metagn Date: Wed, 22 Jan 2025 16:08:21 +0300 Subject: [PATCH 11/48] don't try to transform objconstr/cast type nodes (#24636) fixes #24631 [Object constructors](https://github.com/nim-lang/Nim/blob/793baf34ff72cb8c5485ce209af086e27f656853/compiler/semobjconstr.nim#L462), [casts](https://github.com/nim-lang/Nim/blob/793baf34ff72cb8c5485ce209af086e27f656853/compiler/semexprs.nim#L494) and [type conversions](https://github.com/nim-lang/Nim/blob/793baf34ff72cb8c5485ce209af086e27f656853/compiler/semexprs.nim#L419) copy their type nodes verbatim instead of producing semchecked type nodes. This causes a crash in transf when an untyped expression in the type node has `nil` type. To deal with this, don't try to transform the type node in these expressions at all. I couldn't reproduce the problem with type conversion nodes though so those are unchanged in transf. --- compiler/transf.nim | 10 ++++++++++ tests/template/tgenericobjconstr.nim | 15 +++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/template/tgenericobjconstr.nim diff --git a/compiler/transf.nim b/compiler/transf.nim index 5cf43e6a3f..433a534912 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -106,6 +106,13 @@ proc transformSons(c: PTransf, n: PNode, noConstFold = false): PNode = for i in 0.. Date: Fri, 24 Jan 2025 03:10:14 +0800 Subject: [PATCH 12/48] fixes #24623; fixes #23692; size pragma only allowed for imported types and enum types (#24640) fixes #24623 fixes #23692 ref https://nim-lang.org/docs/manual.html#implementation-specific-pragmas-size-pragma confines `size` pragma to `enums` and imported `objects` for now The `typeDefLeftSidePass` carries out the check for pragmas, but the type is not complete yet. So the `size` pragma checking is postponed at the final pass. --- compiler/semstmts.nim | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 75b327afb4..41154f5962 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1787,6 +1787,17 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = # check the style here after the pragmas have been processed: styleCheckDef(c, s) # compute the type's size and check for illegal recursions: + if a[0].kind == nkPragmaExpr: + let pragmas = a[0][1] + for i in 0 ..< pragmas.len: + if pragmas[i].kind == nkExprColonExpr and + pragmas[i][0].kind == nkIdent and + whichKeyword(pragmas[i][0].ident) == wSize: + if s.typ.kind != tyEnum and sfImportc notin s.flags: + # EventType* {.size: sizeof(uint32).} = enum + # AtomicFlag* {.importc: "atomic_flag", header: "", size: 1.} = object + localError(c.config, pragmas[i].info, "size pragma only allowed for enum types and imported types") + if a[1].kind == nkEmpty: var x = a[2] if x.kind in nkCallKinds and nfSem in x.flags: From 67f9bc2f4bcc0d2fb4d4a0ed71de34fe218a68b1 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 24 Jan 2025 20:00:08 +0800 Subject: [PATCH 13/48] fixes #21923; nimsuggest "outline" output does not list templates (#24643) fixes #21923 --------- Co-authored-by: Louis Berube --- compiler/sem.nim | 15 +++++++++++++++ compiler/semstmts.nim | 13 +------------ compiler/semtempl.nim | 3 +++ nimsuggest/tests/t21923.nim | 15 +++++++++++++++ nimsuggest/tests/tsug_template.nim | 2 +- 5 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 nimsuggest/tests/t21923.nim diff --git a/compiler/sem.nim b/compiler/sem.nim index 7e59f20866..f4b6d06b82 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -500,6 +500,21 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, dec(c.config.evalTemplateCounter) discard c.friendModules.pop() +proc getLineInfo(n: PNode): TLineInfo = + case n.kind + of nkPostfix: + if len(n) > 1: + result = getLineInfo(n[1]) + else: + result = n.info + of nkAccQuoted, nkPragmaExpr: + if len(n) > 0: + result = getLineInfo(n[0]) + else: + result = n.info + else: + result = n.info + const errMissingGenericParamsForTemplate = "'$1' has unspecified generic parameters" diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 41154f5962..c4cd623d9b 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -492,19 +492,8 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind, reportToNimsuggest = tru incl(result.flags, sfGlobal) result.options = c.config.options - proc getLineInfo(n: PNode): TLineInfo = - case n.kind - of nkPostfix: - if len(n) > 1: - return getLineInfo(n[1]) - of nkAccQuoted, nkPragmaExpr: - if len(n) > 0: - return getLineInfo(n[0]) - else: - discard - result = n.info - let info = getLineInfo(n) if reportToNimsuggest: + let info = getLineInfo(n) suggestSym(c.graph, info, result, c.graph.usageSym) proc checkNilable(c: PContext; v: PSym) = diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 9954e3c123..7732e097ec 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -691,6 +691,9 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = s = semIdentVis(c, skTemplate, n[namePos], {}) assert s.kind == skTemplate + let info = getLineInfo(n[namePos]) + suggestSym(c.graph, info, s, c.graph.usageSym) + styleCheckDef(c, s) onDef(n[namePos].info, s) # check parameter list: diff --git a/nimsuggest/tests/t21923.nim b/nimsuggest/tests/t21923.nim new file mode 100644 index 0000000000..7e1d144e4f --- /dev/null +++ b/nimsuggest/tests/t21923.nim @@ -0,0 +1,15 @@ +discard """ +$nimsuggest --tester $file +>outline $file +outline;;skProc;;t21923.foo;;proc (x: int){.gcsafe, raises: [].};;$file;;8;;5;;"";;100 +outline;;skTemplate;;t21923.foo2;;;;$file;;11;;9;;"";;100 +""" + +proc foo(x: int) = + echo "foo" + +template foo2(x: int) = + echo "foo2" + +foo(12) +foo2(12) diff --git a/nimsuggest/tests/tsug_template.nim b/nimsuggest/tests/tsug_template.nim index da494d279d..24b430c0ae 100644 --- a/nimsuggest/tests/tsug_template.nim +++ b/nimsuggest/tests/tsug_template.nim @@ -6,7 +6,7 @@ tmp#[!]# discard """ $nimsuggest --tester $file >sug $1 +sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, raises: [].};;$file;;2;;6;;"";;100;;Prefix sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;100;;Prefix -sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix """ From 1f9cac1f5cdeb242e70bf1e058a87213bbee64fb Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Fri, 24 Jan 2025 13:02:59 +0100 Subject: [PATCH 14/48] Enable macros to use certain things from the OS module when the target OS is not supported (#24639) Essentially this PR removes the `{.error.}` pragmas littered around in the OS module and submodules which prevents them from being imported if the target OS is not supported. This made it impossible to use certain supported features of the OS module in macros from a supported host OS. Instead of the `{.error.}` pragmas the `oscommon` module now has a constant `supportedSystem` which is false in the cases where the `{.error.}` pragmas where generated. All procedures which can't be run by macros is also not declared when `supportedSystem` is false. It would be possible to create dummy versions of the omitted functions with an `{.error.}` pragma that would trigger upon their use, but this is currently not done. This properly fixes #19414 --- compiler/vmops.nim | 2 +- lib/pure/os.nim | 1157 ++++++++++++++++---------------- lib/std/cmdline.nim | 4 +- lib/std/private/oscommon.nim | 216 +++--- lib/std/private/osdirs.nim | 12 +- lib/std/private/osfiles.nim | 2 - lib/std/private/ospaths2.nim | 83 ++- lib/std/private/ossymlinks.nim | 5 +- lib/std/staticos.nim | 22 + 9 files changed, 752 insertions(+), 751 deletions(-) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 45194e6338..8b0b8b5c7c 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -263,7 +263,7 @@ proc registerAdditionalOps*(c: PCtx) = wrap2si(readLines, ioop) systemop getCurrentExceptionMsg systemop getCurrentException - registerCallback c, "stdlib.osdirs.staticWalkDir", proc (a: VmArgs) {.nimcall.} = + registerCallback c, "stdlib.staticos.staticWalkDir", proc (a: VmArgs) {.nimcall.} = setResult(a, staticWalkDirImpl(getString(a, 0), getBool(a, 1))) registerCallback c, "stdlib.staticos.staticDirExists", proc (a: VmArgs) {.nimcall.} = setResult(a, dirExists(getString(a, 0))) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index aac53b3664..c96a493ec6 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -32,20 +32,21 @@ runnableExamples: import std/private/ospaths2 export ospaths2 -import std/private/osfiles -export osfiles +import std/private/oscommon -import std/private/osdirs -export osdirs +when supportedSystem: + import std/private/osfiles + export osfiles -import std/private/ossymlinks -export ossymlinks + import std/private/osdirs + export osdirs + + import std/private/ossymlinks + export ossymlinks import std/private/osappdirs export osappdirs -import std/private/oscommon - include system/inclrtl import std/private/since @@ -82,8 +83,6 @@ elif defined(posix): proc toTime(ts: Timespec): times.Time {.inline.} = result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} @@ -221,283 +220,302 @@ const ## On Windows ``["exe", "cmd", "bat"]``, on Posix ``[""]``. when defined(windows): ["exe", "cmd", "bat"] else: [""] -proc findExe*(exe: string, followSymlinks: bool = true; - extensions: openArray[string]=ExeExts): string {. - tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} = - ## Searches for `exe` in the current working directory and then - ## in directories listed in the ``PATH`` environment variable. - ## - ## Returns `""` if the `exe` cannot be found. `exe` - ## is added the `ExeExts`_ file extensions if it has none. - ## - ## If the system supports symlinks it also resolves them until it - ## meets the actual file. This behavior can be disabled if desired - ## by setting `followSymlinks = false`. +when supportedSystem: + proc findExe*(exe: string, followSymlinks: bool = true; + extensions: openArray[string]=ExeExts): string {. + tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect], noNimJs.} = + ## Searches for `exe` in the current working directory and then + ## in directories listed in the ``PATH`` environment variable. + ## + ## Returns `""` if the `exe` cannot be found. `exe` + ## is added the `ExeExts`_ file extensions if it has none. + ## + ## If the system supports symlinks it also resolves them until it + ## meets the actual file. This behavior can be disabled if desired + ## by setting `followSymlinks = false`. - if exe.len == 0: return - template checkCurrentDir() = - for ext in extensions: - result = addFileExt(exe, ext) - if fileExists(result): return - when defined(posix): - if '/' in exe: checkCurrentDir() - else: - checkCurrentDir() - let path = getEnv("PATH") - for candidate in split(path, PathSep): - if candidate.len == 0: continue - when defined(windows): - var x = (if candidate[0] == '"' and candidate[^1] == '"': - substr(candidate, 1, candidate.len-2) else: candidate) / - exe + if exe.len == 0: return + template checkCurrentDir() = + for ext in extensions: + result = addFileExt(exe, ext) + if fileExists(result): return + when defined(posix): + if '/' in exe: checkCurrentDir() else: - var x = expandTilde(candidate) / exe - for ext in extensions: - var x = addFileExt(x, ext) - if fileExists(x): - when not (defined(windows) or defined(nintendoswitch)): - while followSymlinks: # doubles as if here - if x.symlinkExists: - var r = newString(maxSymlinkLen) - var len = readlink(x.cstring, r.cstring, maxSymlinkLen) - if len < 0: - raiseOSError(osLastError(), exe) - if len > maxSymlinkLen: - r = newString(len+1) - len = readlink(x.cstring, r.cstring, len) - setLen(r, len) - if isAbsolute(r): - x = r + checkCurrentDir() + let path = getEnv("PATH") + for candidate in split(path, PathSep): + if candidate.len == 0: continue + when defined(windows): + var x = (if candidate[0] == '"' and candidate[^1] == '"': + substr(candidate, 1, candidate.len-2) else: candidate) / + exe + else: + var x = expandTilde(candidate) / exe + for ext in extensions: + var x = addFileExt(x, ext) + if fileExists(x): + when defined(posix): #not (defined(windows) or defined(nintendoswitch)): + while followSymlinks: # doubles as if here + if x.symlinkExists: + var r = newString(maxSymlinkLen) + var len = readlink(x.cstring, r.cstring, maxSymlinkLen) + if len < 0: + raiseOSError(osLastError(), exe) + if len > maxSymlinkLen: + r = newString(len+1) + len = readlink(x.cstring, r.cstring, len) + setLen(r, len) + if isAbsolute(r): + x = r + else: + x = parentDir(x) / r else: - x = parentDir(x) / r - else: - break - return x - result = "" + break + return x + result = "" -when weirdTarget: - const times = "fake const" - template Time(x: untyped): untyped = string + when weirdTarget: + const times = "fake const" + template Time(x: untyped): untyped = string -proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = - ## Returns the `file`'s last modification time. - ## - ## See also: - ## * `getLastAccessTime proc`_ - ## * `getCreationTime proc`_ - ## * `fileNewer proc`_ - when defined(posix): - var res: Stat = default(Stat) - if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) - result = res.st_mtim.toTime - else: - var f: WIN32_FIND_DATA - var h = findFirstFile(file, f) - if h == -1'i32: raiseOSError(osLastError(), file) - result = fromWinTime(rdFileTime(f.ftLastWriteTime)) - findClose(h) + proc getLastModificationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = + ## Returns the `file`'s last modification time. + ## + ## See also: + ## * `getLastAccessTime proc`_ + ## * `getCreationTime proc`_ + ## * `fileNewer proc`_ + when defined(posix): + var res: Stat = default(Stat) + if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) + result = res.st_mtim.toTime + else: + var f: WIN32_FIND_DATA + var h = findFirstFile(file, f) + if h == -1'i32: raiseOSError(osLastError(), file) + result = fromWinTime(rdFileTime(f.ftLastWriteTime)) + findClose(h) -proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = - ## Returns the `file`'s last read or write access time. - ## - ## See also: - ## * `getLastModificationTime proc`_ - ## * `getCreationTime proc`_ - ## * `fileNewer proc`_ - when defined(posix): - var res: Stat = default(Stat) - if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) - result = res.st_atim.toTime - else: - var f: WIN32_FIND_DATA - var h = findFirstFile(file, f) - if h == -1'i32: raiseOSError(osLastError(), file) - result = fromWinTime(rdFileTime(f.ftLastAccessTime)) - findClose(h) + proc getLastAccessTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = + ## Returns the `file`'s last read or write access time. + ## + ## See also: + ## * `getLastModificationTime proc`_ + ## * `getCreationTime proc`_ + ## * `fileNewer proc`_ + when defined(posix): + var res: Stat = default(Stat) + if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) + result = res.st_atim.toTime + else: + var f: WIN32_FIND_DATA + var h = findFirstFile(file, f) + if h == -1'i32: raiseOSError(osLastError(), file) + result = fromWinTime(rdFileTime(f.ftLastAccessTime)) + findClose(h) -proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = - ## Returns the `file`'s creation time. - ## - ## **Note:** Under POSIX OS's, the returned time may actually be the time at - ## which the file's attribute's were last modified. See - ## `here `_ for details. - ## - ## See also: - ## * `getLastModificationTime proc`_ - ## * `getLastAccessTime proc`_ - ## * `fileNewer proc`_ - when defined(posix): - var res: Stat = default(Stat) - if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) - result = res.st_ctim.toTime - else: - var f: WIN32_FIND_DATA - var h = findFirstFile(file, f) - if h == -1'i32: raiseOSError(osLastError(), file) - result = fromWinTime(rdFileTime(f.ftCreationTime)) - findClose(h) + proc getCreationTime*(file: string): times.Time {.rtl, extern: "nos$1", noWeirdTarget.} = + ## Returns the `file`'s creation time. + ## + ## **Note:** Under POSIX OS's, the returned time may actually be the time at + ## which the file's attribute's were last modified. See + ## `here `_ for details. + ## + ## See also: + ## * `getLastModificationTime proc`_ + ## * `getLastAccessTime proc`_ + ## * `fileNewer proc`_ + when defined(posix): + var res: Stat = default(Stat) + if stat(file, res) < 0'i32: raiseOSError(osLastError(), file) + result = res.st_ctim.toTime + else: + var f: WIN32_FIND_DATA + var h = findFirstFile(file, f) + if h == -1'i32: raiseOSError(osLastError(), file) + result = fromWinTime(rdFileTime(f.ftCreationTime)) + findClose(h) -proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noWeirdTarget.} = - ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s - ## modification time is later than `b`'s. - ## - ## See also: - ## * `getLastModificationTime proc`_ - ## * `getLastAccessTime proc`_ - ## * `getCreationTime proc`_ - when defined(posix): - # If we don't have access to nanosecond resolution, use '>=' - when not StatHasNanoseconds: - result = getLastModificationTime(a) >= getLastModificationTime(b) + proc fileNewer*(a, b: string): bool {.rtl, extern: "nos$1", noWeirdTarget.} = + ## Returns true if the file `a` is newer than file `b`, i.e. if `a`'s + ## modification time is later than `b`'s. + ## + ## See also: + ## * `getLastModificationTime proc`_ + ## * `getLastAccessTime proc`_ + ## * `getCreationTime proc`_ + when defined(posix): + # If we don't have access to nanosecond resolution, use '>=' + when not StatHasNanoseconds: + result = getLastModificationTime(a) >= getLastModificationTime(b) + else: + result = getLastModificationTime(a) > getLastModificationTime(b) else: result = getLastModificationTime(a) > getLastModificationTime(b) - else: - result = getLastModificationTime(a) > getLastModificationTime(b) -proc isAdmin*: bool {.noWeirdTarget.} = - ## Returns whether the caller's process is a member of the Administrators local - ## group (on Windows) or a root (on POSIX), via `geteuid() == 0`. - when defined(windows): - # Rewrite of the example from Microsoft Docs: - # https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership#examples - # and corresponding PostgreSQL function: - # https://doxygen.postgresql.org/win32security_8c.html#ae6b61e106fa5d6c5d077a9d14ee80569 - var ntAuthority = SID_IDENTIFIER_AUTHORITY(value: SECURITY_NT_AUTHORITY) - var administratorsGroup: PSID - if not isSuccess(allocateAndInitializeSid(addr ntAuthority, - BYTE(2), - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - addr administratorsGroup)): - raiseOSError(osLastError(), "could not get SID for Administrators group") + proc isAdmin*: bool {.noWeirdTarget.} = + ## Returns whether the caller's process is a member of the Administrators local + ## group (on Windows) or a root (on POSIX), via `geteuid() == 0`. + when defined(windows): + # Rewrite of the example from Microsoft Docs: + # https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership#examples + # and corresponding PostgreSQL function: + # https://doxygen.postgresql.org/win32security_8c.html#ae6b61e106fa5d6c5d077a9d14ee80569 + var ntAuthority = SID_IDENTIFIER_AUTHORITY(value: SECURITY_NT_AUTHORITY) + var administratorsGroup: PSID + if not isSuccess(allocateAndInitializeSid(addr ntAuthority, + BYTE(2), + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + addr administratorsGroup)): + raiseOSError(osLastError(), "could not get SID for Administrators group") - try: - var b: WINBOOL - if not isSuccess(checkTokenMembership(0, administratorsGroup, addr b)): - raiseOSError(osLastError(), "could not check access token membership") + try: + var b: WINBOOL + if not isSuccess(checkTokenMembership(0, administratorsGroup, addr b)): + raiseOSError(osLastError(), "could not check access token membership") - result = isSuccess(b) - finally: - if freeSid(administratorsGroup) != nil: - raiseOSError(osLastError(), "failed to free SID for Administrators group") + result = isSuccess(b) + finally: + if freeSid(administratorsGroup) != nil: + raiseOSError(osLastError(), "failed to free SID for Administrators group") - else: - result = geteuid() == 0 - - -proc exitStatusLikeShell*(status: cint): cint = - ## Converts exit code from `c_system` into a shell exit code. - when defined(posix) and not weirdTarget: - if WIFSIGNALED(status): - # like the shell! - 128 + WTERMSIG(status) else: - WEXITSTATUS(status) - else: - status + result = geteuid() == 0 -proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", - tags: [ExecIOEffect], noWeirdTarget.} = - ## Executes a `shell command`:idx:. - ## - ## Command has the form 'program args' where args are the command - ## line arguments given to program. The proc returns the error code - ## of the shell when it has finished (zero if there is no error). - ## The proc does not return until the process has finished. - ## - ## To execute a program without having a shell involved, use `osproc.execProcess proc - ## `_. - ## - ## **Examples:** - ## ```Nim - ## discard execShellCmd("ls -la") - ## ``` - result = exitStatusLikeShell(c_system(command)) - -proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noWeirdTarget.} = - ## Returns the full (`absolute`:idx:) path of an existing file `filename`. - ## - ## Raises `OSError` in case of an error. Follows symlinks. - result = "" - when defined(windows): - var bufsize = MAX_PATH.int32 - var unused: WideCString = nil - var res = newWideCString(bufsize) - while true: - var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) - if L == 0'i32: + proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noWeirdTarget.} = + ## Returns the full (`absolute`:idx:) path of an existing file `filename`. + ## + ## Raises `OSError` in case of an error. Follows symlinks. + result = "" + when defined(windows): + var bufsize = MAX_PATH.int32 + var unused: WideCString = nil + var res = newWideCString(bufsize) + while true: + var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) + if L == 0'i32: + raiseOSError(osLastError(), filename) + elif L > bufsize: + res = newWideCString(L) + bufsize = L + else: + result = res$L + break + # getFullPathName doesn't do case corrections, so we have to use this convoluted + # way of retrieving the true filename + for x in walkFiles(result): + result = x + if not fileExists(result) and not dirExists(result): + # consider using: `raiseOSError(osLastError(), result)` + raise newException(OSError, "file '" & result & "' does not exist") + else: + # according to Posix we don't need to allocate space for result pathname. + # But we need to free return value with free(3). + var r = realpath(filename, nil) + if r.isNil: raiseOSError(osLastError(), filename) - elif L > bufsize: - res = newWideCString(L) - bufsize = L else: - result = res$L - break - # getFullPathName doesn't do case corrections, so we have to use this convoluted - # way of retrieving the true filename - for x in walkFiles(result): - result = x - if not fileExists(result) and not dirExists(result): - # consider using: `raiseOSError(osLastError(), result)` - raise newException(OSError, "file '" & result & "' does not exist") - else: - # according to Posix we don't need to allocate space for result pathname. - # But we need to free return value with free(3). - var r = realpath(filename, nil) - if r.isNil: - raiseOSError(osLastError(), filename) + result = $r + c_free(cast[pointer](r)) + + proc createHardlink*(src, dest: string) {.noWeirdTarget.} = + ## Create a hard link at `dest` which points to the item specified + ## by `src`. + ## + ## .. warning:: Some OS's restrict the creation of hard links to + ## root users (administrators). + ## + ## See also: + ## * `createSymlink proc`_ + when defined(windows): + var wSrc = newWideCString(src) + var wDst = newWideCString(dest) + if createHardLinkW(wDst, wSrc, nil) == 0: + raiseOSError(osLastError(), $(src, dest)) else: - result = $r - c_free(cast[pointer](r)) + if link(src, dest) != 0: + raiseOSError(osLastError(), $(src, dest)) -proc getCurrentCompilerExe*(): string {.compileTime.} = - result = "" - discard "implemented in the vmops" - ## Returns the path of the currently running Nim compiler or nimble executable. - ## - ## Can be used to retrieve the currently executing - ## Nim compiler from a Nim or nimscript program, or the nimble binary - ## inside a nimble program (likewise with other binaries built from - ## compiler API). + proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} = + ## Sleeps `milsecs` milliseconds. + ## A negative `milsecs` causes sleep to return immediately. + when defined(windows): + if milsecs < 0: + return # fixes #23732 + winlean.sleep(int32(milsecs)) + else: + var a, b: Timespec = default(Timespec) + a.tv_sec = posix.Time(milsecs div 1000) + a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 + discard posix.nanosleep(a, b) -proc createHardlink*(src, dest: string) {.noWeirdTarget.} = - ## Create a hard link at `dest` which points to the item specified - ## by `src`. - ## - ## .. warning:: Some OS's restrict the creation of hard links to - ## root users (administrators). - ## - ## See also: - ## * `createSymlink proc`_ - when defined(windows): - var wSrc = newWideCString(src) - var wDst = newWideCString(dest) - if createHardLinkW(wDst, wSrc, nil) == 0: - raiseOSError(osLastError(), $(src, dest)) - else: - if link(src, dest) != 0: - raiseOSError(osLastError(), $(src, dest)) + proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", + tags: [ReadIOEffect], noWeirdTarget.} = + ## Returns the file size of `file` (in bytes). ``OSError`` is + ## raised in case of an error. + when defined(windows): + var a: WIN32_FIND_DATA + var resA = findFirstFile(file, a) + if resA == -1: raiseOSError(osLastError(), file) + result = rdFileSize(a) + findClose(resA) + else: + var rawInfo: Stat = default(Stat) + if stat(file, rawInfo) < 0'i32: + raiseOSError(osLastError(), file) + rawInfo.st_size -proc inclFilePermissions*(filename: string, - permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} = - ## A convenience proc for: - ## ```nim - ## setFilePermissions(filename, getFilePermissions(filename)+permissions) - ## ``` - setFilePermissions(filename, getFilePermissions(filename)+permissions) + proc exitStatusLikeShell*(status: cint): cint = + ## Converts exit code from `c_system` into a shell exit code. + when defined(posix) and not weirdTarget: + if WIFSIGNALED(status): + # like the shell! + 128 + WTERMSIG(status) + else: + WEXITSTATUS(status) + else: + status -proc exclFilePermissions*(filename: string, - permissions: set[FilePermission]) {. - rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} = - ## A convenience proc for: - ## ```nim - ## setFilePermissions(filename, getFilePermissions(filename)-permissions) - ## ``` - setFilePermissions(filename, getFilePermissions(filename)-permissions) + proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", + tags: [ExecIOEffect], noWeirdTarget.} = + ## Executes a `shell command`:idx:. + ## + ## Command has the form 'program args' where args are the command + ## line arguments given to program. The proc returns the error code + ## of the shell when it has finished (zero if there is no error). + ## The proc does not return until the process has finished. + ## + ## To execute a program without having a shell involved, use `osproc.execProcess proc + ## `_. + ## + ## **Examples:** + ## ```Nim + ## discard execShellCmd("ls -la") + ## ``` + result = exitStatusLikeShell(c_system(command)) + + proc inclFilePermissions*(filename: string, + permissions: set[FilePermission]) {. + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} = + ## A convenience proc for: + ## ```nim + ## setFilePermissions(filename, getFilePermissions(filename)+permissions) + ## ``` + setFilePermissions(filename, getFilePermissions(filename)+permissions) + + proc exclFilePermissions*(filename: string, + permissions: set[FilePermission]) {. + rtl, extern: "nos$1", tags: [ReadDirEffect, WriteDirEffect], noWeirdTarget.} = + ## A convenience proc for: + ## ```nim + ## setFilePermissions(filename, getFilePermissions(filename)-permissions) + ## ``` + setFilePermissions(filename, getFilePermissions(filename)-permissions) when not weirdTarget and (defined(freebsd) or defined(dragonfly) or defined(netbsd)): proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize_t, @@ -583,7 +601,7 @@ when not weirdTarget and defined(openbsd): else: result = "" -when not (defined(windows) or defined(macosx) or weirdTarget): +when not (defined(windows) or defined(macosx) or weirdTarget) and supportedSystem: proc getApplHeuristic(): string = when declared(paramStr): result = paramStr(0) @@ -627,318 +645,330 @@ when defined(haiku): else: result = "" -proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget, raises: [].} = - ## Returns the filename of the application's executable. - ## This proc will resolve symlinks. - ## - ## Returns empty string when name is unavailable - ## - ## See also: - ## * `getAppDir proc`_ - ## * `getCurrentCompilerExe proc`_ +when supportedSystem: + proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget, raises: [].} = + ## Returns the filename of the application's executable. + ## This proc will resolve symlinks. + ## + ## Returns empty string when name is unavailable + ## + ## See also: + ## * `getAppDir proc`_ + ## * `getCurrentCompilerExe proc`_ - # Linux: /proc//exe - # Solaris: - # /proc//object/a.out (filename only) - # /proc//path/a.out (complete pathname) - when defined(windows): - var bufsize = int32(MAX_PATH) - var buf = newWideCString(bufsize) - while true: - var L = getModuleFileNameW(0, buf, bufsize) - if L == 0'i32: + # Linux: /proc//exe + # Solaris: + # /proc//object/a.out (filename only) + # /proc//path/a.out (complete pathname) + when defined(windows): + var bufsize = int32(MAX_PATH) + var buf = newWideCString(bufsize) + while true: + var L = getModuleFileNameW(0, buf, bufsize) + if L == 0'i32: + result = "" # error! + break + elif L > bufsize: + buf = newWideCString(L) + bufsize = L + else: + result = buf$L + break + elif defined(macosx): + var size = cuint32(0) + getExecPath1(nil, size) + result = newString(int(size)) + if getExecPath2(result.cstring, size): result = "" # error! - break - elif L > bufsize: - buf = newWideCString(L) - bufsize = L - else: - result = buf$L - break - elif defined(macosx): - var size = cuint32(0) - getExecPath1(nil, size) - result = newString(int(size)) - if getExecPath2(result.cstring, size): - result = "" # error! - if result.len > 0: - try: - result = result.expandFilename - except OSError: + if result.len > 0: + try: + result = result.expandFilename + except OSError: + result = "" + else: + when defined(linux) or defined(aix): + result = getApplAux("/proc/self/exe") + elif defined(solaris): + result = getApplAux("/proc/" & $getpid() & "/path/a.out") + elif defined(genode): + result = "" # Not supported + elif defined(freebsd) or defined(dragonfly) or defined(netbsd): + result = getApplFreebsd() + elif defined(haiku): + result = getApplHaiku() + elif defined(openbsd): + result = try: getApplOpenBsd() except OSError: "" + elif defined(nintendoswitch): result = "" - else: - when defined(linux) or defined(aix): - result = getApplAux("/proc/self/exe") - elif defined(solaris): - result = getApplAux("/proc/" & $getpid() & "/path/a.out") - elif defined(genode): - result = "" # Not supported - elif defined(freebsd) or defined(dragonfly) or defined(netbsd): - result = getApplFreebsd() - elif defined(haiku): - result = getApplHaiku() - elif defined(openbsd): - result = try: getApplOpenBsd() except OSError: "" - elif defined(nintendoswitch): - result = "" - # little heuristic that may work on other POSIX-like systems: - if result.len == 0: - result = try: getApplHeuristic() except OSError: "" + # little heuristic that may work on other POSIX-like systems: + if result.len == 0: + result = try: getApplHeuristic() except OSError: "" -proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} = - ## Returns the directory of the application's executable. + proc getAppDir*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noWeirdTarget.} = + ## Returns the directory of the application's executable. + ## + ## See also: + ## * `getAppFilename proc`_ + result = splitFile(getAppFilename()).dir + +proc getCurrentCompilerExe*(): string {.compileTime.} = + result = "" + discard "implemented in the vmops" + ## Returns the path of the currently running Nim compiler or nimble executable. ## - ## See also: - ## * `getAppFilename proc`_ - result = splitFile(getAppFilename()).dir - -proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} = - ## Sleeps `milsecs` milliseconds. - ## A negative `milsecs` causes sleep to return immediately. - when defined(windows): - if milsecs < 0: - return # fixes #23732 - winlean.sleep(int32(milsecs)) - else: - var a, b: Timespec = default(Timespec) - a.tv_sec = posix.Time(milsecs div 1000) - a.tv_nsec = (milsecs mod 1000) * 1000 * 1000 - discard posix.nanosleep(a, b) - -proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", - tags: [ReadIOEffect], noWeirdTarget.} = - ## Returns the file size of `file` (in bytes). ``OSError`` is - ## raised in case of an error. - when defined(windows): - var a: WIN32_FIND_DATA - var resA = findFirstFile(file, a) - if resA == -1: raiseOSError(osLastError(), file) - result = rdFileSize(a) - findClose(resA) - else: - var rawInfo: Stat = default(Stat) - if stat(file, rawInfo) < 0'i32: - raiseOSError(osLastError(), file) - rawInfo.st_size + ## Can be used to retrieve the currently executing + ## Nim compiler from a Nim or nimscript program, or the nimble binary + ## inside a nimble program (likewise with other binaries built from + ## compiler API). when defined(windows) or weirdTarget: type DeviceId* = int32 FileId* = int64 -else: +elif defined(posix): type DeviceId* = Dev FileId* = Ino -type - FileInfo* = object - ## Contains information associated with a file object. - ## - ## See also: - ## * `getFileInfo(handle) proc`_ - ## * `getFileInfo(file) proc`_ - ## * `getFileInfo(path, followSymlink) proc`_ - id*: tuple[device: DeviceId, file: FileId] ## Device and file id. - kind*: PathComponent ## Kind of file object - directory, symlink, etc. - size*: BiggestInt ## Size of file. - permissions*: set[FilePermission] ## File permissions - linkCount*: BiggestInt ## Number of hard links the file object has. - lastAccessTime*: times.Time ## Time file was last accessed. - lastWriteTime*: times.Time ## Time file was last modified/written to. - creationTime*: times.Time ## Time file was created. Not supported on all systems! - blockSize*: int ## Preferred I/O block size for this object. - ## In some filesystems, this may vary from file to file. - isSpecial*: bool ## Is file special? (on Unix some "files" - ## can be special=non-regular like FIFOs, - ## devices); for directories `isSpecial` - ## is always `false`, for symlinks it is - ## the same as for the link's target. - -template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = - ## Transforms the native file info structure into the one nim uses. - ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows, - ## or a 'Stat' structure on posix - when defined(windows): - template merge[T](a, b): untyped = - cast[T]( - (uint64(cast[uint32](a))) or - (uint64(cast[uint32](b)) shl 32) - ) - formalInfo.id.device = rawInfo.dwVolumeSerialNumber - formalInfo.id.file = merge[FileId](rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) - formalInfo.size = merge[BiggestInt](rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh) - formalInfo.linkCount = rawInfo.nNumberOfLinks - formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime)) - formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime)) - formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime)) - formalInfo.blockSize = 8192 # xxx use Windows API instead of hardcoding - - # Retrieve basic permissions - if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32: - formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec, - fpGroupRead, fpOthersExec, fpOthersRead} - else: - formalInfo.permissions = {fpUserExec..fpOthersRead} - - # Retrieve basic file kind - if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: - formalInfo.kind = pcDir - else: - formalInfo.kind = pcFile - if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: - formalInfo.kind = succ(formalInfo.kind) - - else: - template checkAndIncludeMode(rawMode, formalMode: untyped) = - if (rawInfo.st_mode and rawMode.Mode) != 0.Mode: - formalInfo.permissions.incl(formalMode) - formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) - formalInfo.size = rawInfo.st_size - formalInfo.linkCount = rawInfo.st_nlink.BiggestInt - formalInfo.lastAccessTime = rawInfo.st_atim.toTime - formalInfo.lastWriteTime = rawInfo.st_mtim.toTime - formalInfo.creationTime = rawInfo.st_ctim.toTime - formalInfo.blockSize = rawInfo.st_blksize - - formalInfo.permissions = {} - checkAndIncludeMode(S_IRUSR, fpUserRead) - checkAndIncludeMode(S_IWUSR, fpUserWrite) - checkAndIncludeMode(S_IXUSR, fpUserExec) - - checkAndIncludeMode(S_IRGRP, fpGroupRead) - checkAndIncludeMode(S_IWGRP, fpGroupWrite) - checkAndIncludeMode(S_IXGRP, fpGroupExec) - - checkAndIncludeMode(S_IROTH, fpOthersRead) - checkAndIncludeMode(S_IWOTH, fpOthersWrite) - checkAndIncludeMode(S_IXOTH, fpOthersExec) - - (formalInfo.kind, formalInfo.isSpecial) = - if S_ISDIR(rawInfo.st_mode): - (pcDir, false) - elif S_ISLNK(rawInfo.st_mode): - assert(path != "") # symlinks can't occur for file handles - getSymlinkFileKind(path) - else: - (pcFile, not S_ISREG(rawInfo.st_mode)) - when defined(js): when not declared(FileHandle): type FileHandle = distinct int32 when not declared(File): type File = object -proc getFileInfo*(handle: FileHandle): FileInfo {.noWeirdTarget.} = - ## Retrieves file information for the file object represented by the given - ## handle. - ## - ## If the information cannot be retrieved, such as when the file handle - ## is invalid, `OSError` is raised. - ## - ## See also: - ## * `getFileInfo(file) proc`_ - ## * `getFileInfo(path, followSymlink) proc`_ +when weirdTarget or defined(windows) or defined(posix) or defined(nintendoswitch): + type + FileInfo* = object + ## Contains information associated with a file object. + ## + ## See also: + ## * `getFileInfo(handle) proc`_ + ## * `getFileInfo(file) proc`_ + ## * `getFileInfo(path, followSymlink) proc`_ + id*: tuple[device: DeviceId, file: FileId] ## Device and file id. + kind*: PathComponent ## Kind of file object - directory, symlink, etc. + size*: BiggestInt ## Size of file. + permissions*: set[FilePermission] ## File permissions + linkCount*: BiggestInt ## Number of hard links the file object has. + lastAccessTime*: times.Time ## Time file was last accessed. + lastWriteTime*: times.Time ## Time file was last modified/written to. + creationTime*: times.Time ## Time file was created. Not supported on all systems! + blockSize*: int ## Preferred I/O block size for this object. + ## In some filesystems, this may vary from file to file. + isSpecial*: bool ## Is file special? (on Unix some "files" + ## can be special=non-regular like FIFOs, + ## devices); for directories `isSpecial` + ## is always `false`, for symlinks it is + ## the same as for the link's target. - # Done: ID, Kind, Size, Permissions, Link Count - result = default(FileInfo) - when defined(windows): - var rawInfo: BY_HANDLE_FILE_INFORMATION - # We have to use the super special '_get_osfhandle' call (wrapped above) - # To transform the C file descriptor to a native file handle. - var realHandle = get_osfhandle(handle) - if getFileInformationByHandle(realHandle, addr rawInfo) == 0: - raiseOSError(osLastError(), $handle) - rawToFormalFileInfo(rawInfo, "", result) - else: - var rawInfo: Stat = default(Stat) - if fstat(handle, rawInfo) < 0'i32: - raiseOSError(osLastError(), $handle) - rawToFormalFileInfo(rawInfo, "", result) + template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = + ## Transforms the native file info structure into the one nim uses. + ## 'rawInfo' is either a 'BY_HANDLE_FILE_INFORMATION' structure on Windows, + ## or a 'Stat' structure on posix + when defined(windows): + template merge[T](a, b): untyped = + cast[T]( + (uint64(cast[uint32](a))) or + (uint64(cast[uint32](b)) shl 32) + ) + formalInfo.id.device = rawInfo.dwVolumeSerialNumber + formalInfo.id.file = merge[FileId](rawInfo.nFileIndexLow, rawInfo.nFileIndexHigh) + formalInfo.size = merge[BiggestInt](rawInfo.nFileSizeLow, rawInfo.nFileSizeHigh) + formalInfo.linkCount = rawInfo.nNumberOfLinks + formalInfo.lastAccessTime = fromWinTime(rdFileTime(rawInfo.ftLastAccessTime)) + formalInfo.lastWriteTime = fromWinTime(rdFileTime(rawInfo.ftLastWriteTime)) + formalInfo.creationTime = fromWinTime(rdFileTime(rawInfo.ftCreationTime)) + formalInfo.blockSize = 8192 # xxx use Windows API instead of hardcoding -proc getFileInfo*(file: File): FileInfo {.noWeirdTarget.} = - ## Retrieves file information for the file object. - ## - ## See also: - ## * `getFileInfo(handle) proc`_ - ## * `getFileInfo(path, followSymlink) proc`_ - if file.isNil: - raise newException(IOError, "File is nil") - result = getFileInfo(file.getFileHandle()) + # Retrieve basic permissions + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_READONLY) != 0'i32: + formalInfo.permissions = {fpUserExec, fpUserRead, fpGroupExec, + fpGroupRead, fpOthersExec, fpOthersRead} + else: + formalInfo.permissions = {fpUserExec..fpOthersRead} + + # Retrieve basic file kind + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32: + formalInfo.kind = pcDir + else: + formalInfo.kind = pcFile + if (rawInfo.dwFileAttributes and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32: + formalInfo.kind = succ(formalInfo.kind) -proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.} = - ## Retrieves file information for the file object pointed to by `path`. - ## - ## Due to intrinsic differences between operating systems, the information - ## contained by the returned `FileInfo object`_ will be slightly - ## different across platforms, and in some cases, incomplete or inaccurate. - ## - ## When `followSymlink` is true (default), symlinks are followed and the - ## information retrieved is information related to the symlink's target. - ## Otherwise, information on the symlink itself is retrieved (however, - ## field `isSpecial` is still determined from the target on Unix). - ## - ## If the information cannot be retrieved, such as when the path doesn't - ## exist, or when permission restrictions prevent the program from retrieving - ## file information, `OSError` is raised. - ## - ## See also: - ## * `getFileInfo(handle) proc`_ - ## * `getFileInfo(file) proc`_ - result = default(FileInfo) - when defined(windows): - var - handle = openHandle(path, followSymlink) - rawInfo: BY_HANDLE_FILE_INFORMATION - if handle == INVALID_HANDLE_VALUE: - raiseOSError(osLastError(), path) - if getFileInformationByHandle(handle, addr rawInfo) == 0: - raiseOSError(osLastError(), path) - rawToFormalFileInfo(rawInfo, path, result) - discard closeHandle(handle) - else: - var rawInfo: Stat = default(Stat) - if followSymlink: - if stat(path, rawInfo) < 0'i32: - raiseOSError(osLastError(), path) else: - if lstat(path, rawInfo) < 0'i32: - raiseOSError(osLastError(), path) - rawToFormalFileInfo(rawInfo, path, result) + template checkAndIncludeMode(rawMode, formalMode: untyped) = + if (rawInfo.st_mode and rawMode.Mode) != 0.Mode: + formalInfo.permissions.incl(formalMode) + formalInfo.id = (rawInfo.st_dev, rawInfo.st_ino) + formalInfo.size = rawInfo.st_size + formalInfo.linkCount = rawInfo.st_nlink.BiggestInt + formalInfo.lastAccessTime = rawInfo.st_atim.toTime + formalInfo.lastWriteTime = rawInfo.st_mtim.toTime + formalInfo.creationTime = rawInfo.st_ctim.toTime + formalInfo.blockSize = rawInfo.st_blksize -proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadIOEffect], noWeirdTarget.} = - ## Returns true if both pathname arguments refer to files with identical - ## binary content. - ## - ## See also: - ## * `sameFile proc`_ - result = false - var - a, b: File = default(File) - if not open(a, path1): return false - if not open(b, path2): + formalInfo.permissions = {} + checkAndIncludeMode(S_IRUSR, fpUserRead) + checkAndIncludeMode(S_IWUSR, fpUserWrite) + checkAndIncludeMode(S_IXUSR, fpUserExec) + + checkAndIncludeMode(S_IRGRP, fpGroupRead) + checkAndIncludeMode(S_IWGRP, fpGroupWrite) + checkAndIncludeMode(S_IXGRP, fpGroupExec) + + checkAndIncludeMode(S_IROTH, fpOthersRead) + checkAndIncludeMode(S_IWOTH, fpOthersWrite) + checkAndIncludeMode(S_IXOTH, fpOthersExec) + + (formalInfo.kind, formalInfo.isSpecial) = + if S_ISDIR(rawInfo.st_mode): + (pcDir, false) + elif S_ISLNK(rawInfo.st_mode): + assert(path != "") # symlinks can't occur for file handles + getSymlinkFileKind(path) + else: + (pcFile, not S_ISREG(rawInfo.st_mode)) + + proc getFileInfo*(handle: FileHandle): FileInfo {.noWeirdTarget.} = + ## Retrieves file information for the file object represented by the given + ## handle. + ## + ## If the information cannot be retrieved, such as when the file handle + ## is invalid, `OSError` is raised. + ## + ## See also: + ## * `getFileInfo(file) proc`_ + ## * `getFileInfo(path, followSymlink) proc`_ + + # Done: ID, Kind, Size, Permissions, Link Count + result = default(FileInfo) + when defined(windows): + var rawInfo: BY_HANDLE_FILE_INFORMATION + # We have to use the super special '_get_osfhandle' call (wrapped above) + # To transform the C file descriptor to a native file handle. + var realHandle = get_osfhandle(handle) + if getFileInformationByHandle(realHandle, addr rawInfo) == 0: + raiseOSError(osLastError(), $handle) + rawToFormalFileInfo(rawInfo, "", result) + else: + var rawInfo: Stat = default(Stat) + if fstat(handle, rawInfo) < 0'i32: + raiseOSError(osLastError(), $handle) + rawToFormalFileInfo(rawInfo, "", result) + + proc getFileInfo*(file: File): FileInfo {.noWeirdTarget.} = + ## Retrieves file information for the file object. + ## + ## See also: + ## * `getFileInfo(handle) proc`_ + ## * `getFileInfo(path, followSymlink) proc`_ + if file.isNil: + raise newException(IOError, "File is nil") + result = getFileInfo(file.getFileHandle()) + + proc getFileInfo*(path: string, followSymlink = true): FileInfo {.noWeirdTarget.} = + ## Retrieves file information for the file object pointed to by `path`. + ## + ## Due to intrinsic differences between operating systems, the information + ## contained by the returned `FileInfo object`_ will be slightly + ## different across platforms, and in some cases, incomplete or inaccurate. + ## + ## When `followSymlink` is true (default), symlinks are followed and the + ## information retrieved is information related to the symlink's target. + ## Otherwise, information on the symlink itself is retrieved (however, + ## field `isSpecial` is still determined from the target on Unix). + ## + ## If the information cannot be retrieved, such as when the path doesn't + ## exist, or when permission restrictions prevent the program from retrieving + ## file information, `OSError` is raised. + ## + ## See also: + ## * `getFileInfo(handle) proc`_ + ## * `getFileInfo(file) proc`_ + result = default(FileInfo) + when defined(windows): + var + handle = openHandle(path, followSymlink) + rawInfo: BY_HANDLE_FILE_INFORMATION + if handle == INVALID_HANDLE_VALUE: + raiseOSError(osLastError(), path) + if getFileInformationByHandle(handle, addr rawInfo) == 0: + raiseOSError(osLastError(), path) + rawToFormalFileInfo(rawInfo, path, result) + discard closeHandle(handle) + else: + var rawInfo: Stat = default(Stat) + if followSymlink: + if stat(path, rawInfo) < 0'i32: + raiseOSError(osLastError(), path) + else: + if lstat(path, rawInfo) < 0'i32: + raiseOSError(osLastError(), path) + rawToFormalFileInfo(rawInfo, path, result) + + proc sameFileContent*(path1, path2: string): bool {.rtl, extern: "nos$1", + tags: [ReadIOEffect], noWeirdTarget.} = + ## Returns true if both pathname arguments refer to files with identical + ## binary content. + ## + ## See also: + ## * `sameFile proc`_ + result = false + var + a, b: File = default(File) + if not open(a, path1): return false + if not open(b, path2): + close(a) + return false + let bufSize = getFileInfo(a).blockSize + var bufA = alloc(bufSize) + var bufB = alloc(bufSize) + while true: + var readA = readBuffer(a, bufA, bufSize) + var readB = readBuffer(b, bufB, bufSize) + if readA != readB: + result = false + break + if readA == 0: + result = true + break + result = equalMem(bufA, bufB, readA) + if not result: break + if readA != bufSize: break # end of file + dealloc(bufA) + dealloc(bufB) close(a) - return false - let bufSize = getFileInfo(a).blockSize - var bufA = alloc(bufSize) - var bufB = alloc(bufSize) - while true: - var readA = readBuffer(a, bufA, bufSize) - var readB = readBuffer(b, bufB, bufSize) - if readA != readB: - result = false - break - if readA == 0: - result = true - break - result = equalMem(bufA, bufB, readA) - if not result: break - if readA != bufSize: break # end of file - dealloc(bufA) - dealloc(bufB) - close(a) - close(b) + close(b) + + proc getCurrentProcessId*(): int {.noWeirdTarget.} = + ## Return current process ID. + ## + ## See also: + ## * `osproc.processID(p: Process) `_ + when defined(windows): + proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32", + importc: "GetCurrentProcessId".} + result = GetCurrentProcessId().int + else: + result = getpid() + + proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} = + ## Sets the `file`'s last modification time. `OSError` is raised in case of + ## an error. + when defined(posix): + let unixt = posix.Time(t.toUnix) + let micro = convert(Nanoseconds, Microseconds, t.nanosecond) + var timevals = [Timeval(tv_sec: unixt, tv_usec: micro), + Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification] + if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file) + else: + let h = openHandle(path = file, writeAccess = true) + if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError(), file) + var ft = t.toWinTime.toFILETIME + let res = setFileTime(h, nil, nil, ft.addr) + discard h.closeHandle + if res == 0'i32: raiseOSError(osLastError(), file) proc isHidden*(path: string): bool {.noWeirdTarget.} = ## Determines whether ``path`` is hidden or not, using `this @@ -967,35 +997,6 @@ proc isHidden*(path: string): bool {.noWeirdTarget.} = let fileName = lastPathPart(path) result = len(fileName) >= 2 and fileName[0] == '.' and fileName != ".." -proc getCurrentProcessId*(): int {.noWeirdTarget.} = - ## Return current process ID. - ## - ## See also: - ## * `osproc.processID(p: Process) `_ - when defined(windows): - proc GetCurrentProcessId(): DWORD {.stdcall, dynlib: "kernel32", - importc: "GetCurrentProcessId".} - result = GetCurrentProcessId().int - else: - result = getpid() - -proc setLastModificationTime*(file: string, t: times.Time) {.noWeirdTarget.} = - ## Sets the `file`'s last modification time. `OSError` is raised in case of - ## an error. - when defined(posix): - let unixt = posix.Time(t.toUnix) - let micro = convert(Nanoseconds, Microseconds, t.nanosecond) - var timevals = [Timeval(tv_sec: unixt, tv_usec: micro), - Timeval(tv_sec: unixt, tv_usec: micro)] # [last access, last modification] - if utimes(file, timevals.addr) != 0: raiseOSError(osLastError(), file) - else: - let h = openHandle(path = file, writeAccess = true) - if h == INVALID_HANDLE_VALUE: raiseOSError(osLastError(), file) - var ft = t.toWinTime.toFILETIME - let res = setFileTime(h, nil, nil, ft.addr) - discard h.closeHandle - if res == 0'i32: raiseOSError(osLastError(), file) - func isValidFilename*(filename: string, maxLen = 259.Positive): bool {.since: (1, 1).} = ## Returns `true` if `filename` is valid for crossplatform use. diff --git a/lib/std/cmdline.nim b/lib/std/cmdline.nim index dcf6e0f4ac..140c458f22 100644 --- a/lib/std/cmdline.nim +++ b/lib/std/cmdline.nim @@ -19,7 +19,7 @@ include system/inclrtl when defined(nimPreviewSlimSystem): import std/widestrs - + when defined(nodejs): from std/private/oscommon import ReadDirEffect @@ -33,8 +33,6 @@ elif defined(windows): import std/winlean elif defined(posix): import std/posix -else: - {.error: "The cmdline module has not been implemented for the target platform.".} # Needed by windows in order to obtain the command line for targets diff --git a/lib/std/private/oscommon.nim b/lib/std/private/oscommon.nim index c49d52ef29..6e0214dcb6 100644 --- a/lib/std/private/oscommon.nim +++ b/lib/std/private/oscommon.nim @@ -5,9 +5,13 @@ import std/[oserrors] when defined(nimPreviewSlimSystem): import std/[syncio, assertions, widestrs] +from std/staticos import PathComponent + ## .. importdoc:: osdirs.nim, os.nim -const weirdTarget* = defined(nimscript) or defined(js) +const + weirdTarget* = defined(nimscript) or defined(js) + supportedSystem* = weirdTarget or defined(windows) or defined(posix) type @@ -27,8 +31,6 @@ elif defined(posix): import std/posix proc c_rename(oldname, newname: cstring): cint {. importc: "rename", header: "".} -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: @@ -65,122 +67,110 @@ when defined(windows) and not weirdTarget: result = f.cFileName[0].int == dot and (f.cFileName[1].int == 0 or f.cFileName[1].int == dot and f.cFileName[2].int == 0) +when supportedSystem: + when defined(posix) and not weirdTarget: + proc getSymlinkFileKind*(path: string): + tuple[pc: PathComponent, isSpecial: bool] = + # Helper function. + var s: Stat + assert(path != "") + result = (pcLinkToFile, false) + if stat(path, s) == 0'i32: + if S_ISDIR(s.st_mode): + result = (pcLinkToDir, false) + elif not S_ISREG(s.st_mode): + result = (pcLinkToFile, true) -type - PathComponent* = enum ## Enumeration specifying a path component. + proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} = + ## Moves a file (or directory if `isDir` is true) from `source` to `dest`. + ## + ## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true). + ## In case of other errors `OSError` is raised. + ## Returns true in case of success. + when defined(windows): + let s = newWideCString(source) + let d = newWideCString(dest) + result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 + else: + result = c_rename(source, dest) == 0'i32 + + if not result: + let err = osLastError() + let isAccessDeniedError = + when defined(windows): + const AccessDeniedError = OSErrorCode(5) + isDir and err == AccessDeniedError + else: + err == EXDEV.OSErrorCode + if not isAccessDeniedError: + raiseOSError(err, $(source, dest)) + + when not defined(windows): + const maxSymlinkLen* = 1024 + + proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noNimJs, sideEffect.} = + ## Returns true if `filename` exists and is a regular file or symlink. + ## + ## Directories, device files, named pipes and sockets return false. ## ## See also: - ## * `walkDirRec iterator`_ - ## * `FileInfo object`_ - pcFile, ## path refers to a file - pcLinkToFile, ## path refers to a symbolic link to a file - pcDir, ## path refers to a directory - pcLinkToDir ## path refers to a symbolic link to a directory + ## * `dirExists proc`_ + ## * `symlinkExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, filename) + if a != -1'i32: + result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 + else: + var res: Stat + return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) -when defined(posix) and not weirdTarget: - proc getSymlinkFileKind*(path: string): - tuple[pc: PathComponent, isSpecial: bool] = - # Helper function. - var s: Stat - assert(path != "") - result = (pcLinkToFile, false) - if stat(path, s) == 0'i32: - if S_ISDIR(s.st_mode): - result = (pcLinkToDir, false) - elif not S_ISREG(s.st_mode): - result = (pcLinkToFile, true) - -proc tryMoveFSObject*(source, dest: string, isDir: bool): bool {.noWeirdTarget.} = - ## Moves a file (or directory if `isDir` is true) from `source` to `dest`. - ## - ## Returns false in case of `EXDEV` error or `AccessDeniedError` on Windows (if `isDir` is true). - ## In case of other errors `OSError` is raised. - ## Returns true in case of success. - when defined(windows): - let s = newWideCString(source) - let d = newWideCString(dest) - result = moveFileExW(s, d, MOVEFILE_COPY_ALLOWED or MOVEFILE_REPLACE_EXISTING) != 0'i32 - else: - result = c_rename(source, dest) == 0'i32 - - if not result: - let err = osLastError() - let isAccessDeniedError = - when defined(windows): - const AccessDeniedError = OSErrorCode(5) - isDir and err == AccessDeniedError - else: - err == EXDEV.OSErrorCode - if not isAccessDeniedError: - raiseOSError(err, $(source, dest)) - -when not defined(windows): - const maxSymlinkLen* = 1024 - -proc fileExists*(filename: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noNimJs, sideEffect.} = - ## Returns true if `filename` exists and is a regular file or symlink. - ## - ## Directories, device files, named pipes and sockets return false. - ## - ## See also: - ## * `dirExists proc`_ - ## * `symlinkExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, filename) - if a != -1'i32: - result = (a and FILE_ATTRIBUTE_DIRECTORY) == 0'i32 - else: - var res: Stat - return stat(filename, res) >= 0'i32 and S_ISREG(res.st_mode) + proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], + noNimJs, sideEffect.} = + ## Returns true if the directory `dir` exists. If `dir` is a file, false + ## is returned. Follows symlinks. + ## + ## See also: + ## * `fileExists proc`_ + ## * `symlinkExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, dir) + if a != -1'i32: + result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 + else: + var res: Stat + result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) -proc dirExists*(dir: string): bool {.rtl, extern: "nos$1", tags: [ReadDirEffect], - noNimJs, sideEffect.} = - ## Returns true if the directory `dir` exists. If `dir` is a file, false - ## is returned. Follows symlinks. - ## - ## See also: - ## * `fileExists proc`_ - ## * `symlinkExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, dir) - if a != -1'i32: - result = (a and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 - else: - var res: Stat - result = stat(dir, res) >= 0'i32 and S_ISDIR(res.st_mode) + proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], + noWeirdTarget, sideEffect.} = + ## Returns true if the symlink `link` exists. Will return true + ## regardless of whether the link points to a directory or file. + ## + ## See also: + ## * `fileExists proc`_ + ## * `dirExists proc`_ + when defined(windows): + wrapUnary(a, getFileAttributesW, link) + if a != -1'i32: + # xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK` + # may also be needed. + result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 + else: + var res: Stat + result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) + when defined(windows) and not weirdTarget: + proc openHandle*(path: string, followSymlink=true, writeAccess=false): Handle = + var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL + if not followSymlink: + flags = flags or FILE_FLAG_OPEN_REPARSE_POINT + let access = if writeAccess: GENERIC_WRITE else: 0'i32 -proc symlinkExists*(link: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], - noWeirdTarget, sideEffect.} = - ## Returns true if the symlink `link` exists. Will return true - ## regardless of whether the link points to a directory or file. - ## - ## See also: - ## * `fileExists proc`_ - ## * `dirExists proc`_ - when defined(windows): - wrapUnary(a, getFileAttributesW, link) - if a != -1'i32: - # xxx see: bug #16784 (bug9); checking `IO_REPARSE_TAG_SYMLINK` - # may also be needed. - result = (a and FILE_ATTRIBUTE_REPARSE_POINT) != 0'i32 - else: - var res: Stat - result = lstat(link, res) >= 0'i32 and S_ISLNK(res.st_mode) - -when defined(windows) and not weirdTarget: - proc openHandle*(path: string, followSymlink=true, writeAccess=false): Handle = - var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL - if not followSymlink: - flags = flags or FILE_FLAG_OPEN_REPARSE_POINT - let access = if writeAccess: GENERIC_WRITE else: 0'i32 - - result = createFileW( - newWideCString(path), access, - FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, - nil, OPEN_EXISTING, flags, 0 - ) + result = createFileW( + newWideCString(path), access, + FILE_SHARE_DELETE or FILE_SHARE_READ or FILE_SHARE_WRITE, + nil, OPEN_EXISTING, flags, 0 + ) diff --git a/lib/std/private/osdirs.nim b/lib/std/private/osdirs.nim index a44cad7d94..5c6aa3e4d9 100644 --- a/lib/std/private/osdirs.nim +++ b/lib/std/private/osdirs.nim @@ -6,7 +6,9 @@ import std/oserrors import ospaths2, osfiles import oscommon -export dirExists, PathComponent +import std/staticos +when supportedSystem: + export dirExists, PathComponent when defined(nimPreviewSlimSystem): @@ -20,9 +22,6 @@ elif defined(windows): elif defined(posix): import std/[posix, times] -else: - {.error: "OS module not ported to your operating system!".} - when weirdTarget: {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} @@ -152,10 +151,6 @@ iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noWeirdTarg assert "lib/pure/concurrency".unixToNativePath in paths walkCommon(pattern, isDir) -proc staticWalkDir(dir: string; relative: bool): seq[ - tuple[kind: PathComponent, path: string]] = - discard - iterator walkDir*(dir: string; relative = false, checkDir = false, skipSpecial = false): tuple[kind: PathComponent, path: string] {.tags: [ReadDirEffect].} = @@ -325,7 +320,6 @@ iterator walkDirRec*(dir: string, # continue iteration. # Future work can provide a way to customize this and do error reporting. - proc rawRemoveDir(dir: string) {.noWeirdTarget.} = when defined(windows): wrapUnary(res, removeDirectoryW, dir) diff --git a/lib/std/private/osfiles.nim b/lib/std/private/osfiles.nim index 37d8eabca2..e166dde981 100644 --- a/lib/std/private/osfiles.nim +++ b/lib/std/private/osfiles.nim @@ -21,8 +21,6 @@ elif defined(posix): proc toTime(ts: Timespec): times.Time {.inline.} = result = initTime(ts.tv_sec.int64, ts.tv_nsec.int) -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: diff --git a/lib/std/private/ospaths2.nim b/lib/std/private/ospaths2.nim index b43576424d..43185f50a0 100644 --- a/lib/std/private/ospaths2.nim +++ b/lib/std/private/ospaths2.nim @@ -20,8 +20,6 @@ elif defined(windows): import std/winlean elif defined(posix): import std/posix, system/ansi_c -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: {.pragma: noWeirdTarget, error: "this proc is not available on the NimScript/js target".} @@ -840,7 +838,7 @@ proc unixToNativePath*(path: string, drive=""): string {. inc(i) -when not defined(nimscript): +when not defined(nimscript) and supportedSystem: proc getCurrentDir*(): string {.rtl, extern: "nos$1", tags: [].} = ## Returns the `current working directory`:idx: i.e. where the built ## binary is run. @@ -889,7 +887,7 @@ when not defined(nimscript): else: raiseOSError(osLastError()) -proc absolutePath*(path: string, root = getCurrentDir()): string = +proc absolutePath*(path: string, root = when supportedSystem: getCurrentDir() else: ""): string = ## Returns the absolute path of `path`, rooted at `root` (which must be absolute; ## default: current directory). ## If `path` is absolute, return it, ignoring `root`. @@ -907,7 +905,7 @@ proc absolutePath*(path: string, root = getCurrentDir()): string = joinPath(root, path) proc absolutePathInternal(path: string): string = - absolutePath(path, getCurrentDir()) + absolutePath(path) proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [].} = @@ -984,48 +982,49 @@ proc normalizeExe*(file: var string) {.since: (1, 3, 5).} = if file.len > 0 and DirSep notin file and file != "." and file != "..": file = "./" & file -proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noWeirdTarget.} = - ## Returns true if both pathname arguments refer to the same physical - ## file or directory. - ## - ## Raises `OSError` if any of the files does not - ## exist or information about it can not be obtained. - ## - ## This proc will return true if given two alternative hard-linked or - ## sym-linked paths to the same file or directory. - ## - ## See also: - ## * `sameFileContent proc`_ - result = false - when defined(windows): - var success = true - var f1 = openHandle(path1) - var f2 = openHandle(path2) +when supportedSystem: + proc sameFile*(path1, path2: string): bool {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noWeirdTarget.} = + ## Returns true if both pathname arguments refer to the same physical + ## file or directory. + ## + ## Raises `OSError` if any of the files does not + ## exist or information about it can not be obtained. + ## + ## This proc will return true if given two alternative hard-linked or + ## sym-linked paths to the same file or directory. + ## + ## See also: + ## * `sameFileContent proc`_ + result = false + when defined(windows): + var success = true + var f1 = openHandle(path1) + var f2 = openHandle(path2) - var lastErr: OSErrorCode - if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: - var fi1, fi2: BY_HANDLE_FILE_INFORMATION + var lastErr: OSErrorCode + if f1 != INVALID_HANDLE_VALUE and f2 != INVALID_HANDLE_VALUE: + var fi1, fi2: BY_HANDLE_FILE_INFORMATION - if getFileInformationByHandle(f1, addr(fi1)) != 0 and - getFileInformationByHandle(f2, addr(fi2)) != 0: - result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and - fi1.nFileIndexHigh == fi2.nFileIndexHigh and - fi1.nFileIndexLow == fi2.nFileIndexLow + if getFileInformationByHandle(f1, addr(fi1)) != 0 and + getFileInformationByHandle(f2, addr(fi2)) != 0: + result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber and + fi1.nFileIndexHigh == fi2.nFileIndexHigh and + fi1.nFileIndexLow == fi2.nFileIndexLow + else: + lastErr = osLastError() + success = false else: lastErr = osLastError() success = false - else: - lastErr = osLastError() - success = false - discard closeHandle(f1) - discard closeHandle(f2) + discard closeHandle(f1) + discard closeHandle(f2) - if not success: raiseOSError(lastErr, $(path1, path2)) - else: - var a, b: Stat - if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: - raiseOSError(osLastError(), $(path1, path2)) + if not success: raiseOSError(lastErr, $(path1, path2)) else: - result = a.st_dev == b.st_dev and a.st_ino == b.st_ino + var a, b: Stat + if stat(path1, a) < 0'i32 or stat(path2, b) < 0'i32: + raiseOSError(osLastError(), $(path1, path2)) + else: + result = a.st_dev == b.st_dev and a.st_ino == b.st_ino diff --git a/lib/std/private/ossymlinks.nim b/lib/std/private/ossymlinks.nim index c1760c42ec..9e915a1e4d 100644 --- a/lib/std/private/ossymlinks.nim +++ b/lib/std/private/ossymlinks.nim @@ -2,7 +2,8 @@ include system/inclrtl import std/oserrors import oscommon -export symlinkExists +when supportedSystem: + export symlinkExists when defined(nimPreviewSlimSystem): import std/[syncio, assertions, widestrs] @@ -13,8 +14,6 @@ elif defined(windows): import std/[winlean, times] elif defined(posix): import std/posix -else: - {.error: "OS module not ported to your operating system!".} when weirdTarget: diff --git a/lib/std/staticos.nim b/lib/std/staticos.nim index f9fe265ed2..4237517240 100644 --- a/lib/std/staticos.nim +++ b/lib/std/staticos.nim @@ -14,3 +14,25 @@ proc staticDirExists*(dir: string): bool {.compileTime.} = ## Returns true if the directory `dir` exists. If `dir` is a file, false ## is returned. Follows symlinks. raiseAssert "implemented in the vmops" + +type + PathComponent* = enum ## Enumeration specifying a path component. + ## + ## See also: + ## * `walkDirRec iterator`_ + ## * `FileInfo object`_ + pcFile, ## path refers to a file + pcLinkToFile, ## path refers to a symbolic link to a file + pcDir, ## path refers to a directory + pcLinkToDir ## path refers to a symbolic link to a directory + +proc staticWalkDir*(dir: string; relative = false): seq[ + tuple[kind: PathComponent, path: string]] {.compileTime.} = + ## Walks over the directory `dir` and returns a seq with each directory or + ## file in `dir`. The component type and full path for each item are returned. + ## + ## Walking is not recursive. + ## * If `relative` is true (default: false) + ## the resulting path is shortened to be relative to ``dir``, + ## otherwise the full path is returned. + raiseAssert "implemented in the vmops" From 95b1dda1db86881f0119bd319380790fbd594ac5 Mon Sep 17 00:00:00 2001 From: Tomohiro Date: Sat, 25 Jan 2025 23:43:40 +0900 Subject: [PATCH 15/48] Fix parseBiggestUInt to detect overflow (#24649) With some inputs larger than `BiggestUInt.high`, `parseBiggestUInt` proc in `parseutils.nim` fails to detect overflow and returns random value. This is because `rawParseUInt` try to detects overflow with `if prev > res:` but it doesn't detects the overflow from multiplication. It is possible that `x *= 10` causes overflow and resulting value is larger than original value. Here is example values larger than `BiggestUInt.high` but `parseBiggestUInt` returns without detecting overflow: ``` 22751622367522324480000000 41404969074137497600000000 20701551093035827200000000000000000 22546225502460313600000000000000000 204963831854661632000000000000000000 ``` Following code search for values larger than `BiggestUInt.high` and `parseBiggestUInt` cannot detect overflow: ```nim import std/[strutils] const # Increase this to extend search range NBits = 34'u NBitsMax1 = 1'u shl NBits NBitsMax = NBitsMax1 - 1'u # Increase this when there are too many results and want to see only larger result. MinMultiply10 = 14 var nfound = 0 for i in (NBitsMax div 10'u + 1'u) .. NBitsMax: var x = i n10 = 0 for j in 0 ..< NBits: let px = x x = (x * 10'u) and NBitsMax if x < px: break inc n10 if n10 >= MinMultiply10: echo "i = ", i echo "uint: ", (i shl (64'u - NBits)), '0'.repeat n10 inc nfound if nfound > 15: break echo "found: ", nfound ``` --- lib/pure/parseutils.nim | 8 +++--- tests/stdlib/tparseuints.nim | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index 05a0f74c46..f423b23892 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -527,7 +527,6 @@ proc parseSaturatedNatural*(s: openArray[char], b: var int): int {. proc rawParseUInt(s: openArray[char], b: var BiggestUInt): int = var res = 0.BiggestUInt - prev = 0.BiggestUInt i = 0 if i < s.len - 1 and s[i] == '-' and s[i + 1] in {'0'..'9'}: integerOutOfRangeError() @@ -535,8 +534,11 @@ proc rawParseUInt(s: openArray[char], b: var BiggestUInt): int = if i < s.len and s[i] in {'0'..'9'}: b = 0 while i < s.len and s[i] in {'0'..'9'}: - prev = res - res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt + if res > BiggestUInt.high div 10: # Highest value that you can multiply 10 without overflow + integerOutOfRangeError() + res = res * 10 + let prev = res + res += (ord(s[i]) - ord('0')).BiggestUInt if prev > res: integerOutOfRangeError() inc(i) diff --git a/tests/stdlib/tparseuints.nim b/tests/stdlib/tparseuints.nim index 9c71a27d65..9f22436332 100644 --- a/tests/stdlib/tparseuints.nim +++ b/tests/stdlib/tparseuints.nim @@ -6,6 +6,54 @@ import unittest, strutils block: # parseutils check: parseBiggestUInt("0") == 0'u64 + check: parseBiggestUInt("1") == 1'u64 + check: parseBiggestUInt("2") == 2'u64 + check: parseBiggestUInt("10") == 10'u64 + check: parseBiggestUInt("11") == 11'u64 + check: parseBiggestUInt("99") == 99'u64 + check: parseBiggestUInt("123") == 123'u64 + check: parseBiggestUInt("9876") == 9876'u64 + check: parseBiggestUInt("1_234") == 1234'u64 + check: parseBiggestUInt("123__4") == 1234'u64 + for i in 1.BiggestUInt .. 9.BiggestUInt: + var x = i + for j in 1 .. 19: + check parseBiggestUInt((i + '0'.uint).char.repeat j) == x + x *= 10 + x += i + check: parseBiggestUInt("18446744073709551609") == 0xFFFF_FFFF_FFFF_FFF9'u64 + check: parseBiggestUInt("18446744073709551610") == 0xFFFF_FFFF_FFFF_FFFA'u64 + check: parseBiggestUInt("18446744073709551611") == 0xFFFF_FFFF_FFFF_FFFB'u64 + check: parseBiggestUInt("18446744073709551612") == 0xFFFF_FFFF_FFFF_FFFC'u64 + check: parseBiggestUInt("18446744073709551613") == 0xFFFF_FFFF_FFFF_FFFD'u64 + check: parseBiggestUInt("18446744073709551614") == 0xFFFF_FFFF_FFFF_FFFE'u64 check: parseBiggestUInt("18446744073709551615") == 0xFFFF_FFFF_FFFF_FFFF'u64 expect(ValueError): discard parseBiggestUInt("18446744073709551616") + expect(ValueError): + discard parseBiggestUInt("18446744073709551617") + expect(ValueError): + discard parseBiggestUInt("18446744073709551618") + expect(ValueError): + discard parseBiggestUInt("18446744073709551619") + expect(ValueError): + discard parseBiggestUInt("18446744073709551620") + expect(ValueError): + discard parseBiggestUInt("18446744073709551621") + expect(ValueError): + discard parseBiggestUInt("18446744073709551622") + expect(ValueError): + discard parseBiggestUInt("18446744073709551623") + expect(ValueError): + for i in 0 .. 999: + discard parseBiggestUInt("18446744073709552" & intToStr(i, 3)) + expect(ValueError): + discard parseBiggestUInt("22751622367522324480000000") + expect(ValueError): + discard parseBiggestUInt("41404969074137497600000000") + expect(ValueError): + discard parseBiggestUInt("20701551093035827200000000000000000") + expect(ValueError): + discard parseBiggestUInt("225462255024603136000000000000000000") + expect(ValueError): + discard parseBiggestUInt("204963831854661632000000000000000000") From 8c3e62e6de28489b97f414395d648282bb00e44c Mon Sep 17 00:00:00 2001 From: Leon Lysak Date: Mon, 27 Jan 2025 02:17:58 -0500 Subject: [PATCH 16/48] Update dom.nim (removeEventListener function) (#24650) Essentially just an update for the `removeEventListener` function as per https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener --- lib/js/dom.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/js/dom.nim b/lib/js/dom.nim index be2a34db1f..84576b7c3d 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -1490,7 +1490,9 @@ proc clearInterval*(i: Interval) {.importc, nodecl.} proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) proc addEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) proc dispatchEvent*(et: EventTarget, ev: Event) -proc removeEventListener*(et: EventTarget; ev: cstring; cb: proc(ev: Event)) +proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), useCapture: bool = false) +proc removeEventListener*(et: EventTarget, ev: cstring, cb: proc(ev: Event), options: AddEventListenerOptions) + # Window "methods" proc alert*(w: Window, msg: cstring) From cab3342a2dbe5bbd62aeed0b0436e34a646fa741 Mon Sep 17 00:00:00 2001 From: Peter Munch-Ellingsen Date: Mon, 27 Jan 2025 16:57:53 +0100 Subject: [PATCH 17/48] Fix check for Nintendo Switch target (#24652) This should fix ringabouts comment here: https://github.com/nim-lang/Nim/pull/24639#issuecomment-2615107496 I wasn't aware that `nintendoswitch` and `posix` would be active at the same time, so I falsely inverted a check. --- lib/pure/os.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index c96a493ec6..1fac8f8744 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -255,7 +255,7 @@ when supportedSystem: for ext in extensions: var x = addFileExt(x, ext) if fileExists(x): - when defined(posix): #not (defined(windows) or defined(nintendoswitch)): + when defined(posix) and not defined(nintendoswitch): while followSymlinks: # doubles as if here if x.symlinkExists: var r = newString(maxSymlinkLen) From af5fd3fea3696be127e7d6bdfc397f2a4224359a Mon Sep 17 00:00:00 2001 From: lit Date: Fri, 31 Jan 2025 01:05:51 +0800 Subject: [PATCH 18/48] fix doc format: testament.md (#24654) - **doc(format): testament: fix `Commands` not regarded as table** ![image](https://github.com/user-attachments/assets/85238dd5-e199-41ca-a8cb-05849415097a) - **doc(format): testament: row `--target` not splited as columns** ![image](https://github.com/user-attachments/assets/230ec693-c459-4fee-bc57-f3ab6c34a9b6) --- doc/testament.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/doc/testament.md b/doc/testament.md index 0ff3591ac9..b7caf54a5a 100644 --- a/doc/testament.md +++ b/doc/testament.md @@ -30,28 +30,30 @@ a working NodeJS on `PATH`. Commands ======== +========================== ========================== p|pat|pattern run all the tests matching the given pattern all run all tests inside of category folders c|cat|category run all the tests of a certain category r|run run single test file html generate testresults.html from the database +========================== ========================== Options ======= ---print print results to the console ---verbose print commands (compiling and running tests) ---simulate see what tests would be run but don't run them (for debugging) ---failing only show failing/ignored tests ---targets:"c cpp js objc" run tests for specified targets (default: c) ---nim:path use a particular nim executable (default: $PATH/nim) ---directory:dir Change to directory dir before reading the tests or doing anything else. ---colors:on|off Turn messages coloring on|off. ---backendLogging:on|off Disable or enable backend logging. By default turned on. ---megatest:on|off Enable or disable megatest. Default is on. ---valgrind:on|off Enable or disable valgrind support. Default is on. ---skipFrom:file Read tests to skip from `file` - one test per line, # comments ignored +--print print results to the console +--verbose print commands (compiling and running tests) +--simulate see what tests would be run but don't run them (for debugging) +--failing only show failing/ignored tests +--targets:"c cpp js objc" run tests for specified targets (default: c) +--nim:path use a particular nim executable (default: $PATH/nim) +--directory:dir Change to directory dir before reading the tests or doing anything else. +--colors:on|off Turn messages coloring on|off. +--backendLogging:on|off Disable or enable backend logging. By default turned on. +--megatest:on|off Enable or disable megatest. Default is on. +--valgrind:on|off Enable or disable valgrind support. Default is on. +--skipFrom:file Read tests to skip from `file` - one test per line, # comments ignored Running a single test From 647c6687f181ec6c68cb18ce958e3784b58a23a7 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 31 Jan 2025 10:44:02 +0300 Subject: [PATCH 19/48] don't mark captured field sym in template as fully used (#24660) fixes #24657 --- compiler/semtempl.nim | 7 ++++++- tests/template/tfielduse.nim | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/template/tfielduse.nim diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 7732e097ec..0fa9a8f067 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -67,7 +67,12 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule; # for instance 'nextTry' is both in tables.nim and astalgo.nim ... if not isField or sfGenSym notin s.flags: result = newSymNode(s, info) - markUsed(c, info, s) + if isField: + # possibly not final field sym + incl(s.flags, sfUsed) + markOwnerModuleAsUsed(c, s) + else: + markUsed(c, info, s) onUse(info, s) else: result = n diff --git a/tests/template/tfielduse.nim b/tests/template/tfielduse.nim new file mode 100644 index 0000000000..ff9402d952 --- /dev/null +++ b/tests/template/tfielduse.nim @@ -0,0 +1,9 @@ +# issue #24657 + +proc g() {.error.} = discard + +type T = object + g: int + +template B(): untyped = typeof(T.g) +type _ = B() From 0861dabfa70f40f00c5c95d58c344ad5e0fd19ec Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 31 Jan 2025 10:44:44 +0300 Subject: [PATCH 20/48] add ambiguous identifier message to generic instantiations (#24646) fixes #24644 Another option is to include the symbol names and owners in the type listing as in #24645 but this is a bit verbose. --- compiler/semtypes.nim | 5 +++++ tests/errmsgs/tambtypegeneric.nim | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/errmsgs/tambtypegeneric.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 5f33df5c7d..cc16fd3c0e 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1671,6 +1671,11 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = var err = "cannot instantiate " err.addTypeHeader(c.config, t) err.add "\ngot: <$1>\nbut expected: <$2>" % [describeArgs(c, n), describeArgs(c, t.n, 0)] + if m.firstMismatch.kind == kTypeMismatch and m.firstMismatch.arg < n.len: + let nArg = n[m.firstMismatch.arg] + if nArg.kind in nkSymChoices: + err.add "\n" + err.add ambiguousIdentifierMsg(nArg) localError(c.config, n.info, errGenerated, err) return newOrPrevType(tyError, prev, c) diff --git a/tests/errmsgs/tambtypegeneric.nim b/tests/errmsgs/tambtypegeneric.nim new file mode 100644 index 0000000000..ed42ff6cf9 --- /dev/null +++ b/tests/errmsgs/tambtypegeneric.nim @@ -0,0 +1,11 @@ +import "."/[mambtype1, mambtype2] +type H[K] = object +proc b(_: int) = # slightly different, still not useful, error message if `b` generic + proc r(): H[Y] = discard #[tt.Error + ^ cannot instantiate H [type declared in tambtypegeneric.nim(2, 6)] +got: +but expected: +ambiguous identifier: 'Y' -- use one of the following: + mambtype1.Y: Y + mambtype2.Y: Y]# +b(0) From e2bed72b72708dfd25453ebe0eba672ead37ed43 Mon Sep 17 00:00:00 2001 From: lit Date: Mon, 3 Feb 2025 17:12:44 +0800 Subject: [PATCH 21/48] doc(tempfiles): update link of getTempDir (#24661) - tempfiles: update `getTempDir` link... from os.html to appdirs.html - ~~nims.md: rm three `std/`, which are out of place~~ (ref https://github.com/nim-lang/Nim/pull/24661#discussion_r1937293833) --- lib/std/tempfiles.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/tempfiles.nim b/lib/std/tempfiles.nim index 9b99d44e71..735bd32297 100644 --- a/lib/std/tempfiles.nim +++ b/lib/std/tempfiles.nim @@ -128,7 +128,7 @@ proc genTempPath*(prefix, suffix: string, dir = ""): string = ## ## The path begins with `prefix` and ends with `suffix`. ## - ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). + ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). let dir = getTempDirImpl(dir) result = dir / (prefix & randomPathName(nimTempPathLength) & suffix) @@ -143,7 +143,7 @@ proc createTempFile*(prefix, suffix: string, dir = ""): tuple[cfile: File, path: ## ## .. note:: It is the caller's responsibility to close `result.cfile` and ## remove `result.file` when no longer needed. - ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). + ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). runnableExamples: import std/os doAssertRaises(OSError): discard createTempFile("", "", "nonexistent") @@ -176,7 +176,7 @@ proc createTempDir*(prefix, suffix: string, dir = ""): string = ## If failing to create a temporary directory, `OSError` will be raised. ## ## .. note:: It is the caller's responsibility to remove the directory when no longer needed. - ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). + ## .. note:: `dir` must exist (empty `dir` will resolve to `getTempDir `_). runnableExamples: import std/os doAssertRaises(OSError): discard createTempDir("", "", "nonexistent") From 7695d51fc48b4bd82e1fe8da24162b28b2df19f1 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:48:52 +0800 Subject: [PATCH 22/48] fixes #24658; cpp compilation failure on Nim 2.2.x (#24663) fixes #24658 --- compiler/semstmts.nim | 2 +- tests/cpp/tconstructor.nim | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index c4cd623d9b..9295b873c8 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2351,7 +2351,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) = isInitializer = false break var j = 0 - while p[j].sym.kind == skParam: + while p[j].kind == nkSym and p[j].sym.kind == skParam: initializerCall.add val inc j if isInitializer: diff --git a/tests/cpp/tconstructor.nim b/tests/cpp/tconstructor.nim index 922ee54fd4..2076b15d8d 100644 --- a/tests/cpp/tconstructor.nim +++ b/tests/cpp/tconstructor.nim @@ -128,4 +128,10 @@ block: var b = makeBoo() var b2 = makeBoo2() - main() \ No newline at end of file + main() + +block: # bug #24658 + type + A {.importcpp: "A".} = object + + proc a(something: ptr cint = nil): A {.cdecl, constructor, importcpp: "A(@)".} From 485b414fcec195bf217aba252e97c3c71f9bf489 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 6 Feb 2025 02:37:13 +0800 Subject: [PATCH 23/48] fixes #24666; Compilation error when formatting a complex number (#24667) fixes #24666 ref https://github.com/nim-lang/Nim/pull/22924 --- lib/pure/complex.nim | 2 +- tests/stdlib/tcomplex.nim | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/pure/complex.nim b/lib/pure/complex.nim index 77f2b35bef..b57bcdc533 100644 --- a/lib/pure/complex.nim +++ b/lib/pure/complex.nim @@ -33,7 +33,7 @@ runnableExamples: {.push checks: off, line_dir: off, stack_trace: off, debugger: off.} # the user does not want to trace a part of the standard library! -import std/[math, strformat] +import std/[math, strformat, strutils] type Complex*[T: SomeFloat] = object diff --git a/tests/stdlib/tcomplex.nim b/tests/stdlib/tcomplex.nim index ca83314b94..02023f696b 100644 --- a/tests/stdlib/tcomplex.nim +++ b/tests/stdlib/tcomplex.nim @@ -2,7 +2,7 @@ discard """ matrix: "--mm:refc; --mm:orc" """ -import std/[complex, math] +import std/[complex, math, strformat, formatfloat] import std/assertions proc `=~`[T](x, y: Complex[T]): bool = @@ -113,3 +113,7 @@ doAssert 123.0.im + 456.0 == complex64(456, 123) let localA = complex(0.1'f32) doAssert localA.im is float32 + +block: # bug #24666 + let z = complex64(1, 2) + doAssert fmt"{z}" == "(1.0, 2.0)" From e6f6c369ffd3073896716502af20727bd8feaf50 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 7 Feb 2025 06:19:53 +0800 Subject: [PATCH 24/48] fixes bugs on the Nim manual (#24669) ref https://en.cppreference.com/w/cpp/error/exception/what > Pointer to a null-terminated string with explanatory information. The pointer is guaranteed to be valid at least until the exception object from which it is obtained is destroyed, or until a non-const member function on the exception object is called. The pointer is only valid before `CStdException as e` is destroyed Old examples are broken on macOS arm64 ``` /Users/blue/Desktop/nimony/test4.nim(38) test4 /Users/blue/Desktop/nimony/test4.nim(26) fn /Users/blue/.choosenim/toolchains/nim-#devel/lib/std/assertions.nim(41) failedAssertImpl /Users/blue/.choosenim/toolchains/nim-#devel/lib/std/assertions.nim(36) raiseAssert /Users/blue/.choosenim/toolchains/nim-#devel/lib/system/fatal.nim(53) sysFatal Error: unhandled exception: /Users/blue/Desktop/nimony/test4.nim(26, 3) `$b == "foo2"` [AssertionDefect] ``` --- doc/manual.md | 10 +++++----- tests/cpp/tmanual_exception.nim | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/doc/manual.md b/doc/manual.md index d90e654264..40b7b9f180 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -5186,22 +5186,22 @@ caught by reference. Example: proc fn() = let a = initRuntimeError("foo") doAssert $a.what == "foo" - var b: cstring + var b = "" try: raise initRuntimeError("foo2") except CStdException as e: doAssert e is CStdException - b = e.what() - doAssert $b == "foo2" + b = $e.what() + doAssert b == "foo2" try: raise initStdException() except CStdException: discard try: raise initRuntimeError("foo3") except CRuntimeError as e: - b = e.what() + b = $e.what() except CStdException: doAssert false - doAssert $b == "foo3" + doAssert b == "foo3" fn() ``` diff --git a/tests/cpp/tmanual_exception.nim b/tests/cpp/tmanual_exception.nim index a91ccffe4c..891bccee05 100644 --- a/tests/cpp/tmanual_exception.nim +++ b/tests/cpp/tmanual_exception.nim @@ -1,6 +1,4 @@ discard """ - # doesn't work on macos 13 seemingly due to libc++ linking issue https://stackoverflow.com/a/77375947 - disabled: osx targets: cpp """ @@ -18,21 +16,21 @@ proc initStdException(): CStdException {.importcpp: "std::exception()", construc proc fn() = let a = initRuntimeError("foo") doAssert $a.what == "foo" - var b: cstring + var b = "" try: raise initRuntimeError("foo2") except CStdException as e: doAssert e is CStdException - b = e.what() - doAssert $b == "foo2" + b = $e.what() + doAssert b == "foo2" try: raise initStdException() except CStdException: discard try: raise initRuntimeError("foo3") except CRuntimeError as e: - b = e.what() + b = $e.what() except CStdException: doAssert false - doAssert $b == "foo3" + doAssert b == "foo3" fn() From 1a7bc6d878ff04709ebb1002010fd53b4ba02179 Mon Sep 17 00:00:00 2001 From: Mads Hougesen Date: Mon, 10 Feb 2025 11:12:41 +0100 Subject: [PATCH 25/48] feat(nimpretty): support formatting code from stdin (#24676) This pr adds support for running `nimpretty` on stdin as described in #24622. I tested `:%!nimpretty -` and `:%!nimpretty --stdin` in neovim and both seems to work without issues. --- nimpretty/nimpretty.nim | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/nimpretty/nimpretty.nim b/nimpretty/nimpretty.nim index e5abf0d2d9..c860d2970e 100644 --- a/nimpretty/nimpretty.nim +++ b/nimpretty/nimpretty.nim @@ -16,6 +16,8 @@ import ../compiler / [idents, llstream, ast, msgs, syntaxes, options, pathutils, import parseopt, strutils, os, sequtils +import std/tempfiles + const Version = "0.2" Usage = "nimpretty - Nim Pretty Printer Version " & Version & """ @@ -26,6 +28,7 @@ Usage: Options: --out:file set the output file (default: overwrite the input file) --outDir:dir set the output dir (default: overwrite the input files) + --stdin read input from stdin and write output to stdout --indent:N[=0] set the number of spaces that is used for indentation --indent:0 means autodetection (default behaviour) --maxLineLen:N set the desired maximum line length (default: 80) @@ -84,7 +87,7 @@ proc finalCheck(content: string; origAst: PNode): bool {.nimcall.} = closeParser(parser) result = conf.errorCounter == oldErrors # and goodEnough(newAst, origAst) -proc prettyPrint*(infile, outfile: string, opt: PrettyOptions) = +proc prettyPrint*(infile, outfile: string; opt: PrettyOptions) = var conf = newConfigRef() let fileIdx = fileInfoIdx(conf, AbsoluteFile infile) let f = splitFile(outfile.expandTilde) @@ -99,12 +102,28 @@ proc prettyPrint*(infile, outfile: string, opt: PrettyOptions) = when defined(nimpretty): closeEmitter(parser.em, fullAst, finalCheck) +proc handleStdinInput(opt: PrettyOptions) = + var content = readAll(stdin) + + var (cfile, path) = createTempFile("nimpretty_", ".nim") + + writeFile(path, content) + + prettyPrint(path, path, opt) + + echo(readAll(cfile)) + + close(cfile) + removeFile(path) + proc main = var outfile, outdir: string var infiles = newSeq[string]() var outfiles = newSeq[string]() + var isStdin = false + var backup = false # when `on`, create a backup file of input in case # `prettyPrint` could overwrite it (note that the backup may happen even @@ -112,7 +131,6 @@ proc main = # --backup was un-documented (rely on git instead). var opt = PrettyOptions(indWidth: 0, maxLineLen: 80) - for kind, key, val in getopt(): case kind of cmdArgument: @@ -132,8 +150,15 @@ proc main = of "outDir", "outdir": outdir = val of "indent": opt.indWidth = parseInt(val) of "maxlinelen": opt.maxLineLen = parseInt(val) + # "" is equal to '-' as input + of "stdin", "": isStdin = true else: writeHelp() of cmdEnd: assert(false) # cannot happen + + if isStdin: + handleStdinInput(opt) + return + if infiles.len == 0: quit "[Error] no input file." From b211ada2734c78370e741095c733c054aa2b4d8a Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sat, 15 Feb 2025 03:52:43 +0800 Subject: [PATCH 26/48] fixes #24673; divmod errors for ranges (#24679) fixes #24673 The problem is that there is no way to distinguish `cint`, `cint`, etc ctypes with Nim types. So `when T is cint | clong | clonglong:` is true for types derived from `int`, `int32` and `int64`. In this PR, it fixes the branch to avoid erros for `Natural` --- lib/pure/math.nim | 2 +- tests/stdlib/tmath.nim | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pure/math.nim b/lib/pure/math.nim index e304f5c01b..2fb4257b2b 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -105,7 +105,7 @@ when not defined(js) and not defined(nimscript): # C when compileOption("overflowChecks"): if y == 0: raise new(DivByZeroDefect) - elif (x == T.low and y == -1.T): + elif (x == T.low and int64(y) == -1): raise new(OverflowDefect) let res = divmod_c(x, y) result[0] = res.quot diff --git a/tests/stdlib/tmath.nim b/tests/stdlib/tmath.nim index 69534b16e2..5d3fd450ff 100644 --- a/tests/stdlib/tmath.nim +++ b/tests/stdlib/tmath.nim @@ -545,3 +545,8 @@ when not defined(js) and not defined(danger): doAssertRaises(OverflowDefect): discard sum(x) +block: # bug #24673 + let x: Natural = 5 + let y: Natural = 3 + + doAssert divmod(x, y) == (Natural 1, Natural 2) From a5cc33c1d36fc468fccfc43981d5da1b8c75d36b Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 14 Feb 2025 22:54:17 +0300 Subject: [PATCH 27/48] track introduced locals in vmgen for eval check (#24674) fixes #8758, fixes #10828, fixes #12172, fixes #21610, fixes #23803, fixes #24633, fixes #24634, succeeds #24085 We simply track the symbol ID of every traversed `var`/`let` definition in `vmgen`, then these symbols are always considered evaluable in the current `vmgen` context. The set of symbols is reset before every generation, but both tests worked properly without doing this including the nested `const`, so maybe it's already done in some way I'm not seeing. --- compiler/vm.nim | 5 +- compiler/vmdef.nim | 3 +- compiler/vmgen.nim | 3 +- tests/vm/tconststaticvar.nim | 79 ++++++++++++++++++++++++++++++ tests/vm/tconststaticvar2.nim | 13 +++++ tests/vm/tconststaticvar3.nim | 9 ++++ tests/vm/tconststaticvar_wrong.nim | 6 +++ 7 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 tests/vm/tconststaticvar.nim create mode 100644 tests/vm/tconststaticvar2.nim create mode 100644 tests/vm/tconststaticvar3.nim create mode 100644 tests/vm/tconststaticvar_wrong.nim diff --git a/compiler/vm.nim b/compiler/vm.nim index 778583a31d..fc9da48f37 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -12,7 +12,7 @@ import semmacrosanity import - std/[strutils, tables, parseutils], + std/[strutils, tables, intsets, parseutils], msgs, vmdef, vmgen, nimsets, types, parser, vmdeps, idents, trees, renderer, options, transf, gorgeimpl, lineinfos, btrees, macrocacheimpl, @@ -2425,9 +2425,12 @@ proc evalConstExprAux(module: PSym; idgen: IdGenerator; setupGlobalCtx(module, g, idgen) var c = PCtx g.vm let oldMode = c.mode + let oldLocals = c.locals c.mode = mode + c.locals = initIntSet() c.cannotEval = false let start = genExpr(c, n, requiresValue = mode!=emStaticStmt) + c.locals = oldLocals if c.cannotEval: return errorNode(idgen, prc, n) if c.code[start].opcode == opcEof: return newNodeI(nkEmpty, n.info) diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index 3c39661127..f7e18d3010 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -10,7 +10,7 @@ ## This module contains the type definitions for the new evaluation engine. ## An instruction is 1-3 int32s in memory, it is a register based VM. -import std/[tables, strutils] +import std/[tables, strutils, intsets] import ast, idents, options, modulegraphs, lineinfos @@ -272,6 +272,7 @@ type vmstateDiff*: seq[(PSym, PNode)] # we remember the "diff" to global state here (feature for IC) procToCodePos*: Table[int, int] cannotEval*: bool + locals*: IntSet PStackFrame* = ref TStackFrame TStackFrame* {.acyclic.} = object diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 0db0e93f9f..6e47f6fe44 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1583,6 +1583,7 @@ proc checkCanEval(c: PCtx; n: PNode) = # are in the right scope: if sfGenSym in s.flags and c.prc.sym == nil: discard elif s.kind == skParam and s.typ.kind == tyTypeDesc: discard + elif s.kind in {skVar, skLet} and s.id in c.locals: discard else: cannotEval(c, n) elif s.kind in {skProc, skFunc, skConverter, skMethod, skIterator} and sfForward in s.flags: @@ -1975,7 +1976,7 @@ proc genVarSection(c: PCtx; n: PNode) = c.gen(lowerTupleUnpacking(c.graph, a, c.idgen, c.getOwner)) elif a[0].kind == nkSym: let s = a[0].sym - checkCanEval(c, a[0]) + c.locals.incl(s.id) if s.isGlobal: let runtimeAccessToCompileTime = c.mode == emRepl and sfCompileTime in s.flags and s.position > 0 diff --git a/tests/vm/tconststaticvar.nim b/tests/vm/tconststaticvar.nim new file mode 100644 index 0000000000..cd575663e5 --- /dev/null +++ b/tests/vm/tconststaticvar.nim @@ -0,0 +1,79 @@ +block: # issue #8758 + template baz() = + var i = 0 + + proc foo() = + static: + var i = 0 + baz() + +block: # issue #10828 + proc test(i: byte): bool = + const SET = block: # No issues when defined outside proc + var s: set[byte] + for i in 0u8 .. 255u8: incl(s, i) + s + return i in SET + doAssert test(0) + doAssert test(127) + doAssert test(255) + +block: # issue #12172 + const TEST = block: + var test: array[5, string] + for i in low(test)..high(test): + test[i] = $i + test + proc test = + const TEST2 = block: + var test: array[5, string] # Error here + for i in low(test)..high(test): + test[i] = $i + test + doAssert TEST == TEST2 + doAssert TEST == @["0", "1", "2", "3", "4"] + doAssert TEST2 == @["0", "1", "2", "3", "4"] + test() + +block: # issue #21610 + func stuff(): int = + const r = block: + var r = 1 # Error: cannot evaluate at compile time: r + for i in 2..10: + r *= i + r + r + doAssert stuff() == 3628800 + +block: # issue #23803 + func foo1(c: int): int {.inline.} = + const arr = block: + var res: array[0..99, int] + res[42] = 43 + res + arr[c] + doAssert foo1(41) == 0 + doAssert foo1(42) == 43 + doAssert foo1(43) == 0 + + # works + func foo2(c: int): int {.inline.} = + func initArr(): auto = + var res: array[0..99, int] + res[42] = 43 + res + const arr = initArr() + arr[c] + doAssert foo2(41) == 0 + doAssert foo2(42) == 43 + doAssert foo2(43) == 0 + + # also works + const globalArr = block: + var res: array[0..99, int] + res[42] = 43 + res + func foo3(c: int): int {.inline.} = globalArr[c] + doAssert foo3(41) == 0 + doAssert foo3(42) == 43 + doAssert foo3(43) == 0 diff --git a/tests/vm/tconststaticvar2.nim b/tests/vm/tconststaticvar2.nim new file mode 100644 index 0000000000..83b8309f0a --- /dev/null +++ b/tests/vm/tconststaticvar2.nim @@ -0,0 +1,13 @@ +# issue #24634 + +type J = object + +template m(u: J): int = + let v = u + 0 + +proc g() = + const x = J() + const _ = m(x) + +g() diff --git a/tests/vm/tconststaticvar3.nim b/tests/vm/tconststaticvar3.nim new file mode 100644 index 0000000000..0480450141 --- /dev/null +++ b/tests/vm/tconststaticvar3.nim @@ -0,0 +1,9 @@ +# issue #24633 + +import std/sequtils + +proc f(a: static openArray[int]) = + const s1 = a.mapIt(it) + const s2 = a.toSeq() + +f([1,2,3]) diff --git a/tests/vm/tconststaticvar_wrong.nim b/tests/vm/tconststaticvar_wrong.nim new file mode 100644 index 0000000000..091ff39c4f --- /dev/null +++ b/tests/vm/tconststaticvar_wrong.nim @@ -0,0 +1,6 @@ +proc test = + const TEST = block: + let i = 1 + const j = i + 1 #[tt.Error + ^ cannot evaluate at compile time: i]# + j From b7d8896d00e4b2f1840faa919f7d652e542bc763 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Tue, 18 Feb 2025 07:32:10 -0500 Subject: [PATCH 28/48] Add terminal colors back to unittest under nimPreviewSlimSystem (#24694) --- lib/pure/unittest.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 9b5a3ba199..f14aead2bb 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -109,7 +109,7 @@ import std/private/since import std/exitprocs when defined(nimPreviewSlimSystem): - import std/assertions + import std/[assertions, syncio] import std/[macros, strutils, streams, times, sets, sequtils] From 510ac845188a26d4c317893ff7b260582beee54d Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:24:41 +0800 Subject: [PATCH 29/48] implements `quirky` for functions (#24700) ref https://github.com/nim-lang/Nim/pull/24686 With this PR ```nim import std/streams proc foo() = var name = newStringStream("2r2") raise newException(ValueError, "sh") try: foo() except: discard echo 123 ``` this example no longer leaks --------- Co-authored-by: Andreas Rumpf --- compiler/pragmas.nim | 6 +++++- lib/system/arc.nim | 5 ++++- tests/arc/tvalgrind.nim | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 tests/arc/tvalgrind.nim diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index e488412af7..a6c1917792 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -1318,8 +1318,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, pragmaProposition(c, it) of wEnsures: pragmaEnsures(c, it) - of wEnforceNoRaises, wQuirky: + of wEnforceNoRaises: sym.flags.incl sfNeverRaises + of wQuirky: + sym.flags.incl sfNeverRaises + if sym.kind in {skProc, skMethod, skConverter, skFunc, skIterator}: + sym.options.incl optQuirky of wSystemRaisesDefect: sym.flags.incl sfSystemRaisesDefect of wVirtual: diff --git a/lib/system/arc.nim b/lib/system/arc.nim index 7537fd2125..d67af9817a 100644 --- a/lib/system/arc.nim +++ b/lib/system/arc.nim @@ -87,6 +87,9 @@ else: template count(x: Cell): untyped = x.rc shr rcShift +when not defined(nimHasQuirky): + {.pragma: quirky.} + proc nimNewObj(size, alignment: int): pointer {.compilerRtl.} = let hdrSize = align(sizeof(RefHeader), alignment) let s = size + hdrSize @@ -190,7 +193,7 @@ proc nimRawDispose(p: pointer, alignment: int) {.compilerRtl.} = template `=dispose`*[T](x: owned(ref T)) = nimRawDispose(cast[pointer](x), T.alignOf) #proc dispose*(x: pointer) = nimRawDispose(x) -proc nimDestroyAndDispose(p: pointer) {.compilerRtl, raises: [].} = +proc nimDestroyAndDispose(p: pointer) {.compilerRtl, quirky, raises: [].} = let rti = cast[ptr PNimTypeV2](p) if rti.destructor != nil: cast[DestructorProc](rti.destructor)(p) diff --git a/tests/arc/tvalgrind.nim b/tests/arc/tvalgrind.nim new file mode 100644 index 0000000000..27d089d153 --- /dev/null +++ b/tests/arc/tvalgrind.nim @@ -0,0 +1,16 @@ +discard """ + cmd: "nim c --mm:orc -d:useMalloc $file" + valgrind: "true" +""" + +import std/streams + + +proc foo() = + var name = newStringStream("2r2") + raise newException(ValueError, "sh") + +try: + foo() +except: + discard \ No newline at end of file From ebeef1067f5aaf8ea6ec9c63c513e861e33dcde6 Mon Sep 17 00:00:00 2001 From: metagn Date: Thu, 20 Feb 2025 01:01:26 +0300 Subject: [PATCH 30/48] adapt generic matches to inheritance penalty of final objects (#24691) Applies #24144 to the equivalent matches of generic types, and adds the behavior to matches of generic invocations to generic invocations. Not encountered in many cases so it's hard to come up with tests but an example is the test code in #24688, the match to the generic body never sets the inheritance penalty leaving it at -1, but the match to the generic invocation sets it to 0 which matches worse, when it should set it to -1 because the object does not participate in inheritance. --- compiler/sigmatch.nim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 950ebe5196..b175549dcb 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1692,7 +1692,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, if aAsObject.kind == tyObject and trIsOutParam notin flags: let baseType = aAsObject.base if baseType != nil: - inc c.inheritancePenalty, 1 + int(c.inheritancePenalty < 0) + if tfFinal notin aAsObject.flags: + inc c.inheritancePenalty, 1 + int(c.inheritancePenalty < 0) let ret = typeRel(c, f, baseType, flags) return if ret in {isEqual,isGeneric}: isSubtype else: ret @@ -1733,6 +1734,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, let tr = typeRel(c, f[i], x[i], flags) if tr <= isSubtype: return result = isGeneric + let impl = last(f[0]) + if impl.kind == tyObject and tfFinal notin impl.flags: + # match non-invocation case + inc c.inheritancePenalty, 0 + int(c.inheritancePenalty < 0) elif x.kind == tyGenericInst and f[0] == x[0] and x.len - 1 == f.len: for i in 1..= 0: - inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0) + if aobj.kind == tyObject and tfFinal notin aobj.flags: + inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0) # bug #4863: We still need to bind generic alias crap, so # we cannot return immediately: result = if depth == 0: isGeneric else: isSubtype From 1f07fdd2dc06636faf8aec68cb57e477c044c756 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 20 Feb 2025 06:01:56 +0800 Subject: [PATCH 31/48] fixes #24664; always sets the \0 terminator in `appendString` (#24703) fixes #24664 ```nim proc main() = for i in 0..1: var s = "12345" s.add s echo s main() ``` In the given example, `add` contains two steps: `prepareAdd` and `appendString`. In the first step, a new buffer is created in order to store the final doubled string. But it doesn't copy the null terminator, neither zeromem the left unused spaces. It causes a problem because `appendString` will copy itself which doesn't end with `\0` properly so contaminated memory is copied instead. ``` var s = 12345\0 prepareAdd: var s = 12345xxxxx\0 appendString: var s = 1234512345x ``` --- lib/system/strs_v2.nim | 5 +++-- tests/system/t24664.nim | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/system/t24664.nim diff --git a/lib/system/strs_v2.nim b/lib/system/strs_v2.nim index c77ed6ad1e..95e76b1f8f 100644 --- a/lib/system/strs_v2.nim +++ b/lib/system/strs_v2.nim @@ -112,9 +112,10 @@ proc nimToCStringConv(s: NimStringV2): cstring {.compilerproc, nonReloadable, in proc appendString(dest: var NimStringV2; src: NimStringV2) {.compilerproc, inline.} = if src.len > 0: - # also copy the \0 terminator: - copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len+1) + # don't copy the \0 terminator: + copyMem(unsafeAddr dest.p.data[dest.len], unsafeAddr src.p.data[0], src.len) inc dest.len, src.len + dest.p.data[dest.len] = '\0' proc appendChar(dest: var NimStringV2; c: char) {.compilerproc, inline.} = dest.p.data[dest.len] = c diff --git a/tests/system/t24664.nim b/tests/system/t24664.nim new file mode 100644 index 0000000000..b38e0b4dfc --- /dev/null +++ b/tests/system/t24664.nim @@ -0,0 +1,14 @@ +discard """ + output: ''' +TestString123TestString123 +TestString123TestString123 +''' +""" + +proc foostring() = # bug #24664 + for i in 0..1: + var s = "TestString123" + s.add s + echo s + +foostring() \ No newline at end of file From f0b5bf359ed93600a93da037d787336fea9da695 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 20 Feb 2025 06:02:28 +0800 Subject: [PATCH 32/48] fixes ORC memory leaks; marks hooks with optQuirky (#24701) closes https://github.com/nim-lang/Nim/pull/24686 closes #24693 ```nim # v.nim import std/[json] var test: seq[string] var testData: JsonNode try: ## Fails testData = parseJson("""[{"id": 1"}, {"id": "2"}]""") ## Works # testdata = parseJson("""[{"id": "1"}, {"id": "2"}]""") ## Fails # let stream = newStringStream("""[{"id": 1"}, {"id": "2"}]""") # testData = parseJson(stream, "input", false, false) # stream.close() except: testData = %* [] for t in testData: test.add(t["id"].getStr()) echo $test ``` With this PR: ``` ==66425== LEAK SUMMARY: ==66425== definitely lost: 0 bytes in 0 blocks ==66425== indirectly lost: 0 bytes in 0 blocks ==66425== possibly lost: 0 bytes in 0 blocks ==66425== still reachable: 16,512 bytes in 2 blocks ==66425== suppressed: 0 bytes in 0 blocks ==66425== Reachable blocks (those to which a pointer was found) are not shown. ==66425== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==66425== ==66425== For lists of detected and suppressed errors, rerun with: -s ==66425== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ``` --- compiler/liftdestructors.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 6aa2634401..9020a6f7ff 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -1207,6 +1207,8 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; result.ast[pragmasPos].add newTree(nkExprColonExpr, newIdentNode(g.cache.getIdent("raises"), info), newNodeI(nkBracket, info)) + if kind == attachedDestructor: + incl result.options, optQuirky completePartialOp(g, idgen.module, typ, kind, result) From 91e8e605d026280c1e1293e7f98d08cc16b626a5 Mon Sep 17 00:00:00 2001 From: lit Date: Thu, 20 Feb 2025 06:03:28 +0800 Subject: [PATCH 33/48] fix(dollar): $NaN -> "NaN", $Inf -> "Infinity" only when js (#24695) ref nimpylib/pylib#44 --------- Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com> --- lib/std/formatfloat.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/formatfloat.nim b/lib/std/formatfloat.nim index c35adfd7cb..767de111b5 100644 --- a/lib/std/formatfloat.nim +++ b/lib/std/formatfloat.nim @@ -110,6 +110,10 @@ when defined(js): } if (Number.isSafeInteger(`a`)) `result` = `a` === 0 && 1 / `a` < 0 ? "-0.0" : `a`+".0"; + else if (isNaN(`a`)) // Number.isNaN is since ES6 + `result` = "nan"; // or it'll be "NaN" + else if (!isFinite(`a`)) // Number.isFinite newer but unnecessary here + `result` = `a` > 0 ? "inf" : "-inf"; // or it'll be [-]Infinity else { `result` = `a`+""; if(nimOnlyDigitsOrMinus(`result`)){ From 1af88a2d20add67e3c376f0bc7144f9a0a843183 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 20 Feb 2025 06:03:58 +0800 Subject: [PATCH 34/48] always mangle local variables (#24681) ref #24677 --- compiler/ccgtypes.nim | 2 +- tests/arc/tnodestroyexplicithook.nim | 2 +- tests/ccgbugs/tassign_nil_strings.nim | 2 +- tests/ccgbugs/tmissingvolatile.nim | 2 +- tests/ccgbugs2/tcodegen.nim | 11 +++++++++++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index b3e03f5749..92b439891d 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -115,7 +115,7 @@ proc fillLocalName(p: BProc; s: PSym) = if s.kind == skTemp: # speed up conflict search for temps (these are quite common): if counter != 0: result.add "_" & rope(counter+1) - elif counter != 0 or isKeyword(s.name) or p.module.g.config.cppDefines.contains(key): + elif s.kind != skResult: result.add "_" & rope(counter+1) p.sigConflicts.inc(key) s.loc.snippet = result diff --git a/tests/arc/tnodestroyexplicithook.nim b/tests/arc/tnodestroyexplicithook.nim index 99dd0c6332..43dff86b4f 100644 --- a/tests/arc/tnodestroyexplicithook.nim +++ b/tests/arc/tnodestroyexplicithook.nim @@ -1,5 +1,5 @@ discard """ - ccodecheck: "'Result[(i - 0)] = eqdup'" + ccodecheck: "'Result[(i_1 - 0)] = eqdup'" """ # issue #24626 diff --git a/tests/ccgbugs/tassign_nil_strings.nim b/tests/ccgbugs/tassign_nil_strings.nim index e32bfcade6..11d9eb568a 100644 --- a/tests/ccgbugs/tassign_nil_strings.nim +++ b/tests/ccgbugs/tassign_nil_strings.nim @@ -1,7 +1,7 @@ discard """ matrix: "--mm:refc" output: "Hello" - ccodecheck: "\\i@'a = ((NimStringDesc*) NIM_NIL)'" + ccodecheck: "\\i@'a_1 = ((NimStringDesc*) NIM_NIL)'" """ proc main() = diff --git a/tests/ccgbugs/tmissingvolatile.nim b/tests/ccgbugs/tmissingvolatile.nim index b877eff71c..3455a9cb3d 100644 --- a/tests/ccgbugs/tmissingvolatile.nim +++ b/tests/ccgbugs/tmissingvolatile.nim @@ -1,7 +1,7 @@ discard """ output: "1" cmd: r"nim c --hints:on $options --mm:refc -d:release $file" - ccodecheck: "'NI volatile state;'" + ccodecheck: "'NI volatile state_1;'" targets: "c" """ diff --git a/tests/ccgbugs2/tcodegen.nim b/tests/ccgbugs2/tcodegen.nim index aac1ecaf39..37579e0bf4 100644 --- a/tests/ccgbugs2/tcodegen.nim +++ b/tests/ccgbugs2/tcodegen.nim @@ -45,3 +45,14 @@ block: # bug #22354 main() + +proc main = # bug #24677 + let NULL = 1 + doAssert NULL == 1 + + var COMMA = 1 + doAssert COMMA == 1 + + for NDEBUG in 0..2: + doAssert NDEBUG == NDEBUG +main() From 1f8da3835f6e395d068c92f86787f0fa77fb6d08 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 22 Feb 2025 23:22:30 +0300 Subject: [PATCH 35/48] keep param pragmas in typed proc AST (#24711) fixes #24702 --- compiler/semtypes.nim | 5 ++++- tests/pragmas/tparamcustompragma.nim | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/pragmas/tparamcustompragma.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index cc16fd3c0e..bc3ddb0806 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1501,7 +1501,10 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, addParamOrResult(c, arg, kind) styleCheckDef(c, a[j].info, arg) onDef(a[j].info, arg) - a[j] = newSymNode(arg) + if a[j].kind == nkPragmaExpr: + a[j][0] = newSymNode(arg) + else: + a[j] = newSymNode(arg) var r: PType = nil if n[0].kind != nkEmpty: diff --git a/tests/pragmas/tparamcustompragma.nim b/tests/pragmas/tparamcustompragma.nim new file mode 100644 index 0000000000..b3e8151499 --- /dev/null +++ b/tests/pragmas/tparamcustompragma.nim @@ -0,0 +1,16 @@ +discard """ + nimout: ''' +proc foo(a {.attr.}: int) = + discard + +''' +""" + +# fixes #24702 + +import macros +template attr*() {.pragma.} +proc foo(a {.attr.}: int) = discard +macro showImpl(a: typed) = + echo repr getImpl(a) +showImpl(foo) From 93fb219f1091ff4859e9eda3b138cbec3ba81ff3 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 26 Feb 2025 00:05:59 +0800 Subject: [PATCH 36/48] undeprecates `var T` destructors (#24716) Both cases are now valid. Though, it could be problematic to mix two cases together as built-in types have non var T destructors --- compiler/semstmts.nim | 3 --- doc/destructors.md | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 9295b873c8..1ca9ebefff 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2158,9 +2158,6 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = elif obj.kind == tyGenericInvocation: obj = obj.genericHead else: break if obj.kind in {tyObject, tyDistinct, tySequence, tyString}: - if op == attachedDestructor and t.firstParamType.kind == tyVar and - c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: - message(c.config, n.info, warnDeprecated, "A custom '=destroy' hook which takes a 'var T' parameter is deprecated; it should take a 'T' parameter") obj = canonType(c, obj) let ao = getAttachedOp(c.graph, obj, op) if ao == s: diff --git a/doc/destructors.md b/doc/destructors.md index e192fd362c..5344dfedea 100644 --- a/doc/destructors.md +++ b/doc/destructors.md @@ -129,7 +129,8 @@ other associated resources. Variables are destroyed via this hook when they go out of scope or when the routine they were declared in is about to return. -A `=destroy` hook is allowed to have a parameter of a `var T` or `T` type. Taking a `var T` type is deprecated. The prototype of this hook for a type `T` needs to be: +A `=destroy` hook is allowed to have a parameter of a `var T` or `T` type. +The prototype of this hook for a type `T` needs to be: ```nim proc `=destroy`(x: T) From e449813c61fbdbb150b197fca98610424e1107ad Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 26 Feb 2025 00:08:22 +0800 Subject: [PATCH 37/48] fixes #24725; Invalid =sink generated for pure inheritable object (#24726) fixes #24725 `lacksMTypeField` doesn't take the base types into consideration. And for ` {.inheritable, pure.}`, it shouldn't generate a `m_type` field. --- compiler/ccgtypes.nim | 4 ---- compiler/liftdestructors.nim | 2 +- compiler/types.nim | 9 +++++++++ tests/ccgbugs/tcgbug.nim | 18 ++++++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 92b439891d..ef7550d2c0 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -247,10 +247,6 @@ proc hasNoInit(t: PType): bool = proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDescKind): Rope -proc isObjLackingTypeField(typ: PType): bool {.inline.} = - result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and - (typ.baseClass == nil) or isPureObject(typ)) - proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool = # Arrays and sets cannot be returned by a C procedure, because C is # such a poor programming language. diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 9020a6f7ff..c948916132 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -1197,7 +1197,7 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; fillStrOp(a, typ, result.ast[bodyPos], d, src) else: fillBody(a, typ, result.ast[bodyPos], d, src) - if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not lacksMTypeField(typ): + if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not isObjLackingTypeField(typ): # bug #19205: Do not forget to also copy the hidden type field: genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src) diff --git a/compiler/types.nim b/compiler/types.nim index 1834acdffa..16853e5ffa 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1485,8 +1485,17 @@ proc commonSuperclass*(a, b: PType): PType = y = y.baseClass proc lacksMTypeField*(typ: PType): bool {.inline.} = + ## Returns true if the type is an object that lacks a m_type field. + ## It doesn't check base classes. (typ.sym != nil and sfPure in typ.sym.flags) or tfFinal in typ.flags +proc isObjLackingTypeField*(typ: PType): bool {.inline.} = + ## Returns true if the type is an object that lacks a type field. + ## Object types that store type headers are not final or pure and + ## have inheritable root types, which are not pure, neither. + result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and + (typ.baseClass == nil) or isPureObject(typ)) + include sizealignoffsetimpl proc computeSize*(conf: ConfigRef; typ: PType): BiggestInt = diff --git a/tests/ccgbugs/tcgbug.nim b/tests/ccgbugs/tcgbug.nim index 2eddc6fddc..0db2b12ea4 100644 --- a/tests/ccgbugs/tcgbug.nim +++ b/tests/ccgbugs/tcgbug.nim @@ -161,3 +161,21 @@ typedef struct { int base; } S; var t = newT() doAssert t.s.base == 1 + +type QObject* {.inheritable, pure.} = object + h*: pointer + +proc `=destroy`(self: var QObject) =discard + +proc `=copy`(dest: var QObject, source: QObject) {.error.} + +type QAbstractItemModel* = object of QObject + +type VTable = ref object + inst: QAbstractItemModel + +proc g() = + var x: VTable = VTable() + x.inst = QAbstractItemModel() + +g() From 514a25c9a217e104079a6d3a44e30faaa0e5f76a Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 25 Feb 2025 19:09:04 +0300 Subject: [PATCH 38/48] always skip static types for result of `typeof` (#24718) fixes #24715 In generic typechecking, unresolved static param symbols (i.e. `skGenericParam`) have [the static type itself](https://github.com/nim-lang/Nim/blob/1f8da3835f6e395d068c92f86787f0fa77fb6d08/compiler/semexprs.nim#L1483-L1485) as their type when used in an expression. This is not the case when the static param is resolved (the type is wrapped in static when necessary), but semchecking of types and generic typechecking expects the type of the value to be wrapped in `static` (at least `array[N, int]` breaks). So for now, to solve the issue, `typeof` just skips static types. --- compiler/semmagic.nim | 2 +- compiler/semtypes.nim | 14 ++++++++------ tests/generics/ttypeofstatic.nim | 9 +++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 tests/generics/ttypeofstatic.nim diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index f3dff366eb..18ce19edd2 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -55,7 +55,7 @@ proc semTypeOf(c: PContext; n: PNode): PNode = result.add typExpr if typExpr.typ.kind == tyFromExpr: typExpr.typ.flags.incl tfNonConstExpr - result.typ() = makeTypeDesc(c, typExpr.typ) + result.typ() = makeTypeDesc(c, typExpr.typ.skipTypes({tyStatic})) type SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index bc3ddb0806..ab0c87fbe3 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1736,10 +1736,10 @@ proc maybeAliasType(c: PContext; typeExpr, prev: PType): PType = else: result = nil -proc fixupTypeOf(c: PContext, prev: PType, typExpr: PNode) = +proc fixupTypeOf(c: PContext, prev: PType, typ: PType) = if prev != nil: let result = newTypeS(tyAlias, c) - result.rawAddSon typExpr.typ + result.rawAddSon typ result.sym = prev.sym if prev.kind != tyGenericBody: assignType(prev, result) @@ -1931,10 +1931,11 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType = openScope(c) inc c.inTypeofContext defer: dec c.inTypeofContext # compiles can raise an exception - let t = semExprWithType(c, n, {efInTypeof}) + let ex = semExprWithType(c, n, {efInTypeof}) closeScope(c) + let t = ex.typ.skipTypes({tyStatic}) fixupTypeOf(c, prev, t) - result = t.typ + result = t if result.kind == tyFromExpr: result.flags.incl tfNonConstExpr @@ -1949,10 +1950,11 @@ proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType = m = mode.intVal inc c.inTypeofContext defer: dec c.inTypeofContext # compiles can raise an exception - let t = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {}) + let ex = semExprWithType(c, n[1], if m == 1: {efInTypeof} else: {}) closeScope(c) + let t = ex.typ.skipTypes({tyStatic}) fixupTypeOf(c, prev, t) - result = t.typ + result = t if result.kind == tyFromExpr: result.flags.incl tfNonConstExpr diff --git a/tests/generics/ttypeofstatic.nim b/tests/generics/ttypeofstatic.nim new file mode 100644 index 0000000000..bda8db9b6e --- /dev/null +++ b/tests/generics/ttypeofstatic.nim @@ -0,0 +1,9 @@ +# issue #24715 + +type H[c: static[float64]] = object + value: typeof(c) + +proc u[T: H](_: typedesc[T]) = + discard default(T) + +u(H[1'f64]) From d94e5351459c7ee1e458453c01d9b67c45ce1d3d Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Tue, 25 Feb 2025 11:10:30 -0500 Subject: [PATCH 39/48] Make koch friendlier to offline environments (#24713) --- koch.nim | 2 +- tools/deps.nim | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/koch.nim b/koch.nim index 7c0b006d3b..12aaff9c0a 100644 --- a/koch.nim +++ b/koch.nim @@ -171,7 +171,7 @@ proc bundleAtlasExe(latest: bool, args: string) = cloneDependency(distDir, "https://github.com/nim-lang/atlas.git", commit = commit, allowBundled = true) cloneDependency(distDir / "atlas" / distDir, "https://github.com/nim-lang/sat.git", - commit = SatStableCommit, allowBundled = true) + commit = SatStableCommit, allowBundled = true) # installer.ini expects it under $nim/bin nimCompile("dist/atlas/src/atlas.nim", options = "-d:release --noNimblePath -d:nimAtlasBootstrap " & args) diff --git a/tools/deps.nim b/tools/deps.nim index e9ee2a534e..5160358aab 100644 --- a/tools/deps.nim +++ b/tools/deps.nim @@ -4,10 +4,12 @@ import std/private/gitutils when defined(nimPreviewSlimSystem): import std/assertions -proc exec(cmd: string) = +proc tryexec(cmd: string): int = echo "deps.cmd: " & cmd - let status = execShellCmd(cmd) - doAssert status == 0, cmd + execShellCmd(cmd) + +proc exec(cmd: string) = + doAssert tryexec(cmd) == 0, cmd proc execRetry(cmd: string) = let ok = retryCall(call = block: @@ -34,8 +36,10 @@ proc cloneDependency*(destDirBase: string, url: string, commit = commitHead, let oldDir = getCurrentDir() setCurrentDir(destDir) try: - execRetry "git fetch -q" - exec fmt"git checkout -q {commit}" + let checkoutCmd = fmt"git checkout -q {commit}" + if tryexec(checkoutCmd) != 0: + execRetry "git fetch -q" + exec checkoutCmd finally: setCurrentDir(oldDir) elif allowBundled: From a7a8e364ea466223cb212dcefd5ff401266f5250 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 26 Feb 2025 03:20:24 +0800 Subject: [PATCH 40/48] fixes #12340; enable refc with move analyzer (#23782) fixes https://github.com/nim-lang/Nim/issues/12340 --- compiler/sempass2.nim | 5 +++-- tests/collections/tseq.nim | 8 ++++++++ tests/stdlib/tmarshal.nim | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4122ec2fd6..6730861d81 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -137,8 +137,9 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info) createTypeBoundOps(tracked.graph, tracked.c, typ, info, tracked.c.idgen) - if (tfHasAsgn in typ.flags) or - optSeqDestructors in tracked.config.globalOptions: + if tracked.config.selectedGC == gcRefc or + optSeqDestructors in tracked.config.globalOptions or + tfHasAsgn in typ.flags: tracked.owner.flags.incl sfInjectDestructors proc isLocalSym(a: PEffects, s: PSym): bool = diff --git a/tests/collections/tseq.nim b/tests/collections/tseq.nim index 0f8084c787..ee5f47a3b1 100644 --- a/tests/collections/tseq.nim +++ b/tests/collections/tseq.nim @@ -240,3 +240,11 @@ block: # bug #17197 result = true doAssert needlemanWunsch("ABC", "DEFG", 1, 2, 3) + +block: # bug #12340 + func consume(x: sink seq[int]) = + x[0] += 5 + + let x = @[1, 2, 3, 4] + consume x + doAssert x == @[1, 2, 3, 4] diff --git a/tests/stdlib/tmarshal.nim b/tests/stdlib/tmarshal.nim index 32991ccc91..5bdbc6bdec 100644 --- a/tests/stdlib/tmarshal.nim +++ b/tests/stdlib/tmarshal.nim @@ -1,7 +1,9 @@ discard """ - matrix: "--mm:orc; --mm:refc" + matrix: "--mm:orc" """ +# TODO: --mm:refc + import std/marshal import std/[assertions, objectdollar, streams] From 16280d4e49716716da47b502d52ebc72d16d2e7c Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Thu, 27 Feb 2025 05:19:46 +0800 Subject: [PATCH 41/48] Improve bash completion support (#24692) Following https://github.com/nim-lang/nimble/pull/1347, this patch adds bash completion support for `nim`, `nimgrep`, `nimpretty`, `nimsuggest`. --- tools/nim.bash-completion | 530 ++++++++++++++++++++++++++++--- tools/nimgrep.bash-completion | 339 ++++++++++++++++++++ tools/nimpretty.bash-completion | 267 ++++++++++++++++ tools/nimsuggest.bash-completion | 291 +++++++++++++++++ 4 files changed, 1389 insertions(+), 38 deletions(-) create mode 100644 tools/nimgrep.bash-completion create mode 100644 tools/nimpretty.bash-completion create mode 100644 tools/nimsuggest.bash-completion diff --git a/tools/nim.bash-completion b/tools/nim.bash-completion index 8e569079ac..61636fb0a7 100644 --- a/tools/nim.bash-completion +++ b/tools/nim.bash-completion @@ -1,47 +1,501 @@ # bash completion for nim -*- shell-script -*- +__is_short_or_long() +{ + local actual short long + actual="$1" + short="$2" + long="$3" + [[ ! -z $short && $actual == $short ]] && return 0 + [[ ! -z $long && $actual == $long ]] && return 0 + return 1 +} + +__ask_for_subcmd_or_subopts() +{ + local args cmd subcmd words sub_words word_first word_last word_lastlast + local len ilast ilastlast i ele sub_len n_nopts + + args=("$@") + ask_for_what="${args[0]}" + cmd="${args[1]}" + subcmd="${args[2]}" + ilast="${args[3]}" + words=("${args[@]:4}") + len=${#words[@]} + ilastlast=$((ilast - 1)) + sub_words=("${words[@]:0:ilast}") + sub_len=${#sub_words[@]} + word_first=${words[0]} + word_last=${words[ilast]} + word_lastlast=${words[ilastlast]} + n_nopts=0 + + # printf "\n[DBUG] word_first:${word_first}|ilast:${ilast}|words(${len}):${words[*]}|sub_words(${sub_len}):${sub_words[*]}\n" + + if [[ $word_first != $cmd ]] + then + return 1 + fi + + i=0 + while [[ $i -lt $len ]] + do + ele=${words[i]} + if [[ ! $ele =~ ^- ]] + then + if [[ $ele == $cmd || $ele == $subcmd ]] + then + ((n_nopts+=1)) + elif [[ $i -eq $ilast && $ele =~ ^[a-zA-Z] ]] + then + ((i=i)) + elif [[ -z $ele ]] + then + ((i=i)) + elif [[ $ele =~ ^: ]] + then + ((i+=1)) + else + return 1 + fi + fi + ((i+=1)) + done + + case $ask_for_what in + 1) + if [[ n_nopts -eq 1 ]] + then + if [[ -z $word_last || $word_last =~ ^[a-zA-Z] ]] && [[ $word_lastlast != : ]] + then + return 0 + fi + fi + ;; + 2) + if [[ n_nopts -eq 2 ]] + then + if [[ -z $word_last ]] || [[ $word_last =~ ^[-:] ]] + then + return 0 + fi + fi + esac + + return 1 +} + +__ask_for_subcmd() +{ + __ask_for_subcmd_or_subopts 1 "$@" +} + +__ask_for_subcmd_opts() +{ + __ask_for_subcmd_or_subopts 2 "$@" +} + + _nim() { - local cur prev words cword split - _init_completion -s || return + local curr prev prevprev words + local i_curr n_words i_prev i_prevprev + + COMPREPLY=() + i_curr=$COMP_CWORD + n_words=$((i_curr+1)) + i_prev=$((i_curr-1)) + i_prevprev=$((i_curr-2)) + curr="${COMP_WORDS[i_curr]}" + prev="${COMP_WORDS[i_prev]}" + prevprev="${COMP_WORDS[i_prevprev]}" + words=("${COMP_WORDS[@]:0:n_words}") - COMPREPLY=() - cur=${COMP_WORDS[COMP_CWORD]} + local subcmds opts candids - if [ $COMP_CWORD -eq 1 ] ; then - # first item - suggest commands - kw="compile c doc compileToC cc compileToCpp cpp compileToOC objc js e rst2html rst2tex jsondoc buildIndex genDepend dump check" - COMPREPLY=( $( compgen -W "${kw}" -- $cur ) ) - return 0 - fi - case $prev in - --stackTrace|--lineTrace|--threads|-x|--checks|--objChecks|--fieldChecks|--rangeChecks|--boundChecks|--overflowChecks|-a|--assertions|--floatChecks|--nanChecks|--infChecks) - # Options that require on/off - [[ "$cur" == "=" ]] && cur="" - COMPREPLY=( $(compgen -W 'on off' -- "$cur") ) - return 0 - ;; - --opt) - [[ "$cur" == "=" ]] && cur="" - COMPREPLY=( $(compgen -W 'none speed size' -- "$cur") ) - return 0 - ;; - --app) - [[ "$cur" == "=" ]] && cur="" - COMPREPLY=( $(compgen -W 'console gui lib staticlib' -- "$cur") ) - return 0 - ;; - *) - kw="-r -p= --path= -d= --define= -u= --undef= -f --forceBuild --opt= --app= --stackTrace= --lineTrace= --threads= -x= --checks= --objChecks= --fieldChecks= --rangeChecks= --boundChecks= --overflowChecks= -a= --assertions= --floatChecks= --nanChecks= --infChecks=" - COMPREPLY=( $( compgen -W "${kw}" -- $cur ) ) - _filedir '@(nim)' - #$split - return 0 - ;; - esac - return 0 + # printf "\n[DBUG] curr:$curr|prev:$prev|words(${#words[*]}):${words[*]}\n" + + # Asking for a subcommand + if __ask_for_subcmd nim nim $i_curr "${words[@]}" + then + subcmds="" + # basic + subcmds="${subcmds} compile c" + subcmds="${subcmds} r" + subcmds="${subcmds} doc" + # advanced + subcmds="${subcmds} compileToC cc" + subcmds="${subcmds} compileToCpp cpp" + subcmds="${subcmds} compileToOC objc" + subcmds="${subcmds} js" + subcmds="${subcmds} e" + subcmds="${subcmds} md2html" + subcmds="${subcmds} rst2html" + subcmds="${subcmds} md2tex" + subcmds="${subcmds} rst2tex" + subcmds="${subcmds} doc2tex" + subcmds="${subcmds} jsondoc" + subcmds="${subcmds} ctags" + subcmds="${subcmds} buildIndex" + subcmds="${subcmds} genDepend" + subcmds="${subcmds} dump" + subcmds="${subcmds} check" + COMPREPLY=( $( compgen -W "${subcmds}" -- ${curr}) ) + return 0 + fi + + # Prioritize subcmd over opt + if false + then + return 124 + + elif false && __ask_for_subcmd_opts nim compileToC $i_curr "${words[@]}" + then # for future use + opts=() \ + && candids=() + opts+=("-u" "--undef" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + + else + opts=() \ + && candids=() + opts+=("-p" "--path" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-d" "--define" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + # note, any preceeding left parenthesis will vanish the context + opts+=("-u" "--undef" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-f" "--forceBuild" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stackTrace" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--lineTrace" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--threads" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--checks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--assertions" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--opt" "none speed size") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--debugger" "native") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--app" "console gui lib staticlib") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-r" "--run" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--eval" "CMD") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--fullhelp" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-h" "--help" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-v" "--version" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--objChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--fieldChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--rangeChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--boundChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--overflowChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--floatChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--nanChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--infChecks" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--defusages" "FILE,LINE,COL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-o" "--output" "FILE") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--outdir" "DIR") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--usenimcache" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stdout" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--colors" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--filenames" "abs canonical legacyRelProj") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--processing" "dots filenames off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--unitsep" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--declaredLocs" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--spellSuggest" "NUM") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--hints" "on off list") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--hint" "HINT:on") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--hintAsError" "HINT:on") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-w" "--warnings" "on off list") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--warning" "WARNING:on") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--warningAsError" "X:on X:off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--styleCheck" "off hint error") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--showAllMismatches" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--lib" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--import" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--include" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--nimcache" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--compileOnly" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--noLinking" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--noMain" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--genScript" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--os" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--cpu" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--debuginfo" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--passC" "OPTION") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--passL" "OPTION") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--cc" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--cincludes" "DIR") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--clibdir" "DIR") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--clib" "LIBNAME") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--project" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--docRoot" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--backend" "c cpp js objc") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--docCmd" "CMD") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--docSeeSrcUrl" "URL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--docInternal" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--lineDir" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--embedsrc" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--tlsEmulation" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--implicitStatic" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--trmacros" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--multimethods" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--hotCodeReloading" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--excessiveStackTrace" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stackTraceMsgs" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--skipCfg" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--skipUserCfg" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--skipParentCfg" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--skipProjCfg" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--mm" "orc arc refc markAndSweep boehm go none regions") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--exceptions" "setjmp cpp goto quirky") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--index" "on off only") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--noImportdoc" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--putenv" "KEY=VALUE") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--NimblePath" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--noNimblePath" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--clearNimblePath" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--cppCompileToNamespace" "NAMESPACE") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--nimMainPrefix" "PREFIX") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--expandMacro" "MACRO") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--expandArc" "PROCNAME") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--excludePath" "PATH") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--dynlibOverride" "SYMBOL") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--dynlibOverrideAll" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--listCmd" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--asm" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--parallelBuild" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--incremental" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--verbosity" "0 1 2 3") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--errorMax" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--maxLoopIterationsVM" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--experimental" "dotOperators callOperator parallel destructor notnil dynamicBindSym forLoopMacros caseStmtMacros codeReordering compiletimeFFI vmopsDanger strictFuncs views strictNotNil overloadableEnums strictEffects unicodeOperators flexibleOptionalParams strictDefs strictCaseObjects inferGenericTypes openSym genericsOpenSym vtables") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--legacy" "allowSemcheckedAstModification checkUnsignedConversions laxEffects verboseTypeMismatch emitGenerics jsNoLambdaLifting") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--benchmarkVM" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--profileVM" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--panics" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--deepcopy" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--jsbigint64" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--nimBasePattern" "nimbase.h") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + fi + + local c_short c_long c_accvals + local len i idx0 idx1 idx2 + + case $curr in + # Asking for accepted optvalues, e.g., `out:` + :) + len=${#opts[@]} + i=0 + + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" --) ) \ + && return 0 + ((i+=3)) + done + + return 124 + ;; + + *) + # When in a incomplete opt value, e.g., `--check:of` + if [[ $prev == : ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prevprev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" -- ${curr}) ) \ + && return 0 + ((i+=3)) + done + return 124 + fi + + # When in a complete optname, might need optvalue, e.g., `--check` + if [[ $curr =~ ^--?[:()a-zA-Z]+$ ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$(((i / 3 * 3))) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + + if __is_short_or_long $curr ${c_short} ${c_long} + then + if [[ ! -z $c_accvals ]] + then + COMPREPLY=( $(compgen -W "${curr}:" -- ${curr}) ) \ + && compopt -o nospace \ + && return 0 + else + COMPREPLY=( $(compgen -W "${curr}" -- ${curr}) ) \ + && return 0 + fi + fi + + ((i+=3)) + done # while + + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + + # When in an incomplete optname, e.g., `--chec` + elif [[ $curr =~ ^--?[^:]* ]] + then + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + fi + + if true + then + compopt -o filenames + COMPREPLY=( $(compgen -f -- "$curr") ) + compopt -o nospace + return 0 + fi + + ;; + esac + return 0 } && -complete -onospace -F _nim nim + complete -F _nim nim -# ex: ts=2 sw=2 et filetypesh +# ex: filetype=sh diff --git a/tools/nimgrep.bash-completion b/tools/nimgrep.bash-completion new file mode 100644 index 0000000000..87a05a4cf2 --- /dev/null +++ b/tools/nimgrep.bash-completion @@ -0,0 +1,339 @@ +# bash completion for nimgrep -*- shell-script -*- + +__is_short_or_long() +{ + local actual short long + actual="$1" + short="$2" + long="$3" + [[ ! -z $short && $actual == $short ]] && return 0 + [[ ! -z $long && $actual == $long ]] && return 0 + return 1 +} + +__ask_for_subcmd_or_subopts() +{ + local args cmd subcmd words sub_words word_first word_last word_lastlast + local len ilast ilastlast i ele sub_len n_nopts + + args=("$@") + ask_for_what="${args[0]}" + cmd="${args[1]}" + subcmd="${args[2]}" + ilast="${args[3]}" + words=("${args[@]:4}") + len=${#words[@]} + ilastlast=$((ilast - 1)) + sub_words=("${words[@]:0:ilast}") + sub_len=${#sub_words[@]} + word_first=${words[0]} + word_last=${words[ilast]} + word_lastlast=${words[ilastlast]} + n_nopts=0 + + # printf "\n[DBUG] word_first:${word_first}|ilast:${ilast}|words(${len}):${words[*]}|sub_words(${sub_len}):${sub_words[*]}\n" + + if [[ $word_first != $cmd ]] + then + return 1 + fi + + i=0 + while [[ $i -lt $len ]] + do + ele=${words[i]} + if [[ ! $ele =~ ^- ]] + then + if [[ $ele == $cmd || $ele == $subcmd ]] + then + ((n_nopts+=1)) + elif [[ $i -eq $ilast && $ele =~ ^[a-zA-Z] ]] + then + ((i=i)) + elif [[ -z $ele ]] + then + ((i=i)) + elif [[ $ele =~ ^: ]] + then + ((i+=1)) + else + return 1 + fi + fi + ((i+=1)) + done + + case $ask_for_what in + 1) + if [[ n_nopts -eq 1 ]] + then + if [[ -z $word_last || $word_last =~ ^[a-zA-Z] ]] && [[ $word_lastlast != : ]] + then + return 0 + fi + fi + ;; + 2) + if [[ n_nopts -eq 2 ]] + then + if [[ -z $word_last ]] || [[ $word_last =~ ^[-:] ]] + then + return 0 + fi + fi + esac + + return 1 +} + +__ask_for_subcmd() +{ + __ask_for_subcmd_or_subopts 1 "$@" +} + +__ask_for_subcmd_opts() +{ + __ask_for_subcmd_or_subopts 2 "$@" +} + +_nimgrep() +{ + local curr prev prevprev words + local i_curr n_words i_prev i_prevprev + + COMPREPLY=() + i_curr=$COMP_CWORD + n_words=$((i_curr+1)) + i_prev=$((i_curr-1)) + i_prevprev=$((i_curr-2)) + curr="${COMP_WORDS[i_curr]}" + prev="${COMP_WORDS[i_prev]}" + prevprev="${COMP_WORDS[i_prevprev]}" + words=("${COMP_WORDS[@]:0:n_words}") + + local subcmds opts candids + + # printf "\n[DBUG] curr:$curr|prev:$prev|words(${#words[*]}):${words[*]}\n" + + # Asking for a subcommand + if false && __ask_for_subcmd nimgrep nimgrep $i_curr "${words[@]}" + then + subcmds="" + return 0 + fi + + # Prioritize subcmd over opt + if false + then + return 124 + elif false && __ask_for_subcmd_opts nimgrep compileToC $i_curr "${words[@]}" + then + opts=() \ + && candids=() + else + opts=() \ + && candids=() + opts+=("-f" "--find" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--replace" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--confirm" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--filenames" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--peg" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--re" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-x" "--rex" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-w" "--word" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-i" "--ignoreCase" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-y" "--ignoreStyle" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-r" "--recursive" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--follow" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-s" "--sortTime" "asc desc") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--extensions" "EX") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notextensions" "EX") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--filename" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notfilename" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--dirname" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notdirname" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--dirpath" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notdirpath" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--inFile" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notinFile" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--bin" "on off only") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-t" "--text" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--inContext" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--notinContext" "PAT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--nocolor" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--colorTheme" "THEME") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--color" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--count" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-c" "--context" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-a" "--afterContext" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-b" "--beforeContext" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-g" "--group" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-l" "--newLine" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--cols" "N auto") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--onlyAscii" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-j" "--threads" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stdin" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--verbose" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-h" "--help" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("-v" "--version" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + fi + + local c_short c_long c_accvals + local len i idx0 idx1 idx2 + + case $curr in + # Asking for accepted optvalues, e.g., `out:` + :) + len=${#opts[@]} + i=0 + + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" --) ) \ + && return 0 + ((i+=3)) + done + + return 124 + ;; + + *) + # When in a incomplete opt value, e.g., `--check:of` + if [[ $prev == : ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prevprev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" -- ${curr}) ) \ + && return 0 + ((i+=3)) + done + return 124 + fi + + # When in a complete optname, might need optvalue, e.g., `--check` + if [[ $curr =~ ^--?[:()a-zA-Z]+$ ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$(((i / 3 * 3))) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + + if __is_short_or_long $curr ${c_short} ${c_long} + then + if [[ ! -z $c_accvals ]] + then + COMPREPLY=( $(compgen -W "${curr}:" -- ${curr}) ) \ + && compopt -o nospace \ + && return 0 + else + COMPREPLY=( $(compgen -W "${curr}" -- ${curr}) ) \ + && return 0 + fi + fi + + ((i+=3)) + done # while + + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + + # When in an incomplete optname, e.g., `--chec` + elif [[ $curr =~ ^--?[^:]* ]] + then + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + fi + + if true + then + compopt -o filenames + COMPREPLY=( $(compgen -f -- "$curr") ) + compopt -o nospace + return 0 + fi + + ;; + esac + return 0 + +} && + complete -F _nimgrep nimgrep + +# ex: filetype=sh diff --git a/tools/nimpretty.bash-completion b/tools/nimpretty.bash-completion new file mode 100644 index 0000000000..920db7085c --- /dev/null +++ b/tools/nimpretty.bash-completion @@ -0,0 +1,267 @@ +# bash completion for nimpretty -*- shell-script -*- + +__is_short_or_long() +{ + local actual short long + actual="$1" + short="$2" + long="$3" + [[ ! -z $short && $actual == $short ]] && return 0 + [[ ! -z $long && $actual == $long ]] && return 0 + return 1 +} + +__ask_for_subcmd_or_subopts() +{ + local args cmd subcmd words sub_words word_first word_last word_lastlast + local len ilast ilastlast i ele sub_len n_nopts + + args=("$@") + ask_for_what="${args[0]}" + cmd="${args[1]}" + subcmd="${args[2]}" + ilast="${args[3]}" + words=("${args[@]:4}") + len=${#words[@]} + ilastlast=$((ilast - 1)) + sub_words=("${words[@]:0:ilast}") + sub_len=${#sub_words[@]} + word_first=${words[0]} + word_last=${words[ilast]} + word_lastlast=${words[ilastlast]} + n_nopts=0 + + # printf "\n[DBUG] word_first:${word_first}|ilast:${ilast}|words(${len}):${words[*]}|sub_words(${sub_len}):${sub_words[*]}\n" + + if [[ $word_first != $cmd ]] + then + return 1 + fi + + i=0 + while [[ $i -lt $len ]] + do + ele=${words[i]} + if [[ ! $ele =~ ^- ]] + then + if [[ $ele == $cmd || $ele == $subcmd ]] + then + ((n_nopts+=1)) + elif [[ $i -eq $ilast && $ele =~ ^[a-zA-Z] ]] + then + ((i=i)) + elif [[ -z $ele ]] + then + ((i=i)) + elif [[ $ele =~ ^: ]] + then + ((i+=1)) + else + return 1 + fi + fi + ((i+=1)) + done + + case $ask_for_what in + 1) + if [[ n_nopts -eq 1 ]] + then + if [[ -z $word_last || $word_last =~ ^[a-zA-Z] ]] && [[ $word_lastlast != : ]] + then + return 0 + fi + fi + ;; + 2) + if [[ n_nopts -eq 2 ]] + then + if [[ -z $word_last ]] || [[ $word_last =~ ^[-:] ]] + then + return 0 + fi + fi + esac + + return 1 +} + +__ask_for_subcmd() +{ + __ask_for_subcmd_or_subopts 1 "$@" +} + +__ask_for_subcmd_opts() +{ + __ask_for_subcmd_or_subopts 2 "$@" +} + +_nimpretty() +{ + local curr prev prevprev words + local i_curr n_words i_prev i_prevprev + + COMPREPLY=() + i_curr=$COMP_CWORD + n_words=$((i_curr+1)) + i_prev=$((i_curr-1)) + i_prevprev=$((i_curr-2)) + curr="${COMP_WORDS[i_curr]}" + prev="${COMP_WORDS[i_prev]}" + prevprev="${COMP_WORDS[i_prevprev]}" + words=("${COMP_WORDS[@]:0:n_words}") + + local subcmds opts candids + + # printf "\n[DBUG] curr:$curr|prev:$prev|words(${#words[*]}):${words[*]}\n" + + # Asking for a subcommand + if false && __ask_for_subcmd nimpretty nimpretty $i_curr "${words[@]}" + then + subcmds="" + return 0 + fi + + # Prioritize subcmd over opt + if false + then + return 124 + elif false && __ask_for_subcmd_opts nimpretty compileToC $i_curr "${words[@]}" + then + opts=() \ + && candids=() + else + opts=() \ + && candids=() + opts+=("" "--out" "file") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--outDir" "DIR") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stdin" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--indent" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--maxLineLen" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--version" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--help" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + fi + + local c_short c_long c_accvals + local len i idx0 idx1 idx2 + + case $curr in + # Asking for accepted optvalues, e.g., `out:` + :) + len=${#opts[@]} + i=0 + + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" --) ) \ + && return 0 + ((i+=3)) + done + + return 124 + ;; + + *) + # When in a incomplete opt value, e.g., `--check:of` + if [[ $prev == : ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prevprev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" -- ${curr}) ) \ + && return 0 + ((i+=3)) + done + return 124 + fi + + # When in a complete optname, might need optvalue, e.g., `--check` + if [[ $curr =~ ^--?[:()a-zA-Z]+$ ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$(((i / 3 * 3))) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + + if __is_short_or_long $curr ${c_short} ${c_long} + then + if [[ ! -z $c_accvals ]] + then + COMPREPLY=( $(compgen -W "${curr}:" -- ${curr}) ) \ + && compopt -o nospace \ + && return 0 + else + COMPREPLY=( $(compgen -W "${curr}" -- ${curr}) ) \ + && return 0 + fi + fi + + ((i+=3)) + done # while + + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + + # When in an incomplete optname, e.g., `--chec` + elif [[ $curr =~ ^--?[^:]* ]] + then + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + fi + + if true + then + compopt -o filenames + COMPREPLY=( $(compgen -f -- "$curr") ) + compopt -o nospace + return 0 + fi + + ;; + esac + return 0 + +} && + complete -F _nimpretty nimpretty + +# ex: filetype=sh diff --git a/tools/nimsuggest.bash-completion b/tools/nimsuggest.bash-completion new file mode 100644 index 0000000000..e359093b4e --- /dev/null +++ b/tools/nimsuggest.bash-completion @@ -0,0 +1,291 @@ +# bash completion for nimsuggest -*- shell-script -*- + +__is_short_or_long() +{ + local actual short long + actual="$1" + short="$2" + long="$3" + [[ ! -z $short && $actual == $short ]] && return 0 + [[ ! -z $long && $actual == $long ]] && return 0 + return 1 +} + +__ask_for_subcmd_or_subopts() +{ + local args cmd subcmd words sub_words word_first word_last word_lastlast + local len ilast ilastlast i ele sub_len n_nopts + + args=("$@") + ask_for_what="${args[0]}" + cmd="${args[1]}" + subcmd="${args[2]}" + ilast="${args[3]}" + words=("${args[@]:4}") + len=${#words[@]} + ilastlast=$((ilast - 1)) + sub_words=("${words[@]:0:ilast}") + sub_len=${#sub_words[@]} + word_first=${words[0]} + word_last=${words[ilast]} + word_lastlast=${words[ilastlast]} + n_nopts=0 + + # printf "\n[DBUG] word_first:${word_first}|ilast:${ilast}|words(${len}):${words[*]}|sub_words(${sub_len}):${sub_words[*]}\n" + + if [[ $word_first != $cmd ]] + then + return 1 + fi + + i=0 + while [[ $i -lt $len ]] + do + ele=${words[i]} + if [[ ! $ele =~ ^- ]] + then + if [[ $ele == $cmd || $ele == $subcmd ]] + then + ((n_nopts+=1)) + elif [[ $i -eq $ilast && $ele =~ ^[a-zA-Z] ]] + then + ((i=i)) + elif [[ -z $ele ]] + then + ((i=i)) + elif [[ $ele =~ ^: ]] + then + ((i+=1)) + else + return 1 + fi + fi + ((i+=1)) + done + + case $ask_for_what in + 1) + if [[ n_nopts -eq 1 ]] + then + if [[ -z $word_last || $word_last =~ ^[a-zA-Z] ]] && [[ $word_lastlast != : ]] + then + return 0 + fi + fi + ;; + 2) + if [[ n_nopts -eq 2 ]] + then + if [[ -z $word_last ]] || [[ $word_last =~ ^[-:] ]] + then + return 0 + fi + fi + esac + + return 1 +} + +__ask_for_subcmd() +{ + __ask_for_subcmd_or_subopts 1 "$@" +} + +__ask_for_subcmd_opts() +{ + __ask_for_subcmd_or_subopts 2 "$@" +} + +_nimsuggest() +{ + local curr prev prevprev words + local i_curr n_words i_prev i_prevprev + + COMPREPLY=() + i_curr=$COMP_CWORD + n_words=$((i_curr+1)) + i_prev=$((i_curr-1)) + i_prevprev=$((i_curr-2)) + curr="${COMP_WORDS[i_curr]}" + prev="${COMP_WORDS[i_prev]}" + prevprev="${COMP_WORDS[i_prevprev]}" + words=("${COMP_WORDS[@]:0:n_words}") + + local subcmds opts candids + + # printf "\n[DBUG] curr:$curr|prev:$prev|words(${#words[*]}):${words[*]}\n" + + # Asking for a subcommand + if false && __ask_for_subcmd nimsuggest nimsuggest $i_curr "${words[@]}" + then + subcmds="" + return 0 + fi + + # Prioritize subcmd over opt + if false + then + return 124 + elif false && __ask_for_subcmd_opts nimsuggest compileToC $i_curr "${words[@]}" + then + opts=() \ + && candids=() + else + opts=() \ + && candids=() + opts+=("" "--autobind" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--port" "PORT") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--address" "HOST") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--stdin" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--clientProcessId" "PID") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--epc" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--debug" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--log" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--v1" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--v2" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--v3" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--v4" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--info" "nimVer protocolVer capabilities") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--refresh" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--maxresults" "N") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--tester" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--find" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--exceptionInlayHints" "on off") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + opts+=("" "--help" "") \ + && candids+=(${opts[$((${#opts[@]}-3))]} ${opts[$((${#opts[@]}-2))]}) + fi + + local c_short c_long c_accvals + local len i idx0 idx1 idx2 + + case $curr in + # Asking for accepted optvalues, e.g., `out:` + :) + len=${#opts[@]} + i=0 + + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" --) ) \ + && return 0 + ((i+=3)) + done + + return 124 + ;; + + *) + # When in a incomplete opt value, e.g., `--check:of` + if [[ $prev == : ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$((i / 3 * 3)) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + (false \ + || __is_short_or_long $prevprev ${c_short} ${c_long} \ + || false) \ + && COMPREPLY=( $(compgen -W "${c_accvals}" -- ${curr}) ) \ + && return 0 + ((i+=3)) + done + return 124 + fi + + # When in a complete optname, might need optvalue, e.g., `--check` + if [[ $curr =~ ^--?[:()a-zA-Z]+$ ]] + then + len=${#opts[@]} + i=0 + while [[ $i -lt $len ]] + do + idx0=$(((i / 3 * 3))) + idx1=$((idx0 + 1)) + idx2=$((idx1 + 1)) + c_short=${opts[idx0]} + c_long=${opts[idx1]} + c_accvals=${opts[idx2]} + + if __is_short_or_long $curr ${c_short} ${c_long} + then + if [[ ! -z $c_accvals ]] + then + COMPREPLY=( $(compgen -W "${curr}:" -- ${curr}) ) \ + && compopt -o nospace \ + && return 0 + else + COMPREPLY=( $(compgen -W "${curr}" -- ${curr}) ) \ + && return 0 + fi + fi + + ((i+=3)) + done # while + + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + + # When in an incomplete optname, e.g., `--chec` + elif [[ $curr =~ ^--?[^:]* ]] + then + if true + then + COMPREPLY=( $(compgen -W "${candids[*]}" -- "$curr") ) + compopt -o nospace + return 0 + fi + fi + + if true + then + compopt -o filenames + COMPREPLY=( $(compgen -f -- "$curr") ) + compopt -o nospace + return 0 + fi + + ;; + esac + return 0 + +} && + complete -F _nimsuggest nimsuggest + +# ex: filetype=sh From 49dfc3a0d42c5d280f9209e050135dae9dbadc54 Mon Sep 17 00:00:00 2001 From: metagn Date: Thu, 27 Feb 2025 00:20:41 +0300 Subject: [PATCH 42/48] convert tuple constructors from VM back to original types (#24710) fixes #24698 The same aim as #24224 but for tuple constructors. The difference here is that the type of a tuple constructor is always going to be valid unlike array constructors which can have `seq` etc types, so we can just generate a conversion again. If the conversion fails, it is ignored similar to #24611, this is to protect against modified typed nodes in macros. Also #24611 was only adapted to `semTupleFieldsConstr` and not `semTuplePositionsConstr`, this is now fixed. --- compiler/semexprs.nim | 26 ++++++++++++++++++++++---- tests/tuples/tconstfield.nim | 13 +++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 tests/tuples/tconstfield.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f959783225..2aa646dd60 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2944,7 +2944,14 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType typ.n.add newSymNode(f) n[i][0] = newSymNode(f) result.add n[i] + let oldType = n.typ result.typ() = typ + if oldType != nil and not hasEmpty(oldType): # see hasEmpty comment above + # convert back to old type + let conversion = indexTypesMatch(c, oldType, typ, result) + # ignore matching error, the goal is just to keep the original type info + if conversion != nil: + result = conversion proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = result = n # we don't modify n, but compute the type: @@ -2963,9 +2970,19 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT # hasEmpty/nil check is to not break existing code like # `const foo = [(1, {}), (2, {false})]`, # `const foo = if true: (0, nil) else: (1, new(int))` - n[i] = fitNode(c, expectedElemType, n[i], n[i].info) + let conversion = indexTypesMatch(c, expectedElemType, n[i].typ, n[i]) + # ignore matching error, full tuple will be matched later which may call converter, see #24609 + if conversion != nil: + n[i] = conversion addSonSkipIntLit(typ, n[i].typ.skipTypes({tySink}), c.idgen) + let oldType = n.typ result.typ() = typ + if oldType != nil and not hasEmpty(oldType): # see hasEmpty comment above + # convert back to old type + let conversion = indexTypesMatch(c, oldType, typ, result) + # ignore matching error, the goal is just to keep the original type info + if conversion != nil: + result = conversion include semobjconstr @@ -3054,9 +3071,12 @@ proc semExport(c: PContext, n: PNode): PNode = s = nextOverloadIter(o, c, a) proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = - var tupexp = semTuplePositionsConstr(c, n, flags, expectedType) + result = semTuplePositionsConstr(c, n, flags, expectedType) + var tupexp = result + while tupexp.kind == nkHiddenSubConv: tupexp = tupexp[1] var isTupleType: bool = false if tupexp.len > 0: # don't interpret () as type + internalAssert c.config, tupexp.kind == nkTupleConstr isTupleType = tupexp[0].typ.kind == tyTypeDesc # check if either everything or nothing is tyTypeDesc for i in 1.. Date: Thu, 27 Feb 2025 00:21:03 +0300 Subject: [PATCH 43/48] don't try to infer array range to unresolved range (#24709) fixes #24708 --- compiler/sigmatch.nim | 3 ++- tests/overload/tgenericrangedisamb.nim | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/overload/tgenericrangedisamb.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index b175549dcb..ddf696bec0 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1424,7 +1424,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, return isNone if fRange.rangeHasUnresolvedStatic: - if aRange.kind in {tyGenericParam} and aRange.reduceToBase() == aRange: + if (aRange.kind in {tyGenericParam} and aRange.reduceToBase() == aRange) or + (aRange.kind == tyRange and aRange.rangeHasUnresolvedStatic): return return inferStaticsInRange(c, fRange, a) elif c.c.matchedConcept != nil and aRange.rangeHasUnresolvedStatic: diff --git a/tests/overload/tgenericrangedisamb.nim b/tests/overload/tgenericrangedisamb.nim new file mode 100644 index 0000000000..19d7c97e6f --- /dev/null +++ b/tests/overload/tgenericrangedisamb.nim @@ -0,0 +1,21 @@ +# issue #24708 + +type Matrix[m, n: static int] = array[m * n, float] + +func `[]`(A: Matrix, i, j: int): float = + A[A.n * i + j] + +func `[]`(A: var Matrix, i, j: int): var float = + A[A.n * i + j] + +func `*`[m, n, p: static int](A: Matrix[m, n], B: Matrix[n, p]): Matrix[m, p] = + for i in 0 ..< m: + for k in 0 ..< p: + for j in 0 ..< n: + result[i, k] += A[i, j] * B[j, k] + +func square[n: static int](A: Matrix[n, n]): Matrix[n, n] = + A * A + +let A: Matrix[2, 2] = [-1, 1, 0, -1] +doAssert square(A) == [1.0, -2.0, 0.0, 1.0] From c452275e2942c64d871c20ab29e14497b4340173 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:45:04 +0800 Subject: [PATCH 44/48] fixes #24705; encode `static parameters` into function names for debugging (#24707) fixes #24705 ```nim proc xxx(v: static int) = echo v xxx(10) xxx(20) ``` They are mangled as `_ZN14titaniummangle7xxx_s10E` and `_ZN14titaniummangle7xxx_s20E` with `--debugger:native`. Static parameters are prefixed with `_s` to distinguish simple cases like `xxx(10, 15)` and `xxx(101, 5)` if `xxx` supports two `static[int]` parameters --- compiler/ccgtypes.nim | 8 ++++++-- compiler/ccgutils.nim | 21 +++++++++++++-------- tests/codegen/titaniummangle.nim | 9 ++++++++- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index ef7550d2c0..a49ea802ac 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -57,11 +57,15 @@ proc mangleField(m: BModule; name: PIdent): string = proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string = result = "_Z" # Common prefix in Itanium ABI - result.add encodeSym(m, s, makeUnique) + var params = "" + var staticLists = "" if s.typ.len > 1: #we dont care about the return param for i in 1.. Date: Thu, 27 Feb 2025 23:45:58 +0800 Subject: [PATCH 45/48] fixes #19728; setLen slow when shrinking seq due to zero-filling of released area (#24683) fixes #19728 don't zero-filling memory for "trivial types" without destructor in refc. I tested locally with internal apis. --- compiler/ccgexprs.nim | 10 +++++++++- lib/system/sysstr.nim | 5 +++-- tests/stdlib/tmisc_issues.nim | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 2d4a46412c..fdd8553a3e 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2198,6 +2198,13 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = else: putIntoDest(p, d, e, cIntValue(lengthOrd(p.config, typ))) else: internalError(p.config, e.info, "genArrayLen()") +proc isTrivialTypesToSnippet(t: PType): Snippet = + if containsGarbageCollectedRef(t) or + hasDestructor(t): + result = NimFalse + else: + result = NimTrue + proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = if optSeqDestructors in p.config.globalOptions: e[1] = makeAddr(e[1], p.module.idgen) @@ -2220,7 +2227,8 @@ proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc) = pExpr = cIfExpr(ra, cAddr(derefField(ra, "Sup")), NimNil) else: pExpr = ra - call.snippet = cCast(rt, cgCall(p, "setLengthSeqV2", pExpr, rti, rb)) + call.snippet = cCast(rt, cgCall(p, "setLengthSeqV2", pExpr, rti, rb, + isTrivialTypesToSnippet(t.skipTypes(abstractInst)[0]))) genAssignment(p, a, call, {}) gcUsage(p.config, e) diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index 8cefe7601f..b864da8531 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -300,7 +300,7 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, elemAlign, newLen: int): PGenericS zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) result.len = newLen -proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. +proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int, isTrivial: bool): PGenericSeq {. compilerRtl.} = sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq" if s == nil: @@ -334,7 +334,8 @@ proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. # presence of user defined destructors, the user will expect the cell to be # "destroyed" thus creating the same problem. We can destroy the cell in the # finalizer of the sequence, but this makes destruction non-deterministic. - zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) + if not isTrivial: # optimization for trivial types + zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) else: result = s zeroMem(dataPointer(result, elemAlign, elemSize, result.len), (newLen-%result.len) *% elemSize) diff --git a/tests/stdlib/tmisc_issues.nim b/tests/stdlib/tmisc_issues.nim index 86dcf41629..4f7707d976 100644 --- a/tests/stdlib/tmisc_issues.nim +++ b/tests/stdlib/tmisc_issues.nim @@ -37,3 +37,30 @@ block: # bug #16771 a.foo b doAssert a.n == 42 doAssert b.n == 1 + +block: # bug #24683 + block: + var v = newSeq[int](100) + v[99]= 444 + v.setLen(5) + v.setLen(100) + doAssert v[99] == 0 + + when not defined(js): + block: + var + x = @[1, 2, 3, 4, 45, 56, 67, 999, 88, 777] + + x.setLen(0) # zero-fills 1mb of released data + + type + TGenericSeq = object + len, reserved: int + PGenericSeq = ptr TGenericSeq + + when defined(gcRefc): + cast[PGenericSeq](x).len = 10 + else: + cast[ptr int](addr x)[] = 10 + + doAssert x == @[1, 2, 3, 4, 45, 56, 67, 999, 88, 777] From 7ecb35115b4b01b15aa8316f23ba13b763cb7e50 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:48:15 +0800 Subject: [PATCH 46/48] fixes #24339; underscores used with `fields` and `fieldPairs` (#24341) fixes #24339 --- compiler/semfields.nim | 6 ++++-- tests/stmt/tmiscunderscore.nim | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/semfields.nim b/compiler/semfields.nim index 7e2f5d6fdd..5bace728f3 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -36,7 +36,8 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = of nkIdent, nkSym: result = n let ident = considerQuotedIdent(c.c, n) - if c.replaceByFieldName: + if c.replaceByFieldName and + ident.id != ord(wUnderscore): if ident.id == considerQuotedIdent(c.c, forLoop[0]).id: let fieldName = if c.tupleType.isNil: c.field.name.s elif c.tupleType.n.isNil: "Field" & $c.tupleIndex @@ -45,7 +46,8 @@ proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode = return # other fields: for i in ord(c.replaceByFieldName).. Date: Thu, 27 Feb 2025 18:48:53 +0300 Subject: [PATCH 47/48] move nim version in issue template to the top (#24733) The point is to move it out of the current place between "Description" and "Current Output" as often these are related to each other and including the nim version in the middle breaks the flow of reading. I also thought of moving it below "Expected Output" but this felt too low for a required field and also has the same problem of overshadowing the remaining sections. Not sure if it being at the top is annoying in some other way though. --- .github/ISSUE_TEMPLATE/bug_report.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1e46e05448..52b2d80b44 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -10,6 +10,16 @@ body: Please provide a minimal code example that reproduces the bug if possible. Reports with a reproducible example or detailed information will likely receive fixes faster. +- type: textarea + id: nim-version + attributes: + label: Nim Version + description: | + Can be obtained from `nim -v` on the command line along with the OS/architecture. + For development versions, including the commit hash may help. + validations: + required: true + - type: textarea id: description attributes: @@ -19,16 +29,6 @@ body: placeholder: Bug reports with reproducible code or detailed information will be fixed faster. validations: required: true - -- type: textarea - id: nim-version - attributes: - label: Nim Version - description: | - Can be obtained from `nim -v` on the command line along with the OS/architecture. - For development versions, make sure to include the commit hash. - validations: - required: true - type: textarea id: current-logs From 7e8a650729e9434574c46350aece7609e5465253 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 28 Feb 2025 01:33:35 +0800 Subject: [PATCH 48/48] sink tuples by values (#24731) A reduced case ```nim type AnObject = tuple a: string b: int c: int proc mutate(a: sink AnObject) = `=wasMoved`(a) echo 1 # echo "Value is: ", obj.value proc bar = mutate(("1.2", 0, 0)) bar() ``` --- compiler/ccgutils.nim | 6 +++++- tests/arc/tarcmisc.nim | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index d0efc20bd2..1b75906909 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -101,7 +101,11 @@ proc ccgIntroducedPtr*(conf: ConfigRef; s: PSym, retType: PType): bool = result = true # ordinary objects are always passed by reference, # otherwise casting doesn't work of tyTuple: - result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options) + if s.typ.kind == tySink: + # it's a sink, so we pass it by value + result = false + else: + result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options) else: result = false # first parameter and return type is 'lent T'? --> use pass by pointer diff --git a/tests/arc/tarcmisc.nim b/tests/arc/tarcmisc.nim index 6b8fc3b06f..12d67a999b 100644 --- a/tests/arc/tarcmisc.nim +++ b/tests/arc/tarcmisc.nim @@ -33,6 +33,7 @@ copying 123 42 @["", "d", ""] +mutate: 1 ok destroying variable: 20 destroying variable: 10 @@ -882,3 +883,18 @@ proc test_18070() = # bug #18070 doAssert msg == "", "expected empty string but got: " & $msg test_18070() + +type AnObject = tuple + a: string + b: int + c: int + +proc mutate(a: sink AnObject) = + `=wasMoved`(a) + echo "mutate: 1" + +# echo "Value is: ", obj.value +proc bar = + mutate(("1.2", 0, 0)) + +bar()