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 001/119] 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 002/119] 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 003/119] 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 004/119] 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 005/119] 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 006/119] 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 007/119] 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 008/119] 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 009/119] 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 010/119] 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 011/119] 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 012/119] 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 013/119] 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() From e39d152b89c5635c27dd4d8fc71c48747c5fc20b Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 28 Feb 2025 17:23:19 +0300 Subject: [PATCH 014/119] handle ranges in `annotateType` for set constructors (#24737) fixes #24736 The VM can produce integer nodes with no types as set elements, which are later reannotated in `semmacrosanity.annotateType`. However the case of ranges was not handled properly. Not sure why this is a regression, probably unrelated but will have to see the bisect result to make sure. Note. Originally tried to fix this in `opcInclRange`, generated for and only for range expressions in set constructors, this seems to add the range node directly to the set node without checking if it has overlap with the existing elements by calling `nimsets` so an expression like `{cctNone, cctNone..cctHeader}` can produce `{0, 0..5}`. Doesn't seem to cause problems but `opcIncl` for single elements does check for overlap. Something else to note is that integer nodes produced by `nimsets` have proper types, so another option instead of relying on semmacrosanity to fix this would be to make `opcIncl` and `opcInclRange` call `nimsets` to add to the set node, but this might lose performance. --- compiler/semmacrosanity.nim | 7 ++++++- tests/vm/tsetrange.nim | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/vm/tsetrange.nim diff --git a/compiler/semmacrosanity.nim b/compiler/semmacrosanity.nim index f246e38fb2..cba6b4a46a 100644 --- a/compiler/semmacrosanity.nim +++ b/compiler/semmacrosanity.nim @@ -146,7 +146,12 @@ proc annotateType*(n: PNode, t: PType; conf: ConfigRef) = of nkCurly: if x.kind in {tySet}: n.typ() = t - for m in n: annotateType(m, x.elemType, conf) + for m in n: + if m.kind == nkRange: + annotateType(m[0], x.elemType, conf) + annotateType(m[1], x.elemType, conf) + else: + annotateType(m, x.elemType, conf) else: globalError(conf, n.info, "{} must have the set type") of nkFloatLit..nkFloat128Lit: diff --git a/tests/vm/tsetrange.nim b/tests/vm/tsetrange.nim new file mode 100644 index 0000000000..a0b8ce9c4a --- /dev/null +++ b/tests/vm/tsetrange.nim @@ -0,0 +1,17 @@ +# issue #24736 + +import std/setutils + +type CcsCatType = enum cctNone, cctHeader, cctIndex, cctSetup, cctUnk1, cctStream + +block: # original issue + const CCS_CAT_TYPES = fullSet(CcsCatType) + proc test(t: int): bool = t.CcsCatType in CCS_CAT_TYPES + discard test(5) + +block: # minimized + func foo(): set[CcsCatType] = + {cctNone..cctHeader} + const CCS_CAT_TYPES = foo() + proc test(t: int): bool = t.CcsCatType in CCS_CAT_TYPES + discard test(5) From 569d02e212774ca4745ad968d05b69b75a9bcec7 Mon Sep 17 00:00:00 2001 From: metagn Date: Wed, 5 Mar 2025 15:47:56 +0300 Subject: [PATCH 015/119] generate tyFromExpr for `typeof` static param with generic base type (#24745) fixes #24743, refs #24718 We cannot do this in general for any expression with generic type because the `typeof` logic is called for things like `type Foo` in: ```nim type Foo[T] = object proc init(_: type Foo) = discard ``` We also cannot use `containsUnresolvedType` to work around this specific case because the base type of `static[auto]` is not unresolved, it is a typeclass that isn't lifted to a parameter. The behavior of generating `tyFromExpr` is also consistent with pre-2.0, so we do this in this special case of `static`. --- compiler/semmagic.nim | 10 +++++++++- compiler/semtypes.nim | 24 ++++++++++++++++++------ tests/generics/ttypeofstatic.nim | 24 ++++++++++++++++++------ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 18ce19edd2..e79cfa612a 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -55,7 +55,15 @@ 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.skipTypes({tyStatic})) + var t = typExpr.typ + if t.kind == tyStatic: + let base = t.skipTypes({tyStatic}) + if c.inGenericContext > 0 and base.containsGenericType: + t = makeTypeFromExpr(c, copyTree(typExpr)) + t.flags.incl tfNonConstExpr + else: + t = base + result.typ() = makeTypeDesc(c, t) type SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index ab0c87fbe3..ea150a4068 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1933,11 +1933,17 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType = defer: dec c.inTypeofContext # compiles can raise an exception let ex = semExprWithType(c, n, {efInTypeof}) closeScope(c) - let t = ex.typ.skipTypes({tyStatic}) - fixupTypeOf(c, prev, t) - result = t + result = ex.typ if result.kind == tyFromExpr: result.flags.incl tfNonConstExpr + elif result.kind == tyStatic: + let base = result.skipTypes({tyStatic}) + if c.inGenericContext > 0 and base.containsGenericType: + result = makeTypeFromExpr(c, copyTree(ex)) + result.flags.incl tfNonConstExpr + else: + result = base + fixupTypeOf(c, prev, result) proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType = openScope(c) @@ -1952,11 +1958,17 @@ proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType = defer: dec c.inTypeofContext # compiles can raise an exception 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 + result = ex.typ if result.kind == tyFromExpr: result.flags.incl tfNonConstExpr + elif result.kind == tyStatic: + let base = result.skipTypes({tyStatic}) + if c.inGenericContext > 0 and base.containsGenericType: + result = makeTypeFromExpr(c, copyTree(ex)) + result.flags.incl tfNonConstExpr + else: + result = base + fixupTypeOf(c, prev, result) proc semTypeIdent(c: PContext, n: PNode): PSym = if n.kind == nkSym: diff --git a/tests/generics/ttypeofstatic.nim b/tests/generics/ttypeofstatic.nim index bda8db9b6e..d5aa9dadef 100644 --- a/tests/generics/ttypeofstatic.nim +++ b/tests/generics/ttypeofstatic.nim @@ -1,9 +1,21 @@ -# issue #24715 +block: # issue #24715 + type H[c: static[float64]] = object + value: typeof(c) -type H[c: static[float64]] = object - value: typeof(c) + proc u[T: H](_: typedesc[T]) = + discard default(T) -proc u[T: H](_: typedesc[T]) = - discard default(T) + u(H[1'f64]) -u(H[1'f64]) +block: # issue #24743 + type + K[w: static[auto]] = typeof(w) + V = object + a: K[0] + GenericV[w: static[auto]] = object + a: typeof(w) + + var x: GenericV[0] + doAssert x.a is int + var y: GenericV["abc"] + doAssert y.a is string From dfab30734b08a021de53f63ccddc65701f356e67 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 7 Mar 2025 12:14:00 -0500 Subject: [PATCH 016/119] new-style concepts adjusments (#24697) Yet another one of these. Multiple changes piled up in this one. I've only minimally cleaned it for now (debug code is still here etc). Just want to start putting this up so I might get feedback. I know this is a lot and you all are busy with bigger things. As per my last PR, this might just contain changes that are not ready. ### concept instantiation uniqueness It has already been said that concepts like `ArrayLike[int]` is not unique for each matching type of that concept. Likewise the compiler needs to instantiate a new proc for each unique *bound* type not each unique invocation of `ArrayLike` ### generic parameter bindings Couple of things here. The code in sigmatch has to give it's bindings to the code in concepts, else the information is lost in that step. The code that prepares the generic variables bound in concepts was also changed slightly. Net effect is that it works better. I did choose to use the `LayedIdTable` instead of the `seq`s in `concepts.nim`. This was mostly to avoid confusing myself. It also avoids some unnecessary movings around. I wouldn't doubt this is slightly less performant, but not much in the grand scheme of things and I would prefer to keep things as easy to understand as possible for as long as possible because this stuff can get confusing. ### various fixes in the matching logic Certain forms of modifiers like `var` and generic types like `tyGenericInst` and `tyGenericInvocation` have logic adjustments based on my testing and usage ### signature matching method adjustment This is the weird one, like my last PR. I thought a lot about the feedback from my last attempt and this is what I came up with. Perhaps unfortunately I am preoccupied with a slight grey area. consider the follwing: ```nim type C1 = concept proc p[T](s: Self; x: T) C2[T] = concept proc p(s: Self; x: T) ``` It would be temping to say that these are the same, but I don't think they are. `C2` makes each invocation distinct, and this has important implications in the type system. eg `C2[int]` is not the same type as `C2[string]` and this means that signatures are meant to accept a type that only matches `p` for a single type per unique binding. For `C1` all are the same and the binding `p` accepts multiple types. There are multiple variations of this type classes, `tyAnything` and the like. The make things more complicated, an implementation might match: ```nim type A = object C3 = concept proc p(s: Self; x: A) ``` if the implementation defines: ```nim proc p(x: Impl; y: object) ``` while a concept that fits `C2` may be satisfied by something like: ```nim proc p(x: Impl; y: int) proc spring[T](x: C2[T]) ``` it just depends. None of this is really a problem, it just seems to provoke some more logic in `concepts.nim` that makes all of this (appear to?) work. The logic checks for both kinds of matches with a couple of caveats. The fist is that some unbind-able arrangements may be matched during overload resolution. I don't think this is avoidable and I actually think this is a good way to get a failed compilation. So, first note imo is that failing during binding is preferred to forcing the programming to write annoying stub procs and putting insane gymnastics in the compiler. Second thing is: I think this logic is way to accepting for some parts of overload resolutions. Particularly in `checkGeneric` when disambiguation is happening. Things get hard to understand for me here. ~~I made it so the implicit bindings to not count during disambiguation~~. I still need to test this more, but the thought is that it would help curb excessive ambiguity errors. Again, I'm sorry for this being so many changes. It's probably inconvenient. --------- Co-authored-by: Andreas Rumpf --- compiler/concepts.nim | 530 ++++++++++++------ compiler/layeredtable.nim | 11 +- compiler/semcall.nim | 3 +- compiler/semtypes.nim | 15 +- compiler/semtypinst.nim | 9 +- compiler/sigmatch.nim | 48 +- compiler/types.nim | 19 +- doc/manual.md | 91 +++ tests/concepts/conceptv2negative/tmarrget.nim | 12 + .../conceptv2negative/tmissingbind.nim | 26 + tests/concepts/tconceptsv2.nim | 339 ++++++++++- 11 files changed, 875 insertions(+), 228 deletions(-) create mode 100644 tests/concepts/conceptv2negative/tmarrget.nim create mode 100644 tests/concepts/conceptv2negative/tmissingbind.nim diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 139f851667..af06f8cdca 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -11,7 +11,7 @@ ## for details. Note this is a first implementation and only the "Concept matching" ## section has been implemented. -import ast, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable +import ast, astalgo, semdata, lookups, lineinfos, idents, msgs, renderer, types, layeredtable import std/intsets @@ -19,7 +19,7 @@ when defined(nimPreviewSlimSystem): import std/assertions const - logBindings = false + logBindings = when defined(debugConcepts): true else: false ## Code dealing with Concept declarations ## -------------------------------------- @@ -70,26 +70,87 @@ proc semConceptDeclaration*(c: PContext; n: PNode): PNode = ## ---------------- type + MatchFlags* = enum + mfDontBind # Do not bind generic parameters + mfCheckGeneric # formal <- formal comparison as opposed to formal <- operand + MatchCon = object ## Context we pass around during concept matching. - inferred: seq[(PType, PType)] ## we need a seq here so that we can easily undo inferences \ - ## that turned out to be wrong. + bindings: LayeredIdTable marker: IntSet ## Some protection against wild runaway recursions. potentialImplementation: PType ## the concrete type that might match the concept we try to match. magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and ## cannot be fixed that easily. ## Thus we special case it here. - concpt: PType + concpt: PType ## current concept being evaluated + depthCount = 0 + flags: set[MatchFlags] + + MatchKind = enum + mkNoMatch, mkSubset, mkSame + +const + asymmetricConceptParamMods = {tyVar, tySink, tyLent, tyOwned, tyAlias, tyInferred} # param modifiers that to not have to match implementation -> concept + bindableTypes = {tyGenericParam, tyOr, tyTypeDesc} + +proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool + +proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool + +proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool + +proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool proc existingBinding(m: MatchCon; key: PType): PType = ## checks if we bound the type variable 'key' already to some ## concrete type. - for i in 0.. 0: + # concepts that are more then 2 levels deep are treated like + # tyAnything to stop dependencies from getting out of control + return true + var efPot = potentialImpl + if potentialImpl.isSelf: + if m.concpt.n == concpt.n: + return true + efPot = m.potentialImplementation + + var oldBindings = m.bindings + m.bindings = newTypeMapLayer(m.bindings) + let oldPotentialImplementation = m.potentialImplementation + m.potentialImplementation = efPot + let oldConcept = m.concpt + m.concpt = concpt + + var invocation: PType = nil + if f.kind in {tyGenericInvocation, tyGenericInst}: + invocation = f + inc m.depthCount + result = processConcept(c, concpt, invocation, oldBindings, m) + dec m.depthCount + m.potentialImplementation = oldPotentialImplementation + m.concpt = oldConcept + m.bindings = oldBindings + +proc cmpConceptDefs(c: PContext, fn, an: PNode, m: var MatchCon): bool= + if fn.kind != an.kind: + return false + if fn[namePos].sym.name != an[namePos].sym.name: + return false + let + ft = fn.defSignatureType + at = an.defSignatureType + if ft.len != at.len: + return false + + for i in 1 ..< ft.n.len: + m.bindings = m.bindings.newTypeMapLayer() + + let aType = at.n[i].typ + let fType = ft.n[i].typ + + if aType.isSelf and fType.isSelf: + continue + + if not matchType(c, fType, aType, m): + m.bindings.setToPreviousLayer() + return false + result = true + if not matchReturnType(c, ft.returnType, at.returnType, m): + m.bindings.setToPreviousLayer() + result = false + +proc conceptsMatch(c: PContext, fc, ac: PType; m: var MatchCon): MatchKind = + # XXX: In the future this may need extra parameters to carry info for container types + if fc.n == ac.n: + # This will have to take generic parameters into account at some point + return mkSame + let + fn = fc.conceptBody + an = ac.conceptBody + sameLen = fc.len == ac.len + var match = false + for fdef in fn: + var cmpResult = false + for ia, ndef in an: + match = cmpConceptDefs(c, fdef, ndef, m) + if match: + break + if not match: + return mkNoMatch + return mkSubset + +proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = ## The heart of the concept matching process. 'f' is the formal parameter of some ## routine inside the concept that we're looking for. 'a' is the formal parameter ## of a routine that might match. - const - ignorableForArgType = {tyVar, tySink, tyLent, tyOwned, tyGenericInst, tyAlias, tyInferred} - var a = ao + var + a = ao + f = fo - case a.kind - of tyGenericParam: - let binding = m.existingBinding(a) - if binding != nil: - a = binding - else: - discard + if a.kind in bindableTypes: + a = existingBinding(m, ao) + if a == ao and a.kind == tyGenericParam and a.hasElementType and a.elementType.kind != tyNone: + a = a.elementType + if f.isConcept: + if a.acceptsAllTypes: + return false + if a.isConcept: + # if f is a subset of a then any match to a will also match f. Not the other way around + return conceptsMatch(c, a.reduceToBase, f.reduceToBase, m) >= mkSubset + else: + return matchConceptToImpl(c, f, a, m) + + result = false + case f.kind of tyAlias: result = matchType(c, f.skipModifier, a, m) of tyTypeDesc: if isSelf(f): + let ua = a.skipTypes(asymmetricConceptParamMods) if m.magic in {mArrPut, mArrGet}: - result = false if m.potentialImplementation.reduceToBase.kind in arrPutGetMagicApplies: - m.inferred.add((a, last m.potentialImplementation)) + bindParam(c, m, a, last m.potentialImplementation) result = true + #elif ua.isConcept: + # result = matchType(c, m.concpt, ua, m) else: - result = matchType(c, a, m.potentialImplementation, m) + result = matchType(c, a.skipTypes(ignorableForArgType), m.potentialImplementation, m) else: - if a.kind == tyTypeDesc and f.hasElementType == a.hasElementType: - if f.hasElementType: + if a.kind == tyTypeDesc: + if not(a.hasElementType) or a.elementType.kind == tyNone: + result = true + elif f.hasElementType: result = matchType(c, f.elementType, a.elementType, m) - else: - result = true # both lack it - else: - result = false - of tyGenericInvocation: - result = false - if a.kind == tyGenericInst and a.genericHead.kind == tyGenericBody: - if sameType(f.genericHead, a.genericHead) and f.kidsLen == a.kidsLen-1: - result = matchKids(c, f, a, m, start=FirstGenericParamAt) - of tyGenericParam: - let ak = a.skipTypes({tyVar, tySink, tyLent, tyOwned}) - if ak.kind in {tyTypeDesc, tyStatic} and not isSelf(ak): - result = false - else: - let old = existingBinding(m, f) - if old == nil: - if f.hasElementType and f.elementType.kind != tyNone: - # also check the generic's constraints: - let oldLen = m.inferred.len - result = matchType(c, f.elementType, a, m) - m.inferred.setLen oldLen - if result: - when logBindings: echo "A adding ", f, " ", ak - m.inferred.add((f, ak)) - elif m.magic == mArrGet and ak.kind in {tyArray, tyOpenArray, tySequence, tyVarargs, tyCstring, tyString}: - when logBindings: echo "B adding ", f, " ", last ak - m.inferred.add((f, last ak)) - result = true - else: - when logBindings: echo "C adding ", f, " ", ak - m.inferred.add((f, ak)) - #echo "binding ", typeToString(ak), " to ", typeToString(f) - result = true - elif not m.marker.containsOrIncl(old.id): - result = matchType(c, old, ak, m) - if m.magic == mArrPut and ak.kind == tyGenericParam: - result = true - else: - result = false - #echo "B for ", result, " to ", typeToString(a), " to ", typeToString(m.potentialImplementation) of tyVar, tySink, tyLent, tyOwned: # modifiers in the concept must be there in the actual implementation # too but not vice versa. @@ -186,91 +315,41 @@ proc matchType(c: PContext; f, ao: PType; m: var MatchCon): bool = result = matchType(c, f.elementType, a.elementType, m) elif m.magic == mArrPut: result = matchType(c, f.elementType, a, m) - else: - result = false of tyEnum, tyObject, tyDistinct: - result = sameType(f, a) + if a.kind in ignorableForArgType: + result = matchType(c, f, a.skipTypes(ignorableForArgType), m) + else: + result = sameType(f, a) of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid: result = a.skipTypes(ignorableForArgType).kind == f.kind of tyBool, tyChar, tyInt..tyUInt64: let ak = a.skipTypes(ignorableForArgType) result = ak.kind == f.kind or ak.kind == tyOrdinal or - (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) - of tyConcept: - if a.kind == tyConcept and f.n == a.n: - result = true - elif m.concpt.size == szIllegalRecursion: - result = false - else: - let oldLen = m.inferred.len - let oldPotentialImplementation = m.potentialImplementation - m.potentialImplementation = a - m.concpt.size = szIllegalRecursion - let oldConcept = m.concpt - m.concpt = f - result = conceptMatchNode(c, f.n.lastSon, m) - m.potentialImplementation = oldPotentialImplementation - m.concpt = oldConcept - m.concpt.size = szUnknownSize - if not result: - m.inferred.setLen oldLen - of tyGenericBody: - var ak = a - if a.kind == tyGenericBody: - ak = last(a) - result = matchType(c, last(f), ak, m) - of tyCompositeTypeClass: - var ak = if a.kind == tyCompositeTypeClass: a.last else: a - result = matchType(c, last(f), ak, m) - of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr, - tyGenericInst: - # ^ XXX Rewrite this logic, it's more complex than it needs to be. - if f.kind == tyArray and f.kidsLen == 3: + (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) + of tyArray, tyTuple, tyVarargs, tyOpenArray, tyRange, tySequence, tyRef, tyPtr: + if f.kind == tyArray and f.kidsLen == 3 and a.kind == tyArray: # XXX: this is a work-around! # system.nim creates these for the magic array typeclass result = true else: - result = false let ak = a.skipTypes(ignorableForArgType - {f.kind}) if ak.kind == f.kind and f.kidsLen == ak.kidsLen: result = matchKids(c, f, ak, m) - of tyOr: - let oldLen = m.inferred.len - if a.kind == tyOr: - # say the concept requires 'int|float|string' if the potentialImplementation - # says 'int|string' that is good enough. - var covered = 0 - for ff in f.kids: - for aa in a.kids: - let oldLenB = m.inferred.len - let r = matchType(c, ff, aa, m) - if r: - inc covered + of tyGenericInvocation, tyGenericInst: + result = false + let ea = a.skipTypes(ignorableForArgType) + if ea.kind in {tyGenericInst, tyGenericInvocation}: + var + k1 = f.kidsLen - ord(f.kind == tyGenericInst) + k2 = ea.kidsLen - ord(ea.kind == tyGenericInst) + if sameType(f.genericHead, ea.genericHead) and k1 == k2: + for i in 1 ..< k2: + if not matchType(c, f[i], ea[i], m): break - m.inferred.setLen oldLenB - - result = covered >= a.kidsLen - if not result: - m.inferred.setLen oldLen - else: - result = false - for ff in f.kids: - result = matchType(c, ff, a, m) - if result: break # and remember the binding! - m.inferred.setLen oldLen - of tyNot: - if a.kind == tyNot: - result = matchType(c, f.elementType, a.elementType, m) - else: - let oldLen = m.inferred.len - result = not matchType(c, f.elementType, a, m) - m.inferred.setLen oldLen - of tyAnything: - result = true + result = true of tyOrdinal: result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam of tyStatic: - result = false var scomp = f.base if scomp.kind == tyGenericParam: if f.base.kidsLen > 0: @@ -279,8 +358,71 @@ proc matchType(c: PContext; f, ao: PType; m: var MatchCon): bool = result = matchType(c, scomp, a.base, m) else: result = matchType(c, scomp, a, m) + of tyGenericParam: + if a.acceptsAllTypes: + discard bindParam(c, m, f, a) + result = f.acceptsAllTypes + else: + result = bindParam(c, m, f, a) + of tyAnything: + result = true + of tyNot: + if a.kind == tyNot: + result = matchType(c, f.elementType, a.elementType, m) + else: + m.bindings = m.bindings.newTypeMapLayer() + result = not matchType(c, f.elementType, a, m) + m.bindings.setToPreviousLayer() + of tyAnd: + m.bindings = m.bindings.newTypeMapLayer() + result = true + for ff in traverseTyOr(f): + let r = matchType(c, ff, a, m) + if not r: + m.bindings.setToPreviousLayer() + result = false + break + of tyGenericBody: + var ak = a + if a.kind == tyGenericBody: + ak = last(a) + result = matchType(c, last(f), ak, m) + of tyCompositeTypeClass: + if a.kind == tyCompositeTypeClass: + result = matchKids(c, f, a, m) + else: + result = matchType(c, last(f), a, m) + of tyBuiltInTypeClass: + let target = f.genericHead.kind + result = a.skipTypes(ignorableForArgType).reduceToBase.kind == target + of tyOr: + if a.kind == tyOr: + var covered = 0 + for ff in traverseTyOr(f): + for aa in traverseTyOr(a): + m.bindings = m.bindings.newTypeMapLayer() + let r = matchType(c, ff, aa, m) + if r: + inc covered + break + m.bindings.setToPreviousLayer() + + result = covered >= a.kidsLen + else: + for ff in f.kids: + m.bindings = m.bindings.newTypeMapLayer() + result = matchType(c, ff, a, m) + if result: break # and remember the binding! + m.bindings.setToPreviousLayer() else: result = false + if result and ao.kind == tyGenericParam: + let bf = if f.isSelf: m.potentialImplementation else: f + if bindParam(c, m, ao, bf): + when logBindings: echo " ^ reverse binding" + +proc checkConstraint(c: PContext; f, a: PType; m: var MatchCon): bool = + result = matchType(c, f, a, m) or matchType(c, a, f, m) proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool = ## Like 'matchType' but with extra logic dealing with proc return types @@ -290,30 +432,38 @@ proc matchReturnType(c: PContext; f, a: PType; m: var MatchCon): bool = elif a == nil: result = false else: - result = matchType(c, f, a, m) + result = checkConstraint(c, f, a, m) proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = ## Checks if 'candidate' matches 'n' from the concept body. 'n' is a nkProcDef ## or similar. # watch out: only add bindings after a completely successful match. - let oldLen = m.inferred.len + m.bindings = m.bindings.newTypeMapLayer() let can = candidate.typ.n - let con = n[0].sym.typ.n - + let con = defSignatureType(n).n if can.len < con.len: # too few arguments, cannot be a match: return false - + + if can.len > con.len: + # too many arguments (not optional) + for i in con.len ..< can.len: + if can[i].sym.ast == nil: + return false + + when defined(debugConcepts): + echo "considering: ", renderTree(candidate.procDefSignature), " ", candidate.magic + let common = min(can.len, con.len) for i in 1 ..< common: - if not matchType(c, con[i].typ, can[i].typ, m): - m.inferred.setLen oldLen + if not checkConstraint(c, con[i].typ, can[i].typ, m): + m.bindings.setToPreviousLayer() return false - - if not matchReturnType(c, n[0].sym.typ.returnType, candidate.typ.returnType, m): - m.inferred.setLen oldLen + + if not matchReturnType(c, n.defSignatureType.returnType, candidate.typ.returnType, m): + m.bindings.setToPreviousLayer() return false # all other parameters have to be optional parameters: @@ -321,7 +471,7 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = assert can[i].kind == nkSym if can[i].sym.ast == nil: # has too many arguments one of which is not optional: - m.inferred.setLen oldLen + m.bindings.setToPreviousLayer() return false return true @@ -329,13 +479,14 @@ proc matchSym(c: PContext; candidate: PSym, n: PNode; m: var MatchCon): bool = proc matchSyms(c: PContext, n: PNode; kinds: set[TSymKind]; m: var MatchCon): bool = ## Walk the current scope, extract candidates which the same name as 'n[namePos]', ## 'n' is the nkProcDef or similar from the concept that we try to match. + result = false var candidates = searchScopes(c, n[namePos].sym.name, kinds) searchImportsAll(c, n[namePos].sym.name, kinds, candidates) for candidate in candidates: - #echo "considering ", typeToString(candidate.typ), " ", candidate.magic m.magic = candidate.magic - if matchSym(c, candidate, n, m): return true - result = false + if matchSym(c, candidate, n, m): + result = true + break proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool = ## Traverse the concept's AST ('n') and see if every declaration inside 'n' @@ -368,7 +519,48 @@ proc conceptMatchNode(c: PContext; n: PNode; m: var MatchCon): bool = # error was reported earlier. result = false -proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable; invocation: PType): bool = +proc fixBindings(bindings: var LayeredIdTable; concpt: PType; invocation: PType; m: var MatchCon) = + # invocation != nil means we have a non-atomic concept: + if invocation != nil and invocation.kind == tyGenericInvocation: + assert concpt.sym.typ.kind == tyGenericBody + + for i in 0 .. concpt.sym.typ.len - 1: + let thisSym = concpt.sym.typ[i] + if lookup(bindings, thisSym) != nil: + # dont trust the bindings over existing ones + continue + let found = m.bindings.lookup(thisSym) + if found != nil: + when logBindings: echo "Invocation bind: ", thisSym, " ", found + bindings.put(thisSym, found) + + # bind even more generic parameters + let genBody = invocation.base + assert genBody.kind == tyGenericBody + for i in FirstGenericParamAt ..< invocation.kidsLen: + let bpram = genBody[i - 1] + if lookup(bindings, invocation[i]) != nil: + # dont trust the bindings over existing ones + continue + let boundV = lookup(bindings, bpram) + when logBindings: echo "generic body bind: '", invocation[i], "' '", boundV, "'" + if boundV != nil: + bindings.put(invocation[i], boundV) + bindings.put(concpt, m.potentialImplementation) + +proc processConcept(c: PContext; concpt, invocation: PType, bindings: var LayeredIdTable; m: var MatchCon): bool = + m.bindings = m.bindings.newTypeMapLayer() + if invocation != nil and invocation.kind == tyGenericInst: + let genericBody = invocation.base + for i in 1..= mkSubset + elif arg.acceptsAllTypes: + # XXX: I think this is wrong, or at least partially wrong. Can still test ambiguous types + result = false + elif mfCheckGeneric in m.flags: + # prioritize concepts the least. Specifically if the arg is not a catch all as per above + result = true + else: + result = processConcept(c, concpt, invocation, bindings, m) + + diff --git a/compiler/layeredtable.nim b/compiler/layeredtable.nim index 565fb95464..61a86cff84 100644 --- a/compiler/layeredtable.nim +++ b/compiler/layeredtable.nim @@ -1,4 +1,4 @@ -import std/tables +import std/[tables] import ast type @@ -53,6 +53,15 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} = let tmp = pt.nextLayer[] pt = tmp +iterator pairs*(pt: LayeredIdTable): (ItemId, PType) = + var tm = pt + while true: + for (k, v) in pairs(tm.topLayer): + yield (k, v) + if tm.nextLayer == nil: + break + tm.setToPreviousLayer + proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType = result = nil var tm = typeMap diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 92276b8487..0b1236b254 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -144,7 +144,6 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, while true: determineType(c, sym) z = initCandidate(c, sym, initialBinding, scope, diagnosticsFlag) - # this is kinda backwards as without a check here the described # problems in recalc would not happen, but instead it 100% # does check forever in some cases @@ -184,7 +183,7 @@ proc pickBestCandidate(c: PContext, headSymbol: PNode, # 1) new symbols are discovered but the loop ends before we recalc # 2) new symbols are discovered and resemmed forever # not 100% sure if these are possible though as they would rely - # on somehow introducing a new overload during overload resolution + # on somehow introducing a new overload during overload resolution # Symbol table has been modified. Restart and pre-calculate all syms # before any further candidate init and compare. SLOW, but rare case. diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index ea150a4068..8bca77add5 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1289,12 +1289,14 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, paramType[i] = lifted result = paramType result.last.shouldHaveMeta - - let liftBody = recurse(paramType.skipModifier, true) - if liftBody != nil: - result = liftBody - result.flags.incl tfHasMeta - #result.shouldHaveMeta + if paramType.isConcept: + return addImplicitGeneric(c, paramType, paramTypId, info, genericParams, paramName) + else: + let liftBody = recurse(paramType.skipModifier, true) + if liftBody != nil: + result = liftBody + result.flags.incl tfHasMeta + #result.shouldHaveMeta of tyGenericInvocation: result = nil @@ -1308,7 +1310,6 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, # this may happen for proc type appearing in a type section # before one of its param types return - if body.last.kind == tyUserTypeClass: let expanded = instGenericContainer(c, info, paramType, allowMetaTypes = true) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 6c81f8ac7b..4637ea4046 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -608,10 +608,13 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false): result = t if t == nil: return + var et = t + if t.isConcept: + et = t.reduceToBase const lookupMetas = {tyStatic, tyGenericParam, tyConcept} + tyTypeClasses - {tyAnything} - if t.kind in lookupMetas or - (t.kind == tyAnything and tfRetType notin t.flags): - let lookup = cl.typeMap.lookup(t) + if et.kind in lookupMetas or + (et.kind == tyAnything and tfRetType notin et.flags): + let lookup = cl.typeMap.lookup(et) if lookup != nil: return lookup case t.kind diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index ddf696bec0..47d155e192 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -20,6 +20,7 @@ import std/[intsets, strutils, tables] when defined(nimPreviewSlimSystem): import std/assertions + type MismatchKind* = enum kUnknown, kAlreadyGiven, kUnknownNamedParam, kTypeMismatch, kVarNeeded, @@ -94,6 +95,7 @@ type trNoCovariance trBindGenericParam # bind tyGenericParam even with trDontBind trIsOutParam + trCheckGeneric TTypeRelFlags* = set[TTypeRelFlag] @@ -297,9 +299,9 @@ proc checkGeneric(a, b: TCandidate): int = var winner = 0 for aai, bbi in underspecifiedPairs(aa, bb, 1): var ma = newCandidate(c, bbi) - let tra = typeRel(ma, bbi, aai, {trDontBind}) + let tra = typeRel(ma, bbi, aai, {trDontBind, trCheckGeneric}) var mb = newCandidate(c, aai) - let trb = typeRel(mb, aai, bbi, {trDontBind}) + let trb = typeRel(mb, aai, bbi, {trDontBind, trCheckGeneric}) if tra == isGeneric and trb in {isNone, isInferred, isInferredConvertible}: if winner == -1: return 0 winner = 1 @@ -363,6 +365,8 @@ proc sumGeneric(t: PType): int = result += sumGeneric(a) break else: + if t.isConcept: + result += t.reduceToBase.conceptBody.len break proc complexDisambiguation(a, b: PType): int = @@ -1138,6 +1142,24 @@ proc isCovariantPtr(c: var TCandidate, f, a: PType): bool = else: return false +proc enterConceptMatch(c: var TCandidate; f,a: PType, flags: TTypeRelFlags): TTypeRelation = + var + conceptFlags: set[MatchFlags] = {} + container: PType = nil + concpt = f + if concpt.kind != tyConcept: + container = concpt + concpt = container.reduceToBase + if trDontBind in flags: + conceptFlags.incl mfDontBind + if trCheckGeneric in flags: + conceptFlags.incl mfCheckGeneric + let mres = concepts.conceptMatch(c.c, concpt, a, c.bindings, container, flags = conceptFlags) + if mres: + isGeneric + else: + isNone + when false: proc maxNumericType(prev, candidate: PType): PType = let c = candidate.skipTypes({tyRange}) @@ -1643,8 +1665,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, let roota = if skipBoth or deptha > depthf: a.skipGenericAlias else: a let rootf = if skipBoth or depthf > deptha: f.skipGenericAlias else: f - - if a.kind == tyGenericInst: + + if f.isConcept: + result = enterConceptMatch(c, rootf, roota, flags) + elif a.kind == tyGenericInst: if roota.base == rootf.base: let nextFlags = flags + {trNoCovariance} var hasCovariance = false @@ -1715,7 +1739,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, var x = a.skipGenericAlias if x.kind == tyGenericParam and x.len > 0: x = x.last - let concpt = f[0].skipTypes({tyGenericBody}) + let concpt = f.reduceToBase var preventHack = concpt.kind == tyConcept if x.kind == tyOwned and f[0].kind != tyOwned: preventHack = true @@ -1748,9 +1772,8 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, # Workaround for regression #4589 if f[i].kind != tyTypeDesc: return result = isGeneric - elif x.kind == tyGenericInst and concpt.kind == tyConcept: - result = if concepts.conceptMatch(c.c, concpt, x, c.bindings, f): isGeneric - else: isNone + elif concpt.kind == tyConcept: + result = enterConceptMatch(c, f, x, flags) else: let genericBody = f[0] var askip = skippedNone @@ -1758,7 +1781,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, let aobj = x.skipToObject(askip) let fobj = genericBody.last.skipToObject(fskip) result = typeRel(c, genericBody, x, flags) - if result != isNone: + if result != isNone and concpt.kind != tyConcept: # see tests/generics/tgeneric3.nim for an example that triggers this # piece of code: # @@ -1886,11 +1909,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, else: result = isNone of tyConcept: - if a.kind == tyConcept and sameType(f, a): - result = isGeneric - else: - result = if concepts.conceptMatch(c.c, f, a, c.bindings, nil): isGeneric - else: isNone + result = enterConceptMatch(c, f, a, flags) of tyCompositeTypeClass: considerPreviousT: let roota = a.skipGenericAlias @@ -2411,7 +2430,6 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, let oldInheritancePenalty = m.inheritancePenalty var r = typeRel(m, f, a) - # This special typing rule for macros and templates is not documented # anywhere and breaks symmetry. It's hard to get rid of though, my # custom seqs example fails to compile without this: diff --git a/compiler/types.nim b/compiler/types.nim index 16853e5ffa..2acb164d4d 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1515,6 +1515,20 @@ proc getSize*(conf: ConfigRef; typ: PType): BiggestInt = computeSizeAlign(conf, typ) result = typ.size +proc isConcept*(t: PType): bool= + case t.kind + of tyConcept: true + of tyCompositeTypeClass: + t.hasElementType and isConcept(t.elementType) + of tyGenericBody: + t.typeBodyImpl.kind == tyConcept + of tyGenericInvocation, tyGenericInst: + if t.baseClass.kind == tyGenericBody: + t.baseClass.typeBodyImpl.kind == tyConcept + else: + t.baseClass.kind == tyConcept + else: false + proc containsGenericTypeIter(t: PType, closure: RootRef): bool = case t.kind of tyStatic: @@ -1525,6 +1539,8 @@ proc containsGenericTypeIter(t: PType, closure: RootRef): bool = return false of GenericTypes + tyTypeClasses + {tyFromExpr}: return true + of tyGenericInst: + return t.isConcept else: return false @@ -2053,6 +2069,7 @@ proc genericRoot*(t: PType): PType = proc reduceToBase*(f: PType): PType = #[ + Not recursion safe Returns the lowest order (most general) type that that is compatible with the input. E.g. A[T] = ptr object ... A -> ptr object @@ -2077,7 +2094,7 @@ proc reduceToBase*(f: PType): PType = result = reduceToBase(f.typeBodyImpl) of tyUserTypeClass: if f.isResolvedUserTypeClass: - result = f.base # ?? idk if this is right + result = f.base else: result = f.skipModifier of tyStatic, tyOwned, tyVar, tyLent, tySink: diff --git a/doc/manual.md b/doc/manual.md index 40b7b9f180..8eab6683d5 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -2927,6 +2927,97 @@ a parameter has different names between them. Not supplying the parameter name in such cases results in an ambiguity error. +Concepts +========= + +Concepts are a mechanism for users to define custom type classes that match other +types based on a given set of bindings. + +```nim +type + Comparable = concept # Atomic concept + proc cmp(a, b: Self): int + Indexable[I, T] = concept # Container concept + proc `[]`(x: Self; at: I): T + proc `[]=`(x: var Self; at: I; newVal: T) + proc len(x: Self): I + Index = concept + proc inc(x: var Self) + proc `<`(a, b: Self): bool +proc sort*[I: Index; T: Comparable](x: var Indexable[I, T]) +``` + +In the above example, `Comparable` and `Indexable` are types that will match any type that +can can bind each definition declared in the concept body. The special `Self` type defined +in the concept body refers to the type being matched, also called the "implementation" of +the concept. Implementations that match the concept are generic matches, and the concept +typeclasses themselves work in a similar way to generic type variables in that they are never +concrete types themselves (even if they have concrete type parameters such as `Indexable[int, int]`) +and expressions like `typeof(x)` in the body of `proc sort` from the above example will return the +type of the implementation, not the concept typeclass. Concepts are useful for providing information +to the compiler in generic contexts, most notably for generic type checking, and as a tool for +[Overload resolution]. Generic type checking is forthcoming, so this will only explain overload +resolution for now. + +In the example above, "atomic" and "container" concepts are mentioned. These kinds of concept +are determined by the generic type variables of the concept. Atomic concepts` definitions contain +only concrete types, and the `Self` type is inferred to be concrete. Container types are the same, +under the condition that their generic variables are bound to concrete types and substituted appropriately. +The programmer is free to define a concept that breaks these concreteness rules, thus making a "gray" concept: + +```nim +type + Processor = concept + proc process[T](s: Self; data: T) +``` + +The above concept does not have generic variables, and its definition contains `T` which is not concrete. +This kind of concept may disrupt the compiler's ability to type check generic contexts, but it is useful for +overload resolution. The difference between `Indexable[I, T]` and `Processor` is that a given implementation +is effectively described as an instantiation of `Indexable` (as in `Indexable[int, int]`) whereas a `Processor` +concept describes an implementation designed to handle multiple different types of data `T`. + +Concept overload resolution +----------------------------- + +When an operand's type is being matched to a concept, the operand's type is set as the "potential +implementation". For each definition in the concept body, overload resolution is performed by substituting `Self` +for the potential implementation to try and find a match for each definition. If this succeeds, the concept +matches. Implementations do not need to exactly match the definitions in the concept. For example: + +```nim +type + C1 = concept + proc p(s: Self; x: int) + Implementation = object + +proc p(x: Implementation; y: SomeInteger) +proc spring(x: C1) +spring(Implementation()) +``` +This will bind because `p(Implementation(), 0)` will bind. Conversely, container types will bind to +less specific definitions if the generic constraints and bindings allow it, as per usual generic matching. + +Things start to get more complicated when overload resolution starts "Hierarchical Order Comparison" +I.E. specificity comparison as per [Overload resolution]. In this state the compiler may be comparing +all kinds of types and typeclasses with concepts as defined in the `proc` definitions of each overload. +This leads to confusing and impractical behavior in most situations, so the rules are simplified. They are: + +1. if a concept is being compared with `T` or any type that accepts all other types (`auto`) the concept +is more specific +2. if the concept is being compared with another concept the result is deferred to [Concept subset matching] +3. in any other case the concept is less specific then it's competitor + + +Concept subset matching +------------------------- + +This type of matching is simple. When comparing concepts `C1` and `C2`, if all valid implementations of `C1` +are also valid implementations of `C2` but not vice versa then `C1` is a subset of `C2`. This means that +`C1` will match to `C2` and therefore the disambiguation process will prefer `C2` as it is more specific. +If neither of them are subsets of one another, then the disambiguation proceeds to complexity analysis +and the concept with the most definitions wins, if any. No definite winner is an ambiguity error at +compile time. Statements and expressions ========================== diff --git a/tests/concepts/conceptv2negative/tmarrget.nim b/tests/concepts/conceptv2negative/tmarrget.nim new file mode 100644 index 0000000000..64d6b8c91a --- /dev/null +++ b/tests/concepts/conceptv2negative/tmarrget.nim @@ -0,0 +1,12 @@ +discard """ +action: "reject" +""" + +# stop mArrGet magic from giving everything `[]` +type + C[T] = concept + proc `[]`(b: Self, i: int): T + A = object + +proc p(a: C): int = assert false +discard p(A()) diff --git a/tests/concepts/conceptv2negative/tmissingbind.nim b/tests/concepts/conceptv2negative/tmissingbind.nim new file mode 100644 index 0000000000..78376aeaeb --- /dev/null +++ b/tests/concepts/conceptv2negative/tmissingbind.nim @@ -0,0 +1,26 @@ +discard """ +action: "reject" +""" + +#[ + ArrayImpl is not Sizeable +]# + +type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc something(s: Self) + proc w(b: Buffer, s: Self) + BufferImpl = object + ArrayImpl = object + +proc something(s: ArrayImpl)= discard +#proc size(s: ArrayImpl): int= discard +proc w(x: BufferImpl, d: Sizeable)= discard + +proc spring(s: Buffer, data: Serializable)= discard + +spring(BufferImpl(), ArrayImpl()) diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 0db79c0269..5befd2fafa 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -5,7 +5,15 @@ B[system.int] A[system.string] A[array[0..0, int]] A[seq[int]] -char +100 +a +b +c +a +b +c +1 +2 ''' """ import conceptsv2_helper @@ -104,7 +112,7 @@ block: # simple recursion WritableImpl = object proc launch(a: var Buffer, b: Writable)= discard - proc put(x: var BufferImpl, i: object)= discard + proc put[T](x: var BufferImpl, i: T)= discard proc second(x: BufferImpl)= discard proc put(x: var Buffer, y: WritableImpl)= discard @@ -122,7 +130,7 @@ block: # more complex recursion WritableImpl = object proc launch(a: var Buffer, b: Writable)= discard - proc put(x: var Buffer, i: object)= discard + proc put[T](x: var Buffer, i: T)= discard proc put(x: var BufferImpl, i: object)= discard proc second(x: BufferImpl)= discard proc put(x: var Buffer, y: WritableImpl)= discard @@ -130,37 +138,320 @@ block: # more complex recursion var a = BufferImpl[5]() launch(a, WritableImpl()) -block: # capture p1[T] +block: # co-dependent concepts type - A[T] = object - C = concept - proc p1(x: Self, i: int): float + Writable = concept + proc w(b: var Buffer; s: Self): int + Buffer = concept + proc w(s: var Self; data: Writable): int + SizedWritable = concept + proc size(x: Self): int + proc w(b: var Buffer, x: Self): int + BufferImpl = object + + proc w(x: var BufferImpl, d: int): int = return 100 + proc size(d: int): int = sizeof(int) - proc p1[T](a: A[T], idx: int): T = default(T) - proc p(a: C): int = discard - proc p[T](a: T):int = assert false + proc p(b: var Buffer, data: SizedWritable): int = + b.w(data) - discard p(A[float]()) + var b = BufferImpl() + echo p(b, 5) -block: # mArrGet binding +block: # indirect concept matching + type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc something(s: Self) + proc w(b: Buffer, s: Self) + BufferImpl = object + ArrayImpl = object + + proc something(s: ArrayImpl)= discard + proc size(s: ArrayImpl): int= discard + + proc w(x: BufferImpl, d: Sizeable)= discard + + proc spring(s: Buffer, data: Serializable)=discard + + spring(BufferImpl(), ArrayImpl()) + +block: # instantiate even when generic params are the same type ArrayLike[T] = concept proc len(x: Self): int proc `[]`(b: Self, i: int): T + proc p[T](x: ArrayLike[T])= + for k in x: + echo k + # For this test to work the second call's instantiation has to be incompatible with the first on the back end + p(['a','b','c']) + p("abc") - proc p[T](a: ArrayLike[T]): int= discard - discard p([1,2]) +block: # reject improper generic variables in candidates + type + ArrayLike[T] = concept + proc len(x: Self): int + proc g(b: Self, i: int): T + FreakString = concept + proc len(x: Self): int + proc characterSize(s: Self): int + A = object + + proc g[T, H](s: T, i: H): H = default(T) + proc len(s: A): int = discard + proc characterSize(s: A): int = discard + + proc p(symbol: ArrayLike[char]): int = assert false + proc p(symbol: FreakString): int=discard + + discard p(A()) + +block: # typerel disambiguation by concept subset + type + ArrayLike[T] = concept + proc len(x: Self): int + proc characterSize(s: Self): int + FreakString = concept + proc len(x: Self): int + proc characterSize(s: Self): int + proc tieBreaker(s: Self, j: int): float + A = object + + proc len(s: A): int = discard + proc characterSize(s: A): int = discard + proc tieBreaker(s: A, h: int):float = 0.0 + + proc p(symbol: ArrayLike[char]): int = assert false + proc p(symbol: FreakString): int=discard + + discard p(A()) + +block: # tie break via sumGeneric + type + C1 = concept + proc p1(x: Self, b: int) + proc p2(x: Self, b: float) + proc p3(x: Self, b: string) + C2 = concept + proc b1(x: Self, b: int) + proc b2(x: Self, b: float) + A = object + + proc p1(x: A, b: int)=discard + proc p2(x: A, b: float)=discard + proc p3(x: A, b: string)=discard + + proc b1(x: A, b: int)=discard + proc b2(x: A, b: float)=discard + + proc p(symbol: C1): int = discard + proc p(symbol: C2): int = assert false + + discard p(A()) + +block: # not type + type + C1 = concept + proc p(s: Self, a: int) + C1Impl = object + + proc p(x: C1Impl, a: not float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # not type parameterized + type + C1[T: not int] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # typedesc + type + C1 = concept + proc p(s: Self, a: typedesc[SomeInteger]) + C1Impl = object + + proc p(x: C1Impl, a: typedesc)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + + +block: # or + type + C1 = concept + proc p(s: Self, a: int | float) + C1Impl = object + + proc p(x: C1Impl, a: int | float | string)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # or mixed generic param + type + C1 = concept + proc p(s: Self, a: int | float) + C1Impl = object + + proc p[T: string | float](x: C1Impl, a: int | T) = discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # or parameterized + type + C1[T: int | float | string] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: int | float)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # unconstrained param + type + A = object + C1[T] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: A)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # unconstrained param sanity check + type + A = object + C1[T: auto] = concept + proc p(s: Self, a: T) + C1Impl = object + + proc p(x: C1Impl, a: A)= discard + proc spring(x: C1)= discard + + spring(C1Impl()) + +block: # exact nested concept binding + type + Sizeable = concept + proc size(s: Self): int + Buffer = concept + proc w(s: Self, data: Sizeable) + Serializable = concept + proc w(b: Buffer, s: Self) + ArrayLike = concept + proc len(s: Self): int + ArrayImpl = object + + proc len(s: ArrayImpl): int = discard + proc w(x: Buffer, d: ArrayLike)=discard + + proc spring(data: Serializable)=discard + spring(ArrayImpl()) block: type - A[T] = object - ArrayLike[T] = concept - proc len(x: Self): int - proc `[]`(b: Self, i: int): T - proc tell(s: Self, x: A[int]) - - proc tell(x: string, h: A[int])= discard + StaticallySized = concept + proc staticSize(x: typedesc[Self]): int + proc dynamicSize(x: Self): int + DynamicallySized = concept + proc dynamicSize(x: Self): int - proc spring[T](w: ArrayLike[T])= echo T - - spring("hi") + proc dynamicSize(a: SomeInteger): int = 5 + proc read[T: DynamicallySized](a: var T): int = 1 + proc read[T: SomeInteger](a: var T): int = 2 + + var a: uint16 + assert read(a) == 2 + +block: + type + A[X, Y] = object + x: X + y: Y + C1 = concept + proc p(z: var Self) + C2 = concept + proc g(x: var Self, y: int) + C3 = C1 and C2 + C4 = concept + proc h(x: Self): C3 + + proc p[X, Y](z: var A[int, float]) = discard + proc g[X, Y](z: var A[X, Y], y: int) = discard + proc h[X, Y](z: var A[X, Y]): A[X, Y] = discard + + proc spring(x: C4) = discard + var d = A[int, float]() + d.spring() + +block: + type + A[X, Y] = object + x: X + y: Y + B = object + C1 = concept + proc p(z: var Self, d: A[int, float]) + + proc p[X: int; Y: float](x: var B, y: A[X, Y]) = discard + proc spring(x: var C1) = discard + var d = B() + d.spring() + +block: + type + A = object + C1 = concept + proc p(s: Self; x: auto) + C2[T: int] = concept + proc p(s: Self; x: T) + Impl = object + + proc p(n: Impl; i: int) = discard + + proc spring(x: C1): int = 1 + proc spring(x: C2): int = 2 + + assert spring(Impl()) == 2 + +# this code fails inside a block for some reason +type Indexable[T] = concept + proc `[]`(t: Self, i: int): T + proc len(t: Self): int + +iterator items[T](t: Indexable[T]): T = + for i in 0 ..< t.len: + yield t[i] + +type Enumerable[T] = concept + iterator items(t: Self): T + +proc echoAll[T](t: Enumerable[T]) = + for item in t: + echo item + +type DummyIndexable[T] = distinct seq[T] + +proc `[]`[T](t: DummyIndexable[T], i: int): T = + seq[T](t)[i] + +proc len[T](t: DummyIndexable[T]): int = + seq[T](t).len + + +let dummyIndexable = DummyIndexable(@[1, 2]) +echoAll(dummyIndexable) From b8302cdd97ddbb80240dab947fc794a81f790f65 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 10 Mar 2025 18:20:44 +0800 Subject: [PATCH 017/119] implements internal sink copy (#24747) TODO: - [x] other value types (arrays, strings, seqs, objects) - [x] replaces https://github.com/nim-lang/Nim/pull/24731 - [x] improve code shape - [ ] revert https://github.com/nim-lang/Nim/issues/24175 - [x] if possible, revert https://github.com/nim-lang/Nim/pull/23685 - [ ] if possible, revert https://github.com/nim-lang/Nim/pull/22229 and https://github.com/nim-lang/Nim/pull/23764 - [ ] if possible, remove `if n.containsConstSeq:` - [ ] if possible, always pass value (arrays, strings, seqs, tuples, or even objects without custom hooks (?)) sinks by ref because this PR should ensure these value types are not modified without a copy - [x] fixes `say a, (b = move a; a)` for potential writes https://github.com/nim-lang/Nim/pull/24753 --- compiler/ccgcalls.nim | 29 --------------- compiler/ccgutils.nim | 9 +---- compiler/injectdestructors.nim | 64 ++++++++++++++++++++++++++++++++-- compiler/trees.nim | 29 +++++++++++++++ 4 files changed, 91 insertions(+), 40 deletions(-) diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index aaad53b64b..2017f7dffc 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -418,35 +418,6 @@ proc skipTrivialIndirections(n: PNode): PNode = result = result[1] else: break -proc getPotentialWrites(n: PNode; mutate: bool; result: var seq[PNode]) = - case n.kind: - of nkLiterals, nkIdent, nkFormalParams: discard - of nkSym: - if mutate: result.add n - of nkAsgn, nkFastAsgn, nkSinkAsgn: - getPotentialWrites(n[0], true, result) - getPotentialWrites(n[1], mutate, result) - of nkAddr, nkHiddenAddr: - getPotentialWrites(n[0], true, result) - of nkBracketExpr, nkDotExpr, nkCheckedFieldExpr: - getPotentialWrites(n[0], mutate, result) - of nkCallKinds: - case n.getMagic: - of mIncl, mExcl, mInc, mDec, mAppendStrCh, mAppendStrStr, mAppendSeqElem, - mAddr, mNew, mNewFinalize, mWasMoved, mDestroy: - getPotentialWrites(n[1], true, result) - for i in 2.. conf.target.floatSize * 3): result = true # requested anyway elif (tfFinal in pt.flags) and (pt[0] == nil): @@ -101,11 +98,7 @@ 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: - 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) + 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/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 013ab1c506..be2df8c1f2 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -17,7 +17,7 @@ import ast, astalgo, msgs, renderer, magicsys, types, idents, options, lowerings, modulegraphs, lineinfos, parampatterns, sighashes, liftdestructors, optimizer, - varpartitions, aliasanalysis, dfa, wordrecg + varpartitions, aliasanalysis, dfa, wordrecg, trees import std/[strtabs, tables, strutils, intsets] @@ -1244,6 +1244,55 @@ when false: for i in 0.. 0: + result.add replaceSinkParam(n, mapping) + else: + result = n + proc injectDestructorCalls*(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n: PNode): PNode = when toDebug.len > 0: shouldDebug = toDebug == owner.name.s or toDebug == "always" @@ -1257,15 +1306,24 @@ proc injectDestructorCalls*(g: ModuleGraph; idgen: IdGenerator; owner: PSym; n: var scope = Scope(body: n) let body = p(n, c, scope, normal) + var sinkParams = newSeq[PSym]() + if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}: let params = owner.typ.n for i in 1.. 0: + result = addSinkCopy(c, scope, sinkParams, result) + dbg: echo ">---------transformed-to--------->" echo renderTree(result, {renderIds}) diff --git a/compiler/trees.nim b/compiler/trees.nim index da58878f82..6d85f920c8 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -243,3 +243,32 @@ proc isRunnableExamples*(n: PNode): bool = proc skipAddr*(n: PNode): PNode {.inline.} = result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] else: n + +proc getPotentialWrites*(n: PNode; mutate: bool; result: var seq[PNode]) = + case n.kind: + of nkLiterals, nkIdent, nkFormalParams: discard + of nkSym: + if mutate: result.add n + of nkAsgn, nkFastAsgn, nkSinkAsgn: + getPotentialWrites(n[0], true, result) + getPotentialWrites(n[1], mutate, result) + of nkAddr, nkHiddenAddr: + getPotentialWrites(n[0], true, result) + of nkBracketExpr, nkDotExpr, nkCheckedFieldExpr: + getPotentialWrites(n[0], mutate, result) + of nkCallKinds: + case n.getMagic: + of mIncl, mExcl, mInc, mDec, mAppendStrCh, mAppendStrStr, mAppendSeqElem, + mAddr, mNew, mNewFinalize, mWasMoved, mDestroy: + getPotentialWrites(n[1], true, result) + for i in 2.. Date: Mon, 10 Mar 2025 22:47:03 +0800 Subject: [PATCH 018/119] Fix scanTuple undeclared identifier 'scanf' (#24759) Without this fix, trying to use `scanTuple` in a generic proc imported from a different module fails to compile (`undeclared identifier: 'scanf'`): ```nim # module.nim import std/strscans proc scan*[T](s: string): (bool, string) = s.scanTuple("$+") ``` ```nim # main.nim import ./module echo scan[int]("foo") ``` Workaround is to `export scanf` in `module.nim` or `import std/strscans` in `main.nim`. --- lib/pure/strscans.nim | 2 +- tests/stdlib/mstrscans_undecl_scanf.nim | 4 ++++ tests/stdlib/tstrscans_undecl_scanf.nim | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/stdlib/mstrscans_undecl_scanf.nim create mode 100644 tests/stdlib/tstrscans_undecl_scanf.nim diff --git a/lib/pure/strscans.nim b/lib/pure/strscans.nim index 45ba9a469c..cbb1124420 100644 --- a/lib/pure/strscans.nim +++ b/lib/pure/strscans.nim @@ -512,7 +512,7 @@ macro scanTuple*(input: untyped; pattern: static[string]; matcherTypes: varargs[ inc userMatches else: discard inc p - result.add nnkTupleConstr.newTree(newCall(ident("scanf"), input, newStrLitNode(pattern))) + result.add nnkTupleConstr.newTree(newCall(bindSym("scanf"), input, newStrLitNode(pattern))) for arg in arguments: result[^1][0].add arg result[^1].add arg diff --git a/tests/stdlib/mstrscans_undecl_scanf.nim b/tests/stdlib/mstrscans_undecl_scanf.nim new file mode 100644 index 0000000000..24ce23ee6d --- /dev/null +++ b/tests/stdlib/mstrscans_undecl_scanf.nim @@ -0,0 +1,4 @@ +import std/strscans + +proc scan*[T](s: string): (bool, string) = + s.scanTuple("$+") diff --git a/tests/stdlib/tstrscans_undecl_scanf.nim b/tests/stdlib/tstrscans_undecl_scanf.nim new file mode 100644 index 0000000000..a2b6e8386f --- /dev/null +++ b/tests/stdlib/tstrscans_undecl_scanf.nim @@ -0,0 +1,3 @@ +import std/assertions +import ./mstrscans_undecl_scanf +doAssert scan[int]("foo") == (true, "foo") From ccb40024c65ea2d2856818780a5e721cf1db85a7 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:56:48 +0800 Subject: [PATCH 019/119] remove special treatments of `sink`ing const sequences (#24763) --- compiler/injectdestructors.nim | 38 ++-------------------------------- 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index be2df8c1f2..b0fa733982 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -493,25 +493,6 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode = # no need to destroy it. result.add tmp -proc isDangerousSeq(t: PType): bool {.inline.} = - let t = t.skipTypes(abstractInst) - result = t.kind == tySequence and tfHasOwned notin t.elementType.flags - -proc containsConstSeq(n: PNode): bool = - if n.kind == nkBracket and n.len > 0 and n.typ != nil and isDangerousSeq(n.typ): - return true - result = false - case n.kind - of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast: - result = containsConstSeq(n[1]) - of nkObjConstr, nkClosure: - for i in 1.. 0 and isDangerousSeq(ri.typ): - inc c.inEnsureMove, isEnsureMove - result = c.genCopy(dest, ri, flags) - dec c.inEnsureMove, isEnsureMove - result.add p(ri, c, s, consumed) - c.finishCopy(result, dest, flags, isFromSink = false) - else: - result = c.genSink(s, dest, p(ri, c, s, consumed), flags) - of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: + of nkBracket, nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: result = c.genSink(s, dest, p(ri, c, s, consumed), flags) of nkSym: if isSinkParam(ri.sym) and isLastRead(ri, c, s): From e2d479122990f02aa03b7af3243926a0cb7d15ed Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:57:48 +0800 Subject: [PATCH 020/119] fixes move for `getPotentialWrites` (#24753) `move` would modify parameters as well --- compiler/ccgexprs.nim | 10 ++-------- compiler/cgen.nim | 1 - compiler/trees.nim | 2 +- tests/ccgbugs/targ_lefttoright.nim | 9 +++++++++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index fdd8553a3e..07d2ca453f 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -357,7 +357,7 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = of tyString: if optSeqDestructors in p.config.globalOptions: genGenericAsgn(p, dest, src, flags) - elif ({needToCopy, needToCopySinkParam} * flags == {} and src.storage != OnStatic) or canMove(p, src.lode, dest): + elif (needToCopy notin flags and src.storage != OnStatic) or canMove(p, src.lode, dest): genRefAssign(p, dest, src) else: if (dest.storage == OnStack and p.config.selectedGC != gcGo) or not usesWriteBarrier(p.config): @@ -2757,13 +2757,7 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) = let val = if p.module.compileToCpp: rdLoc(a) else: byRefLoc(p, a) p.s(cpsStmts).addCallStmt(rdLoc(b), val) else: - if n[1].kind == nkSym and isSinkParam(n[1].sym): - var tmp = getTemp(p, n[1].typ.skipTypes({tySink})) - genAssignment(p, tmp, a, {needToCopySinkParam}) - genAssignment(p, d, tmp, {}) - resetLoc(p, tmp) - else: - genAssignment(p, d, a, {}) + genAssignment(p, d, a, {}) resetLoc(p, a) proc genDestroy(p: BProc; n: PNode) = diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 7b71239e96..6f16c4f17d 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -430,7 +430,6 @@ proc rdCharLoc(a: TLoc): Rope = type TAssignmentFlag = enum needToCopy - needToCopySinkParam needTempForOpenArray needAssignCall TAssignmentFlags = set[TAssignmentFlag] diff --git a/compiler/trees.nim b/compiler/trees.nim index 6d85f920c8..6d9c0272d6 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -263,7 +263,7 @@ proc getPotentialWrites*(n: PNode; mutate: bool; result: var seq[PNode]) = getPotentialWrites(n[1], true, result) for i in 2.. Date: Tue, 11 Mar 2025 16:58:22 +0800 Subject: [PATCH 021/119] Add linking options for tinycc backend (#24750) ### Issue When using `tcc` as backend to compile a trivial program ``` nim c --cc:tcc --skipCfg a.nim ``` , errors reported: ``` tcc: error: undefined symbol 'fabs' ``` ### Solution `fabs` belongs to libm. With these two options added, one can compile with an additional clib option: ``` nim c --cc:tcc --skipCfg --clib:m a.nim ``` --- compiler/extccomp.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 82cf5afb91..6226cea960 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -257,8 +257,8 @@ compiler tcc: linkerExe: "tcc", linkTmpl: "-o $exefile $options $buildgui $builddll $objfiles", includeCmd: " -I", - linkDirCmd: "", # XXX: not supported yet - linkLibCmd: "", # XXX: not supported yet + linkDirCmd: " -L", + linkLibCmd: " -l$1", debug: " -g ", pic: "", asmStmtFrmt: "asm($1);$n", From e2e77907795237961aa866717bc6fc3a226d6106 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 11 Mar 2025 11:59:21 +0300 Subject: [PATCH 022/119] fix canRaise for non-proc calls (#24752) fixes #24751 `typeof` leaves the object constructor as a call node for some reason, in this case it tries to access the first child of the type node but the object has no fields so the type field is empty. Alternatively the optimizer can stop looking into `typeof` --- compiler/ast.nim | 8 +++++--- tests/types/ttypeofobjconstr.nim | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 tests/types/ttypeofobjconstr.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index a187687f4e..3ed9a7c675 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -2089,14 +2089,16 @@ proc canRaise*(fn: PNode): bool = result = false elif fn.kind == nkSym and fn.sym.magic == mEcho: result = true - else: + elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil: # TODO check for n having sons? or just return false for now if not - if fn.typ != nil and fn.typ.n != nil and fn.typ.n[0].kind == nkSym: + if fn.typ.n[0].kind == nkSym: result = false else: - result = fn.typ != nil and fn.typ.n != nil and ((fn.typ.n[0].len < effectListLen) or + result = ((fn.typ.n[0].len < effectListLen) or (fn.typ.n[0][exceptionEffects] != nil and fn.typ.n[0][exceptionEffects].safeLen > 0)) + else: + result = false proc toHumanStrImpl[T](kind: T, num: static int): string = result = $kind diff --git a/tests/types/ttypeofobjconstr.nim b/tests/types/ttypeofobjconstr.nim new file mode 100644 index 0000000000..2825f40d85 --- /dev/null +++ b/tests/types/ttypeofobjconstr.nim @@ -0,0 +1,5 @@ +# issue #24751 + +type A = object + +var a: typeof(A()) From a7711d452d655c4e4c109ecea87e307ce3bc63aa Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:59:55 +0800 Subject: [PATCH 023/119] fixes #24754; {.gcsafe.} block breaks move analysis (#24757) fixes #24754 --- compiler/injectdestructors.nim | 2 +- tests/arc/tarcmisc.nim | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index b0fa733982..ce9164058a 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -1164,7 +1164,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, flags: set[MoveOrCopy c.finishCopy(result, dest, flags, isFromSink = false) of nkHiddenSubConv, nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv, nkCast: result = c.genSink(s, dest, p(ri, c, s, sinkArg), flags) - of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt: + of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt, nkPragmaBlock: template process(child, s): untyped = moveOrCopy(dest, child, c, s, flags) # We know the result will be a stmt so we use that fact to optimize handleNestedTempl(ri, process, willProduceStmt = true) diff --git a/tests/arc/tarcmisc.nim b/tests/arc/tarcmisc.nim index 12d67a999b..f8a50c0d21 100644 --- a/tests/arc/tarcmisc.nim +++ b/tests/arc/tarcmisc.nim @@ -898,3 +898,17 @@ proc bar = mutate(("1.2", 0, 0)) bar() + +block: # bug #24754 + type NoCopy = object + id: int + + proc `=copy`(a: var NoCopy, b: NoCopy) {.error.} + + + proc foo(): NoCopy = + {.gcsafe.}: + let s = 12 + NoCopy(id: s) + + doAssert foo().id == 12 From 38ad336c6924ac3fea989b86f8ce099af5be96da Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 11 Mar 2025 12:00:37 +0300 Subject: [PATCH 024/119] fix tuple nodes from VM inserting hidden conv to keep old type (#24756) fixes #24755, refs #24710 Instead of using the node from `indexTypesMatch` which inserts a hidden conv node, just change the type of the node back to the old type directly --- compiler/semexprs.nim | 6 +++--- tests/tuples/tstatictuple.nim | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 tests/tuples/tstatictuple.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 2aa646dd60..191755b52e 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2951,7 +2951,7 @@ proc semTupleFieldsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType 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 + result.typ() = oldType proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = result = n # we don't modify n, but compute the type: @@ -2982,7 +2982,7 @@ proc semTuplePositionsConstr(c: PContext, n: PNode, flags: TExprFlags; expectedT 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 + result.typ() = oldType include semobjconstr @@ -3073,7 +3073,7 @@ proc semExport(c: PContext, n: PNode): PNode = proc semTupleConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType = nil): PNode = result = semTuplePositionsConstr(c, n, flags, expectedType) var tupexp = result - while tupexp.kind == nkHiddenSubConv: tupexp = tupexp[1] + 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 diff --git a/tests/tuples/tstatictuple.nim b/tests/tuples/tstatictuple.nim new file mode 100644 index 0000000000..e5de2b4539 --- /dev/null +++ b/tests/tuples/tstatictuple.nim @@ -0,0 +1,33 @@ +# issue #24755 + +type + Field = object + FieldS = object + FieldOps = enum + foNeg, foAdd, foSub, foMul, foDiv, foAdj, foToSingle, foToDouble + FieldUnop[Op: static FieldOps, T1] = object + f1: T1 + FieldAddSub[S: static tuple, T: tuple] = object + field: T + SomeField = Field | FieldS | FieldUnop | FieldAddSub + SomeField2 = Field | FieldS | FieldUnop | FieldAddSub + +template fieldUnop[X:SomeField](o: static FieldOps, x: X): auto = + FieldUnop[o,X](f1: x) +template fieldAddSub[X,Y](sx: static int, x: X, sy: static int, y: Y): auto = + FieldAddSub[(a:sx,b:sy),tuple[a:X,b:Y]](field:(a:x,b:y)) + +template `:=`*(r: var FieldS, x: SomeField) = + discard +template toSingle(x: SomeField): auto = + fieldUnop(foToSingle, x) +template toDouble(x: SomeField): auto = + fieldUnop(foToDouble, x) +template `+`(x: SomeField, y: SomeField2): auto = + fieldAddSub(1, x, 1, y) + +var + fd: Field + fs,fs2: FieldS + +fs2 := toSingle(fs.toDouble + fd) From 850f32771339efc8ae0a1f070f231e0167ac946c Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Tue, 11 Mar 2025 05:01:32 -0400 Subject: [PATCH 025/119] folding const expressions with branching logic (#24689) motivating example: ```nim iterator p(a: openArray[char]): int = if a.len != 0: if a[0] != '/': discard for t in p(""): discard ``` The compiler wants to evaluate `a[0]` at compile time even though it is protected by the if statement above it. Similarly expressions like `a.len != 0 and a[0] == '/'` have problems. It seems like the logic in semfold needs to be more aware of branches to positively identify when it is okay to fail compilation in these scenarios. It's a bit tough though because it may be the case that non-constant expressions in branching logic can properly protect some constant expressions. --- compiler/semfold.nim | 7 ++++--- tests/controlflow/tcontrolflow.nim | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 00b40f5727..451d675188 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -471,19 +471,20 @@ proc foldArrayAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNo if result.kind == nkExprColonExpr: result = result[1] else: result = nil - localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) + #localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) of nkBracket: idx -= toInt64(firstOrd(g.config, x.typ)) if idx >= 0 and idx < x.len: result = x[int(idx)] else: result = nil - localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) + #localError(g.config, n.info, formatErrorIndexBound(idx, x.len-1) & $n) of nkStrLit..nkTripleStrLit: result = newNodeIT(nkCharLit, x.info, n.typ) if idx >= 0 and idx < x.strVal.len: result.intVal = ord(x.strVal[int(idx)]) else: - localError(g.config, n.info, formatErrorIndexBound(idx, x.strVal.len-1) & $n) + result = nil + #localError(g.config, n.info, formatErrorIndexBound(idx, x.strVal.len-1) & $n) else: result = nil proc foldFieldAccess(m: PSym, n: PNode; idgen: IdGenerator; g: ModuleGraph): PNode = diff --git a/tests/controlflow/tcontrolflow.nim b/tests/controlflow/tcontrolflow.nim index dd21a2bb67..c2f34ce0e2 100644 --- a/tests/controlflow/tcontrolflow.nim +++ b/tests/controlflow/tcontrolflow.nim @@ -114,3 +114,10 @@ block named: # works if true: break named doAssert false, "not reached" + +block: + iterator p(a: openArray[char]): int = + if a.len != 0: + if a[0] != '/': + discard + for t in p(""): discard From 82891e6850c4b10c123f7fa5fcaaade1baaded44 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 11 Mar 2025 16:24:45 +0300 Subject: [PATCH 026/119] give hint for forward declarations with unknown raises effects (#24767) refs #24766 Detect when we track a call to a forward declaration without explicit `raises` effects, then when the `raises` check fails for the proc, give a hint that this forward declaration was tracked as potentially raising any exception. --- compiler/lineinfos.nim | 2 ++ compiler/sempass2.nim | 9 +++++++-- tests/errmsgs/tforwardraises.nim | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 tests/errmsgs/tforwardraises.nim diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index 248e843267..292a02e60e 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -110,6 +110,7 @@ type hintSource = "Source", hintPerformance = "Performance", hintStackTrace = "StackTrace", hintGCStats = "GCStats", hintGlobalVar = "GlobalVar", hintExpandMacro = "ExpandMacro", hintUser = "User", hintUserRaw = "UserRaw", hintExtendedContext = "ExtendedContext", + hintUnknownRaises = "UnknownRaises", hintMsgOrigin = "MsgOrigin", # since 1.3.5 hintDeclaredLoc = "DeclaredLoc", # since 1.5.1 @@ -236,6 +237,7 @@ const hintUser: "$1", hintUserRaw: "$1", hintExtendedContext: "$1", + hintUnknownRaises: "$1 is a forward declaration without explicit .raises, assuming it can raise anything", hintMsgOrigin: "$1", hintDeclaredLoc: "$1" ] diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 6730861d81..0aa96bd6ea 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -84,6 +84,7 @@ type gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool isInnerProc: bool inEnforcedNoSideEffects: bool + unknownRaises: seq[(PSym, TLineInfo)] currOptions: TOptions optionsStack: seq[(TOptions, TNoteKinds)] config: ConfigRef @@ -639,6 +640,8 @@ proc importedFromC(n: PNode): bool = proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) = let pragma = s.ast[pragmasPos] let spec = effectSpec(pragma, wRaises) + if spec.isNil and sfForward in s.flags: + tracked.unknownRaises.add (s, n.info) mergeRaises(tracked, spec, n) let tagSpec = effectSpec(pragma, wTags) @@ -1506,7 +1509,7 @@ proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool = proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: string, hints: bool; effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.}; - hintsArg: PNode = nil; isForbids: bool = false) = + hintsArg: PNode = nil; isForbids: bool = false; unknownRaises: seq[(PSym, TLineInfo)] = @[]) = # check that any real exception is listed in 'spec'; mark those as used; # report any unused exception var used = initIntSet() @@ -1523,6 +1526,8 @@ proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: pushInfoContext(g.config, spec.info) var rr = if r.kind == nkRaiseStmt: r[0] else: r while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon + for (s, info) in unknownRaises.items: + message(g.config, info, hintUnknownRaises, s.name.s) message(g.config, r.info, if emitWarnings: warnEffect else: errGenerated, renderTree(rr) & " " & msg & typeToString(r.typ)) popInfoContext(g.config) @@ -1680,7 +1685,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) = if not isNil(raisesSpec): let useWarning = s.name.s == "=destroy" checkRaisesSpec(g, useWarning, raisesSpec, t.exc, "can raise an unlisted exception: ", - hints=on, subtypeRelation, hintsArg=s.ast[0]) + hints=on, subtypeRelation, hintsArg=s.ast[0], unknownRaises = t.unknownRaises) # after the check, use the formal spec: effects[exceptionEffects] = raisesSpec else: diff --git a/tests/errmsgs/tforwardraises.nim b/tests/errmsgs/tforwardraises.nim new file mode 100644 index 0000000000..9628f69730 --- /dev/null +++ b/tests/errmsgs/tforwardraises.nim @@ -0,0 +1,17 @@ +discard """ + action: reject + nimout: ''' +tforwardraises.nim(15, 14) Hint: n is a forward declaration without explicit .raises, assuming it can raise anything [UnknownRaises] +tforwardraises.nim(14, 26) template/generic instantiation from here +tforwardraises.nim(15, 14) Error: n(0) can raise an unlisted exception: Exception +''' +""" + +# issue #24766 + +proc n(_: int) + +proc s(_: int) {.raises: [CatchableError].} = + if false: n(0) + +proc n(_: int) = s(0) From dfa482e292f802edbd172f381293522f8e483d06 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 13 Mar 2025 00:29:30 +0800 Subject: [PATCH 027/119] fixes #24770; Thread local not registed as GC root when =destroy exists (#24776) fixes #24770 e.g. `seq[(ObjectWithDestructors, string)]`/ For refc, a seq with elements that have destructors will have `hasAsgn` flags. The flag is the criteria whether a seq is thought as `containsGarbageCollectedRef`. i.e. whether to `registerTraverseProc` for the type. The culprit seems to be that `searchTypeForAux` doesn't consider the element type of sequence, even it contains a string that should belong to `GarbageCollectedRef`. With this PR: It now generates ``` nimRegisterThreadLocalMarker(TM__mSF73dT1lSI7DG58StKHLQ_5); ``` in refc --- compiler/ccgstmts.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index ed840a97a8..3aedca9a96 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -18,7 +18,7 @@ proc registerTraverseProc(p: BProc, v: PSym) = var traverseProc = "" if p.config.selectedGC in {gcMarkAndSweep, gcHooks, gcRefc} and optOwnedRefs notin p.config.globalOptions and - containsGarbageCollectedRef(v.loc.t): + containsManagedMemory(v.loc.t): # we register a specialized marked proc here; this has the advantage # that it works out of the box for thread local storage then :-) traverseProc = genTraverseProcForGlobal(p.module, v, v.info) From 4f326246413a8894703b9af9f9bfd26ddcd0883d Mon Sep 17 00:00:00 2001 From: lit Date: Thu, 13 Mar 2025 00:30:08 +0800 Subject: [PATCH 028/119] fixes #24772: system.NaN was negative when C (#24774) fixes #24772 The old implementation was said to copied from Windows SDK, but you can find the newer SDK's definition is updated and the sign is reversed compared to the old. Also, `__builtin_nanf("")` is used if available, which is more efficient than previous (In x86_64 gcc, latter produces 32B code but former just 8B). --- lib/nimbase.h | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/nimbase.h b/lib/nimbase.h index 4b338548af..3b4438331b 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -485,13 +485,22 @@ typedef char* NCSTRING; #define paramCount() cmdCount -// NAN definition copied from math.h included in the Windows SDK version 10.0.14393.0 -#ifndef NAN +#ifndef NAN /* use __builtin_nanf which is faster, if available */ +# if defined(__GNUC__) +# define NAN (__builtin_nanf("")) +# elif defined(__clang__) /* XXX: writing __has_builtin this line cause MSVC complains. */ +# if __has_builtin (__builtin_nanf) +# define NAN (__builtin_nanf("")) +# endif +# endif +#endif + +#ifndef NAN /* modified from math.h included in the Windows SDK version 10.0.26100.0 */ # ifndef _HUGE_ENUF -# define _HUGE_ENUF 1e+300 // _HUGE_ENUF*_HUGE_ENUF must overflow +# define _HUGE_ENUF 1e+300 /* _HUGE_ENUF*_HUGE_ENUF must overflow */ # endif # define NAN_INFINITY ((float)(_HUGE_ENUF * _HUGE_ENUF)) -# define NAN ((float)(NAN_INFINITY * 0.0F)) +# define NAN (-(float)(NAN_INFINITY * 0.0F)) #endif #ifndef INF From 9ebfa7973a09439ba76ea477dad0ddf3bf433e4a Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 13 Mar 2025 00:31:19 +0800 Subject: [PATCH 029/119] fixes generic types `sink T` cannot be inferred for passed arguments (#24761) Otherwise, `sink T` is kept as it is. This PR treats sink types as its base types for the arguments. So the concept would match both cases Required by https://github.com/nim-lang/Nim/pull/24724 --- compiler/sigmatch.nim | 5 +++-- tests/concepts/tconcept_old.nim | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 tests/concepts/tconcept_old.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 47d155e192..da5452b2bf 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1234,9 +1234,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, else: var candidate = f - case f.kind + let fType = f.skipTypes({tySink}) + case fType.kind of tyGenericParam: - var prev = lookup(c.bindings, f) + var prev = lookup(c.bindings, fType) if prev != nil: candidate = prev of tyFromExpr: let computedType = tryResolvingStaticExpr(c, f.n).typ diff --git a/tests/concepts/tconcept_old.nim b/tests/concepts/tconcept_old.nim new file mode 100644 index 0000000000..9459d66410 --- /dev/null +++ b/tests/concepts/tconcept_old.nim @@ -0,0 +1,18 @@ +type + Map[K, V] = concept m, var mvar + m[K] is V + m[K] = V + + Table[K, V] = object + +proc `[]=`[K, V](m: Table[K, V], x: sink K, y: sink V) = + let s = x + +proc `[]`[K, V](m: Table[K, V], x: sink K): V = + let s = x + +proc bat[K, V](x: Map[K, V]): V = + let m = x + +var s = Table[int, string]() +discard bat(s) From fb93295344b78d2d45c81bc78bdba8526a893a09 Mon Sep 17 00:00:00 2001 From: metagn Date: Wed, 12 Mar 2025 19:31:33 +0300 Subject: [PATCH 030/119] fix compound inheritance penalty (#24775) fixes #24773 `c.inheritancePenalty` is supposed to be used for the entire match, but in these places the inheritance penalty of a single argument overrides the entire match penalty. The `+ ord(c.inheritancePenalty < 0)` is copied from other places that use the same idiom, the intent is that the existing penalty changes from -1 to 0 first to mark that it participates in inheritance before adding the inheritance depth. --------- Co-authored-by: Andreas Rumpf --- compiler/sigmatch.nim | 11 +++++++---- tests/overload/tcompoundinheritance.nim | 26 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/overload/tcompoundinheritance.nim diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index da5452b2bf..c7ccf1e209 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1541,12 +1541,14 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, reduceToBase(a) if effectiveArgType.kind == tyObject: if sameObjectTypes(f, effectiveArgType): - c.inheritancePenalty = if tfFinal in f.flags: -1 else: 0 + if tfFinal notin f.flags: + inc c.inheritancePenalty, ord(c.inheritancePenalty < 0) result = isEqual # elif tfHasMeta in f.flags: result = recordRel(c, f, a) elif trIsOutParam notin flags: - c.inheritancePenalty = isObjectSubtype(c, effectiveArgType, f, nil) - if c.inheritancePenalty > 0: + let depth = isObjectSubtype(c, effectiveArgType, f, nil) + if depth > 0: + inc c.inheritancePenalty, depth + ord(c.inheritancePenalty < 0) result = isSubtype of tyDistinct: a = a.skipTypes({tyOwned, tyGenericInst, tyRange}) @@ -1846,9 +1848,10 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, if c.inheritancePenalty > -1: minInheritance = min(minInheritance, c.inheritancePenalty) result = x + c.inheritancePenalty = oldInheritancePenalty if result >= isIntConv: if minInheritance < maxInheritancePenalty: - c.inheritancePenalty = oldInheritancePenalty + minInheritance + inc c.inheritancePenalty, minInheritance + ord(c.inheritancePenalty < 0) if result > isGeneric: result = isGeneric bindingRet result else: diff --git a/tests/overload/tcompoundinheritance.nim b/tests/overload/tcompoundinheritance.nim new file mode 100644 index 0000000000..8595d13e56 --- /dev/null +++ b/tests/overload/tcompoundinheritance.nim @@ -0,0 +1,26 @@ +# issue #24773 + +import std/assertions + +type + A {.inheritable.} = object + B = object of A + C = object of B + +proc add1(v: B) = + doAssert true + +proc add1(v: A) = + doAssert false + +proc add2(v: B, v2: A) = + doAssert true + +proc add2(v: A, v2: A) = + doAssert false + +var x: C +var y: B + +add1(x) +add2(x, y) From 2b699bca530317873d0b3bf2ee2e59ef221010f0 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Sat, 15 Mar 2025 10:05:14 -0400 Subject: [PATCH 031/119] new-style concepts - small bugfix (#24778) --- compiler/concepts.nim | 8 ++++++-- tests/concepts/tconceptsv2.nim | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index af06f8cdca..b18956c3b0 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -280,7 +280,7 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = if f.isConcept: if a.acceptsAllTypes: return false - if a.isConcept: + if a.skipTypes(ignorableForArgType).isConcept: # if f is a subset of a then any match to a will also match f. Not the other way around return conceptsMatch(c, a.reduceToBase, f.reduceToBase, m) >= mkSubset else: @@ -319,7 +319,11 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = if a.kind in ignorableForArgType: result = matchType(c, f, a.skipTypes(ignorableForArgType), m) else: - result = sameType(f, a) + if a.kind == tyGenericInst: + # tyOr does this to generic typeclasses + result = a.base.sym == f.sym + else: + result = sameType(f, a) of tyEmpty, tyString, tyCstring, tyPointer, tyNil, tyUntyped, tyTyped, tyVoid: result = a.skipTypes(ignorableForArgType).kind == f.kind of tyBool, tyChar, tyInt..tyUInt64: diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 5befd2fafa..afa66eda33 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -392,7 +392,7 @@ block: proc p[X, Y](z: var A[int, float]) = discard proc g[X, Y](z: var A[X, Y], y: int) = discard - proc h[X, Y](z: var A[X, Y]): A[X, Y] = discard + proc h[X, Y](z: A[X, Y]): A[X, Y] = discard proc spring(x: C4) = discard var d = A[int, float]() @@ -428,6 +428,37 @@ block: assert spring(Impl()) == 2 +block: + type + C1[T] = concept + proc p(s: var Self; x: T) + FreakString = concept + proc p(w: var C1; s: Self) + proc a(x: Self) + DynArray[CT, T] = object + + proc p[CT; T; W; ](w: C1[T]; o: DynArray[CT, T]): int = discard + proc spring(s: auto) = discard + proc spring(s: FreakString) = discard + + spring("hi") + +block: + type + RawWriter = concept + proc write(s: Self; data: pointer; length: int) + ArrayBuffer[N: static int] = object + SeqBuffer = object + CompatBuffer = ArrayBuffer | SeqBuffer + + proc write[T:CompatBuffer](a: var T; data: pointer; length: int) = + discard + + proc spring(r:RawWriter, i: byte)=discard + + var s = ArrayBuffer[1500]() + spring(s, 8.uint8) + # this code fails inside a block for some reason type Indexable[T] = concept proc `[]`(t: Self, i: int): T From 7c5d0055100caaa24ec570554690fb65fbdf3970 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 18 Mar 2025 18:51:34 +0800 Subject: [PATCH 032/119] fixes #10625; setjmp on linux mangles ebp leading to early collection (#24787) fixes #10625 --- compiler/extccomp.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 6226cea960..4bae400dc0 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -474,6 +474,11 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} = proc cFileSpecificOptions(conf: ConfigRef; nimname, fullNimFile: string): string = result = conf.compileOptions + if (conf.cCompiler == ccGcc or conf.cCompiler == ccCLang) and + conf.selectedGC == gcRefc: + # bug #10625 + addOpt(result, "-fno-omit-frame-pointer") + for option in conf.compileOptionsCmd: if strutils.find(result, option, 0) < 0: addOpt(result, option) From 1d3260757502694a16f424212e93601b014207bf Mon Sep 17 00:00:00 2001 From: Angus Gibson Date: Wed, 19 Mar 2025 18:15:54 +1100 Subject: [PATCH 033/119] Allow parsing year "00" with "yy" pattern (#24785) The "yy" pattern is relative to the current century, so year "00" should be valid. --- lib/pure/times.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pure/times.nim b/lib/pure/times.nim index f03bea011a..3cdd3903c9 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -2016,7 +2016,6 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, var year = takeInt(2..2) var thisCen = now().year div 100 parsed.year = some(thisCen*100 + year) - result = year > 0 of yyyy: let year = if input[i] in {'+', '-'}: From 9ace1f97acd93411ac3d8aeeec3ee2d6dbf7f280 Mon Sep 17 00:00:00 2001 From: Esteban C Borsani Date: Sat, 22 Mar 2025 12:38:38 -0300 Subject: [PATCH 034/119] Fix SIGSEGV when closing SSL async socket while sending/receiving (#24795) Async SSL socket SIGSEGV's sometimes when calling socket.close() while send/recv. The issue was found here https://github.com/nitely/nim-hyperx/pull/59. Possibly related: #24024 This can occur when closing the socket while sending or receiving, because `socket.sslHandle` is freed. The sigsegv can also occur on calls that require `socket.bioIn` or `socket.bioOut` because those use `socket.sslHandle` internally. This PR checks sslHandle is set before doing any operation that requires it. --- lib/pure/asyncnet.nim | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index b56289c0c5..fb37afa427 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -207,6 +207,9 @@ proc newAsyncSocket*(domain, sockType, protocol: cint, Protocol(protocol), buffered, inheritable) when defineSsl: + proc raiseSslHandleError = + raiseSSLError("The SSL Handle is closed/unset") + proc getSslError(socket: AsyncSocket, err: cint): cint = assert socket.isSsl assert err < 0 @@ -227,6 +230,8 @@ when defineSsl: proc sendPendingSslData(socket: AsyncSocket, flags: set[SocketFlag]) {.async.} = + if socket.sslHandle == nil: + raiseSslHandleError() let len = bioCtrlPending(socket.bioOut) if len > 0: var data = newString(len) @@ -246,6 +251,8 @@ when defineSsl: await sendPendingSslData(socket, flags) of SSL_ERROR_WANT_READ: var data = await recv(socket.fd.AsyncFD, BufferSize, flags) + if socket.sslHandle == nil: + raiseSslHandleError() let length = len(data) if length > 0: let ret = bioWrite(socket.bioIn, cast[cstring](addr data[0]), length.cint) @@ -262,6 +269,8 @@ when defineSsl: op: untyped) = var opResult {.inject.} = -1.cint while opResult < 0: + if socket.sslHandle == nil: + raiseSslHandleError() ErrClearError() # Call the desired operation. opResult = op @@ -306,6 +315,8 @@ proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} = await connect(socket.fd.AsyncFD, address, port, socket.domain) if socket.isSsl: when defineSsl: + if socket.sslHandle == nil: + raiseSslHandleError() if not isIpAddress(address): # Set the SNI address for this connection. This call can fail if # we're not using TLSv1+. @@ -727,6 +738,8 @@ proc close*(socket: AsyncSocket) = defer: socket.fd.AsyncFD.closeSocket() socket.closed = true # TODO: Add extra debugging checks for this. + when defineSsl: + socket.sslHandle = nil when defineSsl: if socket.isSsl: From 482662d19855788c61b46b1c8f3236c57cba6fb3 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 23 Mar 2025 05:48:21 +0800 Subject: [PATCH 035/119] fixes #24721; Table add missing sink (#24724) fixes #24721 --- lib/pure/collections/tableimpl.nim | 2 +- lib/pure/collections/tables.nim | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 3542741fac..bdd0786c59 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -30,7 +30,7 @@ proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline, outParamsAt: [3 rawGetDeepImpl() proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], - key: A, val: sink B, hc: Hash, h: Hash) = + key: sink A, val: sink B, hc: Hash, h: Hash) = rawInsertImpl() template checkIfInitialized() = diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 082e1e96f5..e4a8a94f33 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -281,7 +281,7 @@ proc initTable*[A, B](initialSize = defaultInitialSize): Table[A, B] = result = default(Table[A, B]) initImpl(result, initialSize) -proc `[]=`*[A, B](t: var Table[A, B], key: A, val: sink B) = +proc `[]=`*[A, B](t: var Table[A, B], key: sink A, val: sink B) = ## Inserts a `(key, value)` pair into `t`. ## ## See also: @@ -494,7 +494,7 @@ proc len*[A, B](t: Table[A, B]): int = result = t.counter -proc add*[A, B](t: var Table[A, B], key: A, val: sink B) {.deprecated: +proc add*[A, B](t: var Table[A, B], key: sink A, val: sink B) {.deprecated: "Deprecated since v1.4; it was more confusing than useful, use `[]=`".} = ## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists. ## @@ -888,7 +888,7 @@ proc `[]`*[A, B](t: TableRef[A, B], key: A): var B = result = t[][key] -proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: sink B) = +proc `[]=`*[A, B](t: TableRef[A, B], key: sink A, val: sink B) = ## Inserts a `(key, value)` pair into `t`. ## ## See also: @@ -1045,7 +1045,7 @@ proc len*[A, B](t: TableRef[A, B]): int = result = t.counter -proc add*[A, B](t: TableRef[A, B], key: A, val: sink B) {.deprecated: +proc add*[A, B](t: TableRef[A, B], key: sink A, val: sink B) {.deprecated: "Deprecated since v1.4; it was more confusing than useful, use `[]=`".} = ## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists. ## @@ -1297,7 +1297,7 @@ proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = proc rawInsert[A, B](t: var OrderedTable[A, B], data: var OrderedKeyValuePairSeq[A, B], - key: A, val: sink B, hc: Hash, h: Hash) = + key: sink A, val: sink B, hc: Hash, h: Hash) = rawInsertImpl() data[h].next = -1 if t.first < 0: t.first = h @@ -1349,7 +1349,7 @@ proc initOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTable[A, result = default(OrderedTable[A, B]) initImpl(result, initialSize) -proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: sink B) = +proc `[]=`*[A, B](t: var OrderedTable[A, B], key: sink A, val: sink B) = ## Inserts a `(key, value)` pair into `t`. ## ## See also: @@ -1547,7 +1547,7 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = result = t.counter -proc add*[A, B](t: var OrderedTable[A, B], key: A, val: sink B) {.deprecated: +proc add*[A, B](t: var OrderedTable[A, B], key: sink A, val: sink B) {.deprecated: "Deprecated since v1.4; it was more confusing than useful, use `[]=`".} = ## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists. ## @@ -1907,7 +1907,7 @@ proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B = echo a['z'] result = t[][key] -proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: sink B) = +proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: sink A, val: sink B) = ## Inserts a `(key, value)` pair into `t`. ## ## See also: @@ -2048,7 +2048,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = result = t.counter -proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: sink B) {.deprecated: +proc add*[A, B](t: OrderedTableRef[A, B], key: sink A, val: sink B) {.deprecated: "Deprecated since v1.4; it was more confusing than useful, use `[]=`".} = ## Puts a new `(key, value)` pair into `t` even if `t[key]` already exists. ## From fcba14707a06271f6e14b6c5641d6d5fbc96ff0d Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 23 Mar 2025 06:59:06 +0300 Subject: [PATCH 036/119] disable "dest register is set" for vm statements (#24797) closes #24780 This proc `genStmt` is only called to run the VM in `vm.evalStmt`, otherwise it's not used in vmgen. Now it acts the same as `proc gen(PCtx, PNode)`, used by `discard` statements, which just calls `freeTemp` on the dest if it was set rather than erroring. --- compiler/vmgen.nim | 6 ++++-- tests/test_nimscript.nims | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 6e47f6fe44..68ef99395e 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -2313,9 +2313,11 @@ proc genStmt*(c: PCtx; n: PNode): int = result = c.code.len var d: TDest = -1 c.gen(n, d) - c.gABC(n, opcEof) if d >= 0: - globalError(c.config, n.info, "VM problem: dest register is set") + # for discardable calls etc, otherwise not valid + freeTemp(c, d) + #globalError(c.config, n.info, "VM problem: dest register is set") + c.gABC(n, opcEof) proc genExpr*(c: PCtx; n: PNode, requiresValue = true): int = c.removeLastEof diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims index 32b7d1416e..15e9d878d8 100644 --- a/tests/test_nimscript.nims +++ b/tests/test_nimscript.nims @@ -136,3 +136,10 @@ block: # cpDir, cpFile, dirExists, fileExists, mkDir, mvDir, mvFile, rmDir, rmF block: # check parseopt can get command line: discard initOptParser() + +# issue #24780: + +proc discardableCall(cmd: string): int {.discardable.} = + result = 123 + +discardableCall "echo hi" From 0b9ed84d32c45f036c44d6235cb6c3bf3c20d202 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:07:45 +0800 Subject: [PATCH 037/119] disable implicit `sinkinference` for stdlibs (#24803) ref https://github.com/nim-lang/Nim/issues/24794 --- lib/system/inclrtl.nim | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/system/inclrtl.nim b/lib/system/inclrtl.nim index 3bf0b98930..28f569a59d 100644 --- a/lib/system/inclrtl.nim +++ b/lib/system/inclrtl.nim @@ -46,5 +46,3 @@ else: {.pragma: compilerRtl, compilerproc.} {.pragma: benign, gcsafe.} - -{.push sinkInference: on.} From d15705e05b166077634a6caa96808d34af1f5d5b Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 25 Mar 2025 05:52:43 +0800 Subject: [PATCH 038/119] fixes usenimrtl with `useMalloc` (#24804) Follow up https://github.com/nim-lang/Nim/pull/19512 ref https://github.com/nim-lang/Nim/issues/24794 Otherwise, `/Users/blue/Desktop/Nim/lib/system/mm/malloc.nim(4, 1) Error: redefinition of 'allocImpl'; previous declaration here: /Users/blue/Desktop/Nim/lib/system/memalloc.nim(51, 8)` In `proc allocImpl*(size: Natural): pointer {.noconv, rtl, tags: [], benign, raises: [].}`, `rtl` means it is an `importc` function instead of a proc forward decl. --- lib/system/mmdisp.nim | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 26f2f0bbf0..de82c0fb47 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -55,7 +55,8 @@ elif defined(gogc): include system / mm / go elif (defined(nogc) or defined(gcDestructors)) and defined(useMalloc): - include system / mm / malloc + when not defined(useNimRtl): + include system / mm / malloc when defined(nogc): proc GC_getStatistics(): string = "" From 909f3b8b798a8e2526dc19a1b8e91698402e85fb Mon Sep 17 00:00:00 2001 From: Zoom Date: Tue, 25 Mar 2025 10:40:01 +0400 Subject: [PATCH 039/119] [feature] stdlib: strutils.multiReplace for character sets (#24805) Multiple replacements based on character sets in a single pass. Useful for string sanitation. Follows existing `multiReplace` semantics. Note: initially copied the substring version logic with a `while` and a named block break, but Godbolt showed it had produced slightly larger assembly using higher registers than the final version. - [x] Tests - [x] changelog.md --- changelog.md | 2 ++ lib/pure/strutils.nim | 40 +++++++++++++++++++++++++++++++++++--- tests/stdlib/tstrutils.nim | 19 +++++++++++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index b9671147f0..ad5ab5f0e3 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,8 @@ errors. - `setutils.symmetricDifference` along with its operator version `` setutils.`-+-` `` and in-place version `setutils.toggle` have been added to more efficiently calculate the symmetric difference of bitsets. +- `strutils.multiReplace` overload for character set replacements in a single pass. + Useful for string sanitation. Follows existing multiReplace semantics. [//]: # "Changes:" - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 687dedd514..c941afd085 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -2202,7 +2202,8 @@ func replace*(s, sub: string, by = ""): string {.rtl, ## * `replace func<#replace,string,char,char>`_ for replacing ## single characters ## * `replaceWord func<#replaceWord,string,string,string>`_ - ## * `multiReplace func<#multiReplace,string,varargs[]>`_ + ## * `multiReplace func<#multiReplace,string,varargs[]>`_ for substrings + ## * `multiReplace func<#multiReplace,openArray[char],varargs[]>`_ for single characters result = "" let subLen = sub.len if subLen == 0: @@ -2245,7 +2246,8 @@ func replace*(s: string, sub, by: char): string {.rtl, ## See also: ## * `find func<#find,string,char,Natural,int>`_ ## * `replaceWord func<#replaceWord,string,string,string>`_ - ## * `multiReplace func<#multiReplace,string,varargs[]>`_ + ## * `multiReplace func<#multiReplace,string,varargs[]>`_ for substrings + ## * `multiReplace func<#multiReplace,openArray[char],varargs[]>`_ for single characters result = newString(s.len) var i = 0 while i < s.len: @@ -2330,7 +2332,39 @@ func multiReplace*(s: string, replacements: varargs[(string, string)]): string = add result, s[i] inc(i) - +func multiReplace*(s: openArray[char]; replacements: varargs[(set[char], char)]): string {.noinit.} = + ## Performs multiple character replacements in a single pass through the input. + ## + ## `multiReplace` scans the input `s` from left to right and replaces + ## characters based on character sets, applying the first matching replacement + ## at each position. Useful for sanitizing or transforming strings with + ## predefined character mappings. + ## + ## The order of the `replacements` matters: + ## - First matching replacement is applied + ## - Subsequent replacements are not considered for the same character + ## + ## See also: + ## - `multiReplace(s: string; replacements: varargs[(string, string)]) <#multiReplace,string,varargs[]>`_, + runnableExamples: + const WinSanitationRules = [ + ({'\0'..'\31'}, ' '), + ({'"'}, '\''), + ({'/', '\\', ':', '|'}, '-'), + ({'*', '?', '<', '>'}, '_'), + ] + # Sanitize a filename with Windows-incompatible characters + const file = "a/file:with?invalid*chars.txt" + doAssert file.multiReplace(WinSanitationRules) == "a-file-with_invalid_chars.txt" + {.cast(noSideEffect).}: + result = newStringUninit(s.len) + for i in 0..'}, '_'), + ] + # Basic character set replacements + doAssert multiReplace("abba", SanitationRules) == "abba" + doAssert multiReplace("a/b\\c:d", SanitationRules) == "a-b-c-d" + doAssert multiReplace("a*b?c", SanitationRules) == "a_b_c" + doAssert multiReplace("\0\3test", SanitationRules) == " test" + doAssert multiReplace("testquote\"", SanitationRules) == "testquote'" + doAssert multiReplace("", SanitationRules) == "" + doAssert multiReplace("/\\:*?\"\0<>", ({'\0'..'\255'}, '.')) == "........." + # `parseEnum`, ref issue #14030 # check enum defined at top level # xxx this is probably irrelevant, and pollutes scope # for remaining tests From d573578b28bc4393dac7f3154b5da29b1fa75358 Mon Sep 17 00:00:00 2001 From: lit Date: Tue, 25 Mar 2025 14:41:17 +0800 Subject: [PATCH 040/119] repl: support eof, define object with fields (#24784) For `nim secret`: - **fix(repl): eof(ctrl-D/Z) and ctrl-C were ignored** - **feat(repl): continueLine figures section, constr, bool ops** --------- Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com> --- compiler/llstream.nim | 52 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/compiler/llstream.nim b/compiler/llstream.nim index cc81484830..9392bb41b2 100644 --- a/compiler/llstream.nim +++ b/compiler/llstream.nim @@ -11,7 +11,7 @@ import pathutils - +import std/strutils when defined(nimPreviewSlimSystem): import std/syncio @@ -86,6 +86,47 @@ const LineContinuationOprs = {'+', '-', '*', '/', '\\', '<', '>', '!', '?', '^', '|', '%', '&', '$', '@', '~', ','} AdditionalLineContinuationOprs = {'#', ':', '='} + LineContinuationTokens = [ + "let", "var", "const", "type", # section + "object", "tuple", + # from ./layouter.oprSet + "div", "mod", "shl", "shr", "in", "notin", "is", + "isnot", "not", "of", "as", "from", "..", "and", "or", "xor", + ] # must be all `nimIdentNormalized`-ed + +proc eqIdent(a, bNormalized: string): bool = + a.nimIdentNormalize == bNormalized + +proc endsWithIdent(s, subs: string): bool = + let le = subs.len + if le > s.len: return false + s[^le .. ^1].eqIdent subs + +proc continuesWithIdent(s, subs: string, start: int): bool = + s.substr(start, start+subs.high).eqIdent subs + +proc endsWithIdent(s, subs: string, endIdx: var int): bool = + endIdx.dec subs.len + result = s.continuesWithIdent(subs, endIdx+1) + +proc containsObjectOf(x: string): bool = + const sep = ' ' + var idx = x.rfind(sep) + if idx == -1: return + template eatWord(word) = + while x[idx] == sep: idx.dec + result = x.endsWithIdent(word, idx) + if not result: return + eatWord "of" + eatWord "object" + result = true + +proc endsWithLineContinuationToken(x: string): bool = + result = false + for tok in LineContinuationTokens: + if x.endsWithIdent(tok): + return true + result = x.containsObjectOf proc endsWithOpr*(x: string): bool = result = x.endsWith(LineContinuationOprs) @@ -93,7 +134,9 @@ proc endsWithOpr*(x: string): bool = proc continueLine(line: string, inTripleString: bool): bool {.inline.} = result = inTripleString or line.len > 0 and ( line[0] == ' ' or - line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)) + line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) or + line.endsWithLineContinuationToken() + ) proc countTriples(s: string): int = result = 0 @@ -109,7 +152,10 @@ proc llReadFromStdin(s: PLLStream, buf: pointer, bufLen: int): int = s.rd = 0 var line = newStringOfCap(120) var triples = 0 - while readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line): + while true: + if not readLineFromStdin(if s.s.len == 0: ">>> " else: "... ", line): + # now readLineFromStdin meets EOF (ctrl-D/Z) or ctrl-C + quit() s.s.add(line) s.s.add("\n") inc triples, countTriples(line) From 8e36fb0fec90fb3a6abdd485755471a5578f83c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=8F=A1=E7=8C=AB=E7=8C=AB?= <164346864@qq.com> Date: Wed, 26 Mar 2025 03:32:12 +0800 Subject: [PATCH 041/119] Update nativesockets.nim, `namelen` should be the len of `name` (#24810) In other places where `getsockname` is called, the size of the 'name' is used. https://github.com/nim-lang/Nim/blob/d573578b28bc4393dac7f3154b5da29b1fa75358/lib/pure/nativesockets.nim#L347-L351 https://github.com/nim-lang/Nim/blob/d573578b28bc4393dac7f3154b5da29b1fa75358/lib/pure/nativesockets.nim#L585-L595 https://github.com/nim-lang/Nim/blob/d573578b28bc4393dac7f3154b5da29b1fa75358/lib/pure/nativesockets.nim#L622-L624 https://github.com/nim-lang/Nim/blob/d573578b28bc4393dac7f3154b5da29b1fa75358/lib/pure/nativesockets.nim#L347-L350 I have checked the [Windows documentation](https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-getsockname#remarks), and it describes it like this: "On call, the namelen parameter contains the size of the name buffer, in bytes. On return, the namelen parameter contains the actual size in bytes of the name parameter." [https://www.man7.org/linux/man-pages/man2/getsockname.2.html](https://www.man7.org/linux/man-pages/man2/getsockname.2.html) say: The addrlen argument should be initialized to indicate the amount of space (in bytes) pointed to by addr. --- lib/pure/nativesockets.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 2bae53d6c8..765be085d0 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -723,7 +723,7 @@ when useNimNetLite: ## ## Similar to POSIX's `getsockname`:idx:. template sockGetNameOrRaiseError(socket: untyped, name: untyped) = - var namelen = sizeof(socket).SockLen + var namelen = sizeof(name).SockLen if getsockname(socket, cast[ptr SockAddr](addr(name)), addr(namelen)) == -1'i32: raiseOSError(osLastError()) From ddd83f8d8afc58a17bee2558d6a26b0b5c54a601 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 26 Mar 2025 03:42:40 +0800 Subject: [PATCH 042/119] fixes #24800; Invalid C code generation with a method, case object in refc (#24809) fixes #24800 This PR avoids a conversion from `sink T` to `T` I will add a test case --- compiler/ccgexprs.nim | 3 ++- tests/tuples/ttuples_various.nim | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 07d2ca453f..6f69a45c5e 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2638,7 +2638,8 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = proc genConv(p: BProc, e: PNode, d: var TLoc) = let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) - if sameBackendTypeIgnoreRange(destType, e[1].typ): + let srcType = e[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) + if sameBackendTypeIgnoreRange(destType, srcType): expr(p, e[1], d) else: genSomeCast(p, e, d) diff --git a/tests/tuples/ttuples_various.nim b/tests/tuples/ttuples_various.nim index e392731d2f..498d624edb 100644 --- a/tests/tuples/ttuples_various.nim +++ b/tests/tuples/ttuples_various.nim @@ -1,11 +1,12 @@ discard """ +matrix: "--mm:refc; --mm:arc" output: ''' it's nil @[1, 2, 3] ''' """ -import macros +import std/[options, macros] block anontuples: @@ -209,3 +210,21 @@ block: # tuple unpacking assignment with underscore doAssert (a, b) == (6, 2) (b, _) = (7, 8) doAssert (a, b) == (6, 7) + +# bug #24800 +type + B[T] = object + case r: bool + of false: + v: ref int + of true: + x: T + U = ref object of RootObj + +method y(_: U) {.base.} = + var s = default(B[tuple[f: B[int], w: B[int]]]) + discard some(s.x) + +proc foo = + var s = U() + y(s) From b82d7e8ba1bf15a24561c97198f8741ffe9f454c Mon Sep 17 00:00:00 2001 From: Zoom Date: Wed, 26 Mar 2025 00:06:40 +0400 Subject: [PATCH 043/119] stdlib: substr uses copymem if available, improve docs (#24792) - `system.substr` now uses `copymem` when available, introducing a small template for nimvm detection (#12517 #12518) - Docs are updated to clarify behaviour on out-of-bounds input - Runnable examples cover more edge cases and do not repeat between overloads - Docs now explain the difference between overloads What bothers me is that the `substr*(a: openArray[char]): string =` which was added by @beef331 is practically an implementation of #14810, which is just a conversion from `openArray` to `string` but somehow it ended up being a `substr` overload, even though its behaviour is totally different, _the "substringing" is performed by a previous step_ (conversion to openArray) and the bounds are not checked. I'm not sure it's that great for overloads to differ in subtle ways so much. What are the cases that `substr` covers now, that prohibit renaming it to `toString` (or something like that)? --- changelog.md | 4 ++ lib/system.nim | 112 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/changelog.md b/changelog.md index ad5ab5f0e3..f3cc3fee4d 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,7 @@ errors. ## Standard library additions and changes [//]: # "Additions:" + - `setutils.symmetricDifference` along with its operator version `` setutils.`-+-` `` and in-place version `setutils.toggle` have been added to more efficiently calculate the symmetric difference of bitsets. @@ -29,8 +30,11 @@ errors. Useful for string sanitation. Follows existing multiReplace semantics. [//]: # "Changes:" + - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. +- `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation. + ## Language changes - An experimental option `--experimental:typeBoundOps` has been added that diff --git a/lib/system.nim b/lib/system.nim index e8d8a8c513..2e4536fdc1 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2769,41 +2769,89 @@ template once*(body: untyped): untyped = {.pop.} # warning[GcMem]: off, warning[Uninit]: off -proc substr*(s: openArray[char]): string = - ## Copies a slice of `s` into a new string and returns this new - ## string. - runnableExamples: - let a = "abcdefgh" - assert a.substr(2, 5) == "cdef" - assert a.substr(2) == "cdefgh" - assert a.substr(5, 99) == "fgh" - result = newString(s.len) - for i, ch in s: - result[i] = ch +template NotJSnotVMnotNims(): static bool = # hack, see: #12517 #12518 + when nimvm: + false + else: + notJSnotNims -proc substr*(s: string, first, last: int): string = # A bug with `magic: Slice` requires this to exist this way - ## Copies a slice of `s` into a new string and returns this new - ## string. +proc substr*(a: openArray[char]): string = + ## Returns a new string, copying contents of `a`. ## - ## The bounds `first` and `last` denote the indices of - ## the first and last characters that shall be copied. If `last` - ## is omitted, it is treated as `high(s)`. If `last >= s.len`, `s.len` - ## is used instead: This means `substr` can also be used to `cut`:idx: - ## or `limit`:idx: a string's length. + ## .. warning:: As opposed to other `substr` overloads, no additional input + ## validation and clamping is performed! + ## + ## This proc does not prevent raising an `IndexDefect` when `a` is being + ## passed using a `toOpenArray` call with out-of-bounds indexes: + ## * `doAssertRaises(IndexDefect): discard "abc".toOpenArray(-9, 9).substr()` + ## + ## If clamping is required, consider using + ## `substr(s: string; first, last: int) <#substr,string,int,int>`_: + ## * `doAssert "abc".substr(-9, 9) == "abc"` + runnableExamples: + let a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] + assert a.substr() == "abcdefgh" + assert a.toOpenArray(2, 5).substr() == "cdef" + assert a.toOpenArray(2, high(a)).substr() == "cdefgh" # From index 2 to `high(a)` + doAssertRaises(IndexDefect): discard a.toOpenArray(5, 99).substr() + {.cast(noSideEffect).}: + result = newStringUninit(a.len) + when NotJSnotVMnotNims: + if a.len > 0: + copyMem(result[0].addr, a[0].unsafeAddr, a.len) + else: + for i, ch in a: + result[i] = ch + +proc substr*(s: string; first, last: int): string = # A bug with `magic: Slice` requires this to exist this way + ## Returns a new string containing a substring (slice) of `s`, + ## copying characters from index `first` to index `last` inclusive. + ## + ## Index values are validated and capped: + ## - Negative `first` is clamped to 0 + ## - If `last >= s.len`, it is clamped to `high(s)` + ## - If `last < first`, returns an empty string + ## This means `substr` can also be used to `cut`:idx: or `limit`:idx: + ## a string's length. + ## + ## .. note:: + ## If index values are ensured to be in-bounds, for performance + ## critical cases consider using a non-clamping overload + ## `substr(a: openArray[char]) <#substr,openArray[char]>`_ runnableExamples: let a = "abcdefgh" - assert a.substr(2, 5) == "cdef" - assert a.substr(2) == "cdefgh" - assert a.substr(5, 99) == "fgh" - - let first = max(first, 0) - let L = max(min(last, high(s)) - first + 1, 0) - result = newString(L) - for i in 0 .. L-1: - result[i] = s[i+first] + assert a.substr(2, 5) == "cdef" # Normal substring + # Invalid indexes + assert a.substr(5, 99) == "fgh" # From index 5 to `high(a)` + assert a.substr(42, 99) == "" # `first` out of bounds + assert a.substr(100, 5) == "" # `first > last` + assert a.substr(-1, 2) == "abc" # Negative `first` clamped to 0 + let + first = max(first, 0) + last = min(last, high(s)) + L = max(last - first + 1, 0) + {.cast(noSideEffect).}: + result = newStringUninit(L) + when NotJSnotVMnotNims: + if L > 0: + copyMem(result[0].addr, s[first].unsafeAddr, L) + else: + for i in 0..`_ overload that returns + ## a substring from `first` to the end of the string. + ## + ## `first` value is validated and capped: + ## - `first >= s.len` returns an empty string + ## - Negative `first` is clamped to 0. + runnableExamples: + let a = "abcdefgh" + assert a.substr(2) == "cdefgh" # From index 2 to string end (`high(a)`) + assert a.substr(100) == "" # `first` out of bounds + assert a.substr(-1) == "abcdefgh" # Negative `first` clamped to 0 + substr(s, first, high(s)) when defined(nimconfig): include "system/nimscript" @@ -2818,8 +2866,10 @@ when not defined(js): proc toOpenArray*[T](x: seq[T]; first, last: int): openArray[T] {. magic: "Slice".} - ## Allows passing the slice of `x` from the element at `first` to the element - ## at `last` to `openArray[T]` parameters without copying it. + ## Returns a non-owning slice (a `view`:idx:) of `x` from the element at + ## index `first` to `last` inclusive. Allows passing slices without copying, + ## as opposed to using the slice operator + ## `\`[]\` <#[],openArray[T],HSlice[U: Ordinal,V: Ordinal]>`_. ## ## Example: ## ```nim From 73112d64a3824963f6e74a06b60d6b9b2d189050 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 26 Mar 2025 23:49:00 +0800 Subject: [PATCH 044/119] fixes #24793; Revert "remove special treatments of sinking const sequences (#24812) fixes #24793 There doesn't seem to have a better solution --- compiler/injectdestructors.nim | 38 +++++++++++++++++++++++-- tests/objects/tobject_default_value.nim | 8 ++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index ce9164058a..90c83124b1 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -493,6 +493,25 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode = # no need to destroy it. result.add tmp +proc isDangerousSeq(t: PType): bool {.inline.} = + let t = t.skipTypes(abstractInst) + result = t.kind == tySequence and tfHasOwned notin t.elementType.flags + +proc containsConstSeq(n: PNode): bool = + if n.kind == nkBracket and n.len > 0 and n.typ != nil and isDangerousSeq(n.typ): + return true + result = false + case n.kind + of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast: + result = containsConstSeq(n[1]) + of nkObjConstr, nkClosure: + for i in 1.. 0 and isDangerousSeq(ri.typ): + inc c.inEnsureMove, isEnsureMove + result = c.genCopy(dest, ri, flags) + dec c.inEnsureMove, isEnsureMove + result.add p(ri, c, s, consumed) + c.finishCopy(result, dest, flags, isFromSink = false) + else: + result = c.genSink(s, dest, p(ri, c, s, consumed), flags) + of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit: result = c.genSink(s, dest, p(ri, c, s, consumed), flags) of nkSym: if isSinkParam(ri.sym) and isLastRead(ri, c, s): diff --git a/tests/objects/tobject_default_value.nim b/tests/objects/tobject_default_value.nim index ffa08d4315..8b6ea812b7 100644 --- a/tests/objects/tobject_default_value.nim +++ b/tests/objects/tobject_default_value.nim @@ -811,3 +811,11 @@ template main {.dirty.} = static: main() main() + +block: + type + MyTyp = ref object + thing = initTable[string,string]() + + var t = MyTyp() + t.thing[""] = "" From 58b1f2817787d68815df3e21048f20eb0eac83c9 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 28 Mar 2025 19:52:45 +0800 Subject: [PATCH 045/119] fixes `implicitConv` discarding flags (#24817) follow up https://github.com/nim-lang/Nim/pull/24809 ref https://github.com/nim-lang/Nim/pull/24815 --- compiler/sigmatch.nim | 2 ++ tests/tuples/ttuples_various.nim | 1 + 2 files changed, 3 insertions(+) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index c7ccf1e209..0393d8ec65 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2185,6 +2185,8 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate, # keep varness if arg.typ != nil and arg.typ.kind == tyVar: result.typ() = toVar(result.typ, tyVar, c.idgen) + # copy the tfVarIsPtr flag + result.typ.flags = arg.typ.flags else: result.typ() = result.typ.skipTypes({tyVar}) diff --git a/tests/tuples/ttuples_various.nim b/tests/tuples/ttuples_various.nim index 498d624edb..2f20e1b78b 100644 --- a/tests/tuples/ttuples_various.nim +++ b/tests/tuples/ttuples_various.nim @@ -1,4 +1,5 @@ discard """ +targets: "c cpp" matrix: "--mm:refc; --mm:arc" output: ''' it's nil From ecdcffed4b4c3bf1e016d62ceae49009fc8b125c Mon Sep 17 00:00:00 2001 From: Zoom Date: Fri, 28 Mar 2025 18:06:22 +0400 Subject: [PATCH 046/119] Mark `system.newStringUninit` sideeffect-free (#24813) - Allows using with `--experimental:strictFuncs` - `{.cast(noSideEffect).}:` inside the proc was required to mutate `s.len`, same as used in `newSeqImpl`. - Removed now unnecessary `noSideEffect` casts in `system.nim` - Closes #24811 Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com> --- changelog.md | 1 + lib/pure/strutils.nim | 3 +-- lib/system.nim | 25 ++++++++++++------------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/changelog.md b/changelog.md index f3cc3fee4d..08c4bd097d 100644 --- a/changelog.md +++ b/changelog.md @@ -34,6 +34,7 @@ errors. - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. - `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation. +- `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`. ## Language changes diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index c941afd085..4e2ae306f8 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -2356,8 +2356,7 @@ func multiReplace*(s: openArray[char]; replacements: varargs[(set[char], char)]) # Sanitize a filename with Windows-incompatible characters const file = "a/file:with?invalid*chars.txt" doAssert file.multiReplace(WinSanitationRules) == "a-file-with_invalid_chars.txt" - {.cast(noSideEffect).}: - result = newStringUninit(s.len) + result = newStringUninit(s.len) for i in 0.. 0: + {.cast(noSideEffect).}: + when defined(nimSeqsV2): + let s = cast[ptr NimStringV2](addr result) + if len > 0: + s.len = len + s.p.data[len] = '\0' + else: + let s = cast[NimString](result) s.len = len - s.p.data[len] = '\0' - else: - let s = cast[NimString](result) - s.len = len - s.data[len] = '\0' + s.data[len] = '\0' else: proc newStringUninit*(len: Natural): string {. magic: "NewString", importc: "mnewString", noSideEffect.} @@ -2794,8 +2795,7 @@ proc substr*(a: openArray[char]): string = assert a.toOpenArray(2, 5).substr() == "cdef" assert a.toOpenArray(2, high(a)).substr() == "cdefgh" # From index 2 to `high(a)` doAssertRaises(IndexDefect): discard a.toOpenArray(5, 99).substr() - {.cast(noSideEffect).}: - result = newStringUninit(a.len) + result = newStringUninit(a.len) when NotJSnotVMnotNims: if a.len > 0: copyMem(result[0].addr, a[0].unsafeAddr, a.len) @@ -2830,8 +2830,7 @@ proc substr*(s: string; first, last: int): string = # A bug with `magic: Slice` first = max(first, 0) last = min(last, high(s)) L = max(last - first + 1, 0) - {.cast(noSideEffect).}: - result = newStringUninit(L) + result = newStringUninit(L) when NotJSnotVMnotNims: if L > 0: copyMem(result[0].addr, s[first].unsafeAddr, L) From e0a4876981746713186811de293fb7abcd992dec Mon Sep 17 00:00:00 2001 From: Jake Leahy Date: Sat, 29 Mar 2025 23:28:28 +1100 Subject: [PATCH 047/119] Fix `nim-gdb.py` script (#24824) Script wasn't working on my machine with GDB 16.2 Main issues - `gdb.types` wasn't imported, leading to import error on initial load - dollar function didn't work with the new mangling scheme Fixes them, also updates the test script to work with some new mangling changes. Test evidence ![image](https://github.com/user-attachments/assets/450b020f-1665-4ed2-9073-d02537150914) --- tests/untestable/gdb/gdb_pretty_printer_test.py | 10 +++++----- tools/debug/nim-gdb.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/untestable/gdb/gdb_pretty_printer_test.py b/tests/untestable/gdb/gdb_pretty_printer_test.py index aed0cfeb0b..8035a95ff6 100644 --- a/tests/untestable/gdb/gdb_pretty_printer_test.py +++ b/tests/untestable/gdb/gdb_pretty_printer_test.py @@ -27,14 +27,14 @@ outputs = [ 'seq(3, 3) = {1, 2, 3}', 'seq(3, 3) = {"one", "two", "three"}', 'Table(3, 64) = {[4] = "four", [5] = "five", [6] = "six"}', - 'Table(3, 8) = {["two"] = 2, ["three"] = 3, ["one"] = 1}', + 'Table(3, 8) = {["three"] = 3, ["one"] = 1, ["two"] = 2}', '{a = 1, b = "some string"}', '("hello", 42)' ] -argRegex = re.compile("^.* = (?:No suitable Nim \$ operator found for type: \w+\s*)*(.*)$") +argRegex = re.compile(r"^.* = (?:No suitable Nim \$ operator found for type: \w+\s*)*(.*)$") # Remove this error message which can pop up -noSuitableRegex = re.compile("(No suitable Nim \$ operator found for type: \w+\s*)") +noSuitableRegex = re.compile(r"(No suitable Nim \$ operator found for type: \w+\s*)") for i, expected in enumerate(outputs): gdb.write(f"\x1b[38;5;105m{i+1}) expecting: {expected}: \x1b[0m", gdb.STDLOG) @@ -46,11 +46,11 @@ for i, expected in enumerate(outputs): if i == 6: # myArray is passed as pointer to int to myDebug. I look up myArray up in the stack gdb.execute("up") - raw = gdb.parse_and_eval("myArray") + raw = gdb.parse_and_eval("myArray_1") elif i == 9: # myOtherArray is passed as pointer to int to myDebug. I look up myOtherArray up in the stack gdb.execute("up") - raw = gdb.parse_and_eval("myOtherArray") + raw = gdb.parse_and_eval("myOtherArray_1") else: rawArg = re.sub(noSuitableRegex, "", gdb.execute("info args", to_string = True)) raw = rawArg.split("=", 1)[-1].strip() diff --git a/tools/debug/nim-gdb.py b/tools/debug/nim-gdb.py index 8c9854bdad..59e6ee99ce 100644 --- a/tools/debug/nim-gdb.py +++ b/tools/debug/nim-gdb.py @@ -1,4 +1,5 @@ import gdb +import gdb.types import re import sys import traceback @@ -151,8 +152,8 @@ class DollarPrintFunction (gdb.Function): "Nim's equivalent of $ operator as a gdb function, available in expressions `print $dollar(myvalue)" dollar_functions = re.findall( - r'(?:NimStringDesc \*|NimStringV2)\s?(dollar__[A-z0-9_]+?)\(([^,)]*)\);', - gdb.execute("info functions dollar__", True, True) + r'(?:NimStringDesc \*|NimStringV2)\s?([A-z0-9_]+?dollar_[A-z0-9_]+?)\(([^,)]*)\);', + gdb.execute("info functions dollar_", True, True) ) def __init__ (self): From 0f5732bc8c35b8f11b55d34da1cbd3b3937b6f4d Mon Sep 17 00:00:00 2001 From: James Date: Sat, 29 Mar 2025 15:08:45 -0700 Subject: [PATCH 048/119] Add withValue for immutable tables (#24825) This change adds `withValue` templates for the `Table` type that are able to operate on immutable table values -- the existing implementation requires a `var`. This is needed for situations where performance is sensitive. There are two goals with my implementation: 1. Don't create a copy of the value in the table. That's why I need the `cursor` pragma. Otherwise, it would copy the value 2. Don't double calculate the hash. That's kind of intrinsic with this implementation. But the only way to achieve this without this PR is to first check `if key in table` then to read `table[key]` I brought this up in the discord and a few folks tried to come up with options that were as fast as this, but nothing quite matched the performance here. Thread starts here: https://discord.com/channels/371759389889003530/371759389889003532/1355206546966974584 --- lib/pure/collections/tables.nim | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index e4a8a94f33..9a71a28d50 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -676,6 +676,68 @@ template withValue*[A, B](t: var Table[A, B], key: A, else: body2 +template withValue*[A, B](t: Table[A, B], key: A, + value, body1, body2: untyped) = + ## Retrieves the value at `t[key]` if it exists, assigns + ## it to the variable `value` and executes `body` + runnableExamples: + type + User = object + name: string + + proc `=copy`(dest: var User, source: User) {.error.} + + proc exec(t: Table[int, User]) = + t.withValue(1, value): + assert value.name == "Hello" + do: + doAssert false + + var executedElseBranch = false + t.withValue(521, value): + doAssert false + do: + executedElseBranch = true + assert executedElseBranch + + var t = initTable[int, User]() + t[1] = User(name: "Hello") + t.exec() + + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + if index > 0: + let value {.cursor, inject.} = t.data[index].val + body1 + else: + body2 + +template withValue*[A, B](t: Table[A, B], key: A, + value, body: untyped) = + ## Retrieves the value at `t[key]` if it exists, assigns + ## it to the variable `value` and executes `body` + runnableExamples: + type + User = object + name: string + + proc `=copy`(dest: var User, source: User) {.error.} + + proc exec(t: Table[int, User]) = + t.withValue(1, value): + assert value.name == "Hello" + + t.withValue(521, value): + doAssert false + + var t = initTable[int, User]() + t[1] = User(name: "Hello") + t.exec() + + withValue(t, key, value, body): + discard + iterator pairs*[A, B](t: Table[A, B]): (A, B) = ## Iterates over any `(key, value)` pair in the table `t`. From f9c8775783c98094615a90760b2ae9a4aca03c70 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 1 Apr 2025 15:37:54 +0800 Subject: [PATCH 049/119] `conv` needs to be picky about aliases and introduces a temp for `addr conv` (#24818) ref https://github.com/nim-lang/Nim/pull/24817 ref https://github.com/nim-lang/Nim/pull/24815 ref https://github.com/status-im/nim-eth/pull/784 ```nim {.emit:""" void foo(unsigned long long* x) { } """.} proc foo(x: var culonglong) {.importc: "foo", nodecl.} proc main(x: var uint64) = # var s: culonglong = u # TODO: var m = uint64(12) # var s = culonglong(m) foo(culonglong m) var u = uint64(12) main(u) ``` Notes that this code gives incompatible errors in 2.0.0, 2.2.0 and the devel branch. With this PR, `conv` is kept, but it seems to go back to https://github.com/nim-lang/Nim/pull/24807 --- compiler/ccgcalls.nim | 4 ++-- compiler/ccgexprs.nim | 15 +++++++++++---- compiler/types.nim | 2 +- tests/ccgbugs/taddrconvs.nim | 27 +++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/ccgbugs/taddrconvs.nim diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 2017f7dffc..02e689071c 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -338,7 +338,7 @@ proc withTmpIfNeeded(p: BProc, a: TLoc, needsTmp: bool): TLoc = else: result = a -proc literalsNeedsTmp(p: BProc, a: TLoc): TLoc = +proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc = result = getTemp(p, a.lode.typ, needsInit=false) genAssignment(p, result, a, {}) @@ -358,7 +358,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n (optByRef notin param.options or not p.module.compileToCpp): a = initLocExpr(p, n) if n.kind in {nkCharLit..nkNilLit}: - addAddrLoc(p.config, literalsNeedsTmp(p, a), result) + addAddrLoc(p.config, expressionsNeedsTmp(p, a), result) else: addAddrLoc(p.config, withTmpIfNeeded(p, a, needsTmp), result) elif p.module.compileToCpp and param.typ.kind in {tyVar} and diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 6f69a45c5e..26908a92ec 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -962,6 +962,11 @@ proc cowBracket(p: BProc; n: PNode) = proc cow(p: BProc; n: PNode) {.inline.} = if n.kind == nkHiddenAddr: cowBracket(p, n[0]) +template ignoreConv(e: PNode): bool = + let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) + let srcType = e[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) + sameBackendTypePickyAliases(destType, srcType) + proc genAddr(p: BProc, e: PNode, d: var TLoc) = # careful 'addr(myptrToArray)' needs to get the ampersand: if e[0].typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr}: @@ -974,7 +979,11 @@ proc genAddr(p: BProc, e: PNode, d: var TLoc) = d.lode = e else: var a: TLoc = initLocExpr(p, e[0]) - putIntoDest(p, d, e, addrLoc(p.config, a), a.storage) + if e[0].kind in {nkHiddenStdConv, nkHiddenSubConv, nkConv} and not ignoreConv(e[0]): + # addr (conv x) introduces a temp because `conv x` is not a rvalue + putIntoDest(p, d, e, addrLoc(p.config, expressionsNeedsTmp(p, a)), a.storage) + else: + putIntoDest(p, d, e, addrLoc(p.config, a), a.storage) template inheritLocation(d: var TLoc, a: TLoc) = if d.k == locNone: d.storage = a.storage @@ -2637,9 +2646,7 @@ proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = putIntoDest(p, d, n, cCast(destType, wrapPar(val)), a.storage) proc genConv(p: BProc, e: PNode, d: var TLoc) = - let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) - let srcType = e[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) - if sameBackendTypeIgnoreRange(destType, srcType): + if ignoreConv(e): expr(p, e[1], d) else: genSomeCast(p, e, d) diff --git a/compiler/types.nim b/compiler/types.nim index 2acb164d4d..9853cf1222 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1420,7 +1420,7 @@ proc sameBackendTypeIgnoreRange*(x, y: PType): bool = proc sameBackendTypePickyAliases*(x, y: PType): bool = var c = initSameTypeClosure() - c.flags.incl {IgnoreTupleFields, PickyCAliases, PickyBackendAliases} + c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases} c.cmp = dcEqIgnoreDistinct result = sameTypeAux(x, y, c) diff --git a/tests/ccgbugs/taddrconvs.nim b/tests/ccgbugs/taddrconvs.nim new file mode 100644 index 0000000000..6990648c4a --- /dev/null +++ b/tests/ccgbugs/taddrconvs.nim @@ -0,0 +1,27 @@ +discard """ + targets: "c cpp" + matrix: "--mm:refc; --mm:orc" +""" + +{.emit:""" +void foo(unsigned long long* x) +{ +} +""".} + +block: + proc foo(x: var culonglong) {.importc: "foo", nodecl.} + + proc main(x: var uint64) = + foo(culonglong x) + + var u = uint64(12) + main(u) + +block: + proc foo(x: var culonglong) {.importc: "foo", nodecl.} + + proc main() = + var m = uint64(12) + foo(culonglong(m)) + main() From 3617d2e077757373fdc3757565fd644a336f4f95 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 2 Apr 2025 15:29:15 +0800 Subject: [PATCH 050/119] fixes `lastRead` uses the `when nimvm` branch (#24834) ```nim proc foo = var x = "1234" var y = x when nimvm: discard else: var s = x doAssert s == "1234" doAssert y == "1234" static: foo() foo() ``` `dfa` chooses the `nimvm` branch, `x` is misread as a last read and `wasMoved`. `injectDestructor` is used for codegen and is not used for vmgen. It's reasonable to choose the codegen path instead of the `nimvm` path so the code works for codegen. Though the problem is often hidden by `cursorinference` or `optimizer`. found in https://github.com/nim-lang/Nim/pull/24831 --- compiler/dfa.nim | 4 ++-- tests/destructor/t23837.nim | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index 5534d07e7c..ef6a767f07 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -439,8 +439,8 @@ proc gen(c: var Con; n: PNode) = genUse(c, n) of nkIfStmt, nkIfExpr: genIf(c, n) of nkWhenStmt: - # This is "when nimvm" node. Chose the first branch. - gen(c, n[0][1]) + # This is "when nimvm" node. Chose the second branch. + gen(c, n[1][0]) of nkCaseStmt: genCase(c, n) of nkWhileStmt: genWhile(c, n) of nkBlockExpr, nkBlockStmt: genBlock(c, n) diff --git a/tests/destructor/t23837.nim b/tests/destructor/t23837.nim index e219dd6b55..7ee20fee41 100644 --- a/tests/destructor/t23837.nim +++ b/tests/destructor/t23837.nim @@ -48,4 +48,18 @@ proc main() = let s = leakyWrapper() echo s -main() \ No newline at end of file +main() + +block: + proc foo = + var x = "1234" + var y = x + when nimvm: + discard + else: + var s = x + doAssert s == "1234" + doAssert y == "1234" + + static: foo() + foo() From 4352fa2ef0cbba953d9a90b90873e8dd0364b72e Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 3 Apr 2025 00:46:29 +0800 Subject: [PATCH 051/119] fixes #24801; Invalid C codegen generated when destroying distinct seq types (#24835) fixes #24801 Because distinct `seq` types match `proc `=destroy`*[T](x: var T) {.inline, magic: "Destroy".}`. But the Nim compiler generates lifted seq types for corresponding distinct types. So we skip the address for distinct types. Related to https://github.com/nim-lang/Nim/pull/22207 I had a hard time finding the other place where generic destructors get replaced by attachedDestructors --- compiler/liftdestructors.nim | 14 ++++++-------- compiler/sempass2.nim | 9 ++++++++- tests/destructor/tdistinctseq.nim | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index c948916132..49c06ce1d5 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -40,7 +40,7 @@ template asink*(t: PType): PSym = getAttachedOp(c.g, t, attachedSink) proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; - info: TLineInfo; idgen: IdGenerator; isDistinct = false): PSym + info: TLineInfo; idgen: IdGenerator): PSym proc createTypeBoundOps*(g: ModuleGraph; c: PContext; orig: PType; info: TLineInfo; idgen: IdGenerator) @@ -1063,9 +1063,7 @@ proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType; assert typ.kind == tyDistinct let baseType = typ.elementType if getAttachedOp(g, baseType, kind) == nil: - # TODO: fixme `isDistinct` is a fix for #23552; remove it after - # `-d:nimPreviewNonVarDestructor` becomes the default - discard produceSym(g, c, baseType, kind, info, idgen, isDistinct = true) + discard produceSym(g, c, baseType, kind, info, idgen) result = getAttachedOp(g, baseType, kind) setAttachedOp(g, idgen.module, typ, kind, result) @@ -1104,7 +1102,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache incl result.flags, sfGeneratedOp proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp; - info: TLineInfo; idgen: IdGenerator; isDiscriminant = false; isDistinct = false): PSym = + info: TLineInfo; idgen: IdGenerator; isDiscriminant = false): PSym = if kind == attachedDup: return symDupPrototype(g, typ, owner, kind, info, idgen) @@ -1115,7 +1113,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp idgen, result, info) if kind == attachedDestructor and g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and - ((g.config.isDefined("nimPreviewNonVarDestructor") and not isDiscriminant) or (typ.kind in {tyRef, tyString, tySequence} and not isDistinct)): + ((g.config.isDefined("nimPreviewNonVarDestructor") and not isDiscriminant) or (typ.kind in {tyRef, tyString, tySequence})): dest.typ = typ else: dest.typ = makeVarType(typ.owner, typ, idgen) @@ -1157,13 +1155,13 @@ proc genTypeFieldCopy(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add newAsgnStmt(xx, yy) proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp; - info: TLineInfo; idgen: IdGenerator; isDistinct = false): PSym = + info: TLineInfo; idgen: IdGenerator): PSym = if typ.kind == tyDistinct: return produceSymDistinctType(g, c, typ, kind, info, idgen) result = getAttachedOp(g, typ, kind) if result == nil: - result = symPrototype(g, typ, typ.owner, kind, info, idgen, isDistinct = isDistinct) + result = symPrototype(g, typ, typ.owner, kind, info, idgen) var a = TLiftCtx(info: info, g: g, kind: kind, c: c, asgnForType: typ, idgen: idgen, fn: result) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 0aa96bd6ea..f1fcd94c53 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -11,7 +11,7 @@ import ast, astalgo, msgs, renderer, magicsys, types, idents, trees, wordrecg, options, guards, lineinfos, semfold, semdata, modulegraphs, varpartitions, typeallowed, nilcheck, errorhandling, - semstrictfuncs, suggestsymdb, pushpoppragmas + semstrictfuncs, suggestsymdb, pushpoppragmas, lowerings import std/[tables, intsets, strutils, sequtils] @@ -1081,6 +1081,13 @@ proc trackCall(tracked: PEffects; n: PNode) = let op = getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)) if op != nil: n[0].sym = op + if TTypeAttachedOp(opKind) == attachedDestructor and + op.typ.len == 2 and op.typ.firstParamType.kind != tyVar: + if n[1].kind == nkSym and n[1].sym.kind == skParam and + n[1].typ.kind == tyVar: + n[1] = genDeref(n[1]) + else: + n[1] = skipAddr(n[1]) if op != nil and op.kind == tyProc: for i in 1.. Date: Thu, 3 Apr 2025 13:53:42 +0300 Subject: [PATCH 052/119] fix infinite recursion with pushed user pragmas (#24839) fixes #24838 --- compiler/pragmas.nim | 9 +++++---- tests/pragmas/tpushuserpragma.nim | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 tests/pragmas/tpushuserpragma.nim diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index a6c1917792..51e044ce0b 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -107,7 +107,7 @@ proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode = return it[1] proc pragma*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords; - isStatement: bool = false) + isStatement: bool = false; comesFromPush = false) proc recordPragma(c: PContext; n: PNode; args: varargs[string]) = var recorded = newNodeI(nkReplayAction, n.info) @@ -893,7 +893,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, if keyDeep: localError(c.config, it.info, "user pragma cannot have arguments") - pragma(c, sym, userPragma.ast, validPragmas, isStatement) + pragma(c, sym, userPragma.ast, validPragmas, isStatement, comesFromPush) n.sons[i..i] = userPragma.ast.sons # expand user pragma with its content i.inc(userPragma.ast.len - 1) # inc by -1 is ok, user pragmas was empty else: @@ -1405,11 +1405,12 @@ proc pragmaRec(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords; inc i proc pragma(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords; - isStatement: bool) = + isStatement: bool; comesFromPush = false) = if n == nil: return pragmaRec(c, sym, n, validPragmas, isStatement) # XXX: in the case of a callable def, this should use its info - implicitPragmas(c, sym, n.info, validPragmas) + if not comesFromPush: + implicitPragmas(c, sym, n.info, validPragmas) proc pragmaCallable*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords, isStatement: bool = false) = diff --git a/tests/pragmas/tpushuserpragma.nim b/tests/pragmas/tpushuserpragma.nim new file mode 100644 index 0000000000..8a7ca33e86 --- /dev/null +++ b/tests/pragmas/tpushuserpragma.nim @@ -0,0 +1,15 @@ +# issue #24838 + +{.pragma: testit, raises: [], deprecated: "abc".} + +{.push testit.} +proc xxx() {.testit.} = + discard "hello" +proc yyy() = + discard "hello" +{.pop.} + +xxx() #[tt.Warning +^ abc; xxx is deprecated [Deprecated]]# +yyy() #[tt.Warning +^ abc; yyy is deprecated [Deprecated]]# From 73aeac81d1616494eef5c0fab2dae72f747d97e5 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:54:00 +0800 Subject: [PATCH 053/119] fixes #24806; don't elide `wasMoved` when syms are used in blocks (#24831) fixes #24806 Blocks don't merge symbols that are used before destruction to the parent scope, which causes `wasMoved; destroy` to elide incorrectly --- compiler/optimizer.nim | 8 ++++++++ tests/arc/t24806.nim | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 tests/arc/t24806.nim diff --git a/compiler/optimizer.nim b/compiler/optimizer.nim index 34e8ec80f4..bf188334b1 100644 --- a/compiler/optimizer.nim +++ b/compiler/optimizer.nim @@ -28,11 +28,14 @@ type hasReturn, hasBreak: bool label: PSym # can be nil parent: ptr BasicBlock + symToDel: seq[PNode] Con = object somethingTodo: bool inFinally: int +proc invalidateWasMoved(c: var BasicBlock; x: PNode) + proc nestedBlock(parent: var BasicBlock; kind: TNodeKind): BasicBlock = BasicBlock(wasMovedLocs: @[], kind: kind, hasReturn: false, hasBreak: false, label: nil, parent: addr(parent)) @@ -62,6 +65,10 @@ proc mergeBasicBlockInfo(parent: var BasicBlock; this: BasicBlock) {.inline.} = if this.hasReturn: parent.wasMovedLocs.setLen 0 parent.hasReturn = true + elif this.symToDel.len > 0: + parent.symToDel = this.symToDel + for i in this.symToDel: + invalidateWasMoved(parent, i) proc wasMovedTarget(matches: var IntSet; branch: seq[PNode]; moveTarget: PNode): bool = result = false @@ -149,6 +156,7 @@ proc analyse(c: var Con; b: var BasicBlock; n: PNode) = # any usage of the location before destruction implies we # cannot elide the 'wasMoved(x)': b.invalidateWasMoved n + b.symToDel.add n of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, diff --git a/tests/arc/t24806.nim b/tests/arc/t24806.nim new file mode 100644 index 0000000000..4af0f5c1a3 --- /dev/null +++ b/tests/arc/t24806.nim @@ -0,0 +1,39 @@ +discard """ + matrix: "-d:useMalloc;" +""" + +type + GlobFilter* = object + incl*: bool + glob*: string + + GlobState* = object + one: int + two: int + +proc aa() = + let filters = @[GlobFilter(incl: true, glob: "**")] + var wbg = newSeqOfCap[GlobState](1) + wbg.add GlobState() + var + dirc = @[wbg] + while true: + wbg = dirc[^1] + dirc.add wbg + break + +var handlerLocs = newSeq[string]() +handlerLocs.add "sammich" +aa() +aa() + +block: # bug #24806 + proc aa() = + var + a = @[0] + b = @[a] + block: + a = b[0] + b.add a + + aa() From 2ed45eb848cbd4d9f88602adf9baf4c7b0d70961 Mon Sep 17 00:00:00 2001 From: "la.panon." Date: Thu, 3 Apr 2025 22:54:39 +0900 Subject: [PATCH 054/119] Make `loadConfig` available from NimScript (#24840) fixes #24837 I really wanted to name the variable just `stream` and leave `defer: ...` and `result =...` out, but the compiler says the variable is redefined, so this is the form. --- lib/pure/parsecfg.nim | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index 0744d94a69..99b1c9a41e 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -540,10 +540,17 @@ proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config = proc loadConfig*(filename: string): Config = ## Loads the specified configuration file into a new Config instance. - let file = open(filename, fmRead) - let fileStream = newFileStream(file) - defer: fileStream.close() - result = fileStream.loadConfig(filename) + when nimvm: + # HACK: As a workaround, + # since open() using {.importc.} is not available on NimScript. + let stringStream = newStringStream(readFile(filename)) + defer: stringStream.close() + result = stringStream.loadConfig(filename) + else: + let file = open(filename, fmRead) + let fileStream = newFileStream(file) + defer: fileStream.close() + result = fileStream.loadConfig(filename) proc replace(s: string): string = var d = "" From 26b86c8f4d2a6b8eef6690e6531ddc562ac18c05 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 3 Apr 2025 22:09:58 +0800 Subject: [PATCH 055/119] Makes `except:` panics on `Defect` (#24821) implements https://github.com/nim-lang/RFCs/issues/557 It inserts defect handing into a bare except branch ```nim try: raiseAssert "test" except: echo "nope" ``` => ```nim try: raiseAssert "test" except: # New behaviov, now well-defined: **never** catches the assert, regardless of panic mode raiseDefect() echo "nope" ``` In this way, `except` still catches foreign exceptions, but panics on `Defect`. Probably when Nim has `except {.foreign.}`, we can extend `raiseDefect` to foreign exceptions as well. That's supposed to be a small use case anyway. `--legacy:noPanicOnExcept` is provided for a transition period. --- changelog.md | 2 ++ compiler/options.nim | 2 ++ compiler/transf.nim | 20 ++++++++++++++++++++ compiler/vmops.nim | 4 ++++ lib/pure/asyncmacro.nim | 2 +- lib/pure/unittest.nim | 31 +++++++++++++++++++++++-------- lib/system.nim | 8 ++++++++ lib/system/embedded.nim | 3 +++ lib/system/jssys.nim | 10 ++++++++++ testament/important_packages.nim | 4 ++-- tests/async/tasynctry.nim | 2 +- tests/ccgbugs/t21995.nim | 2 +- tests/ccgbugs/t9286.nim | 2 +- tests/float/tfloatrange.nim | 4 ++-- tests/iter/titer_issues.nim | 3 +++ tests/js/tarrayboundscheck.nim | 4 ++-- 16 files changed, 85 insertions(+), 18 deletions(-) diff --git a/changelog.md b/changelog.md index 08c4bd097d..14e37490e5 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,8 @@ errors. - With `-d:nimPreviewAsmSemSymbol`, backticked symbols are type checked in the `asm/emit` statements. +- The bare `except:` now panics on `Defect`. Use `except Exception:` or `except Defect:` to catch `Defect`. `--legacy:noPanicOnExcept` is provided for a transition period. + ## Standard library additions and changes [//]: # "Additions:" diff --git a/compiler/options.nim b/compiler/options.nim index ea75a68487..af2334a39d 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -248,6 +248,8 @@ type ## Useful for libraries that rely on local passC jsNoLambdaLifting ## Old transformation for closures in JS backend + noPanicOnExcept + ## don't panic on bare except SymbolFilesOption* = enum disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest diff --git a/compiler/transf.nim b/compiler/transf.nim index 433a534912..89911daf15 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -957,6 +957,23 @@ proc transformCall(c: PTransf, n: PNode): PNode = else: result = s +proc transformBareExcept(c: PTransf, n: PNode): PNode = + result = newTransNode(nkExceptBranch, n, 1) + if isEmptyType(n[0].typ): + result[0] = newNodeI(nkStmtList, n[0].info) + else: + result[0] = newNodeIT(nkStmtListExpr, n[0].info, n[0].typ) + # Generating `raiseDefect()` + let raiseDefectCall = callCodegenProc(c.graph, "raiseDefect", n[0].info) + result[0].add raiseDefectCall + if n[0].kind in {nkStmtList, nkStmtListExpr}: + # flattens stmtList + for son in n[0]: + result[0].add son + else: + result[0].add n[0] + result[0] = transform(c, result[0]) + proc transformExceptBranch(c: PTransf, n: PNode): PNode = if n[0].isInfixAs() and not isImportedException(n[0][1].typ, c.graph.config): let excTypeNode = n[0][1] @@ -985,6 +1002,9 @@ proc transformExceptBranch(c: PTransf, n: PNode): PNode = # Replace the `Exception as foobar` with just `Exception`. result[0] = transform(c, n[0][1]) result[1] = actions + elif n.len == 1 and + noPanicOnExcept notin c.graph.config.legacyFeatures: + result = transformBareExcept(c, n) else: result = transformSons(c, n) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 8b0b8b5c7c..9403fe1e4b 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -143,6 +143,9 @@ proc getCurrentExceptionMsgWrapper(a: VmArgs) {.nimcall.} = proc getCurrentExceptionWrapper(a: VmArgs) {.nimcall.} = setResult(a, a.currentException) +proc raiseDefectWrapper(a: VmArgs) {.nimcall.} = + discard + proc staticWalkDirImpl(path: string, relative: bool): PNode = result = newNode(nkBracket) for k, f in walkDir(path, relative): @@ -263,6 +266,7 @@ proc registerAdditionalOps*(c: PCtx) = wrap2si(readLines, ioop) systemop getCurrentExceptionMsg systemop getCurrentException + systemop raiseDefect 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.} = diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 951d98bd39..30c5e8f539 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -46,7 +46,7 @@ template createCb(futTyp, strName, identName, futureVarCompletions: untyped) = {.gcsafe.}: next.addCallback(cast[proc() {.closure, gcsafe.}](proc = identName(fut, it))) - except: + except Exception: futureVarCompletions if fut.finished: # Take a look at tasyncexceptions for the bug which this fixes. diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index f14aead2bb..1cd5fd1bb9 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -556,15 +556,16 @@ template test*(name, body) {.dirty.} = body {.pop.} - except: + except Exception: let e = getCurrentException() let eTypeDesc = "[" & exceptionTypeName(e) & "]" checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " " & eTypeDesc) - if e == nil: # foreign - fail() - else: - var stackTrace {.inject.} = e.getStackTrace() - fail() + var stackTrace {.inject.} = e.getStackTrace() + fail() + + except: + checkpoint("Unhandled exception: " & getCurrentExceptionMsg() & " []") + fail() finally: if testStatusIMPL == TestStatus.FAILED: @@ -760,6 +761,14 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped = expect IOError, OSError, ValueError, AssertionDefect: defectiveRobot() + template expectException(errorTypes, lineInfoLit, body): NimNode {.dirty.} = + try: + body + checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.") + fail() + except errorTypes: + discard + template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} = {.push warning[BareExcept]:off.} try: @@ -770,17 +779,23 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped = fail() except errorTypes: discard - except: + except Exception: let err = getCurrentException() checkpoint(lineInfoLit & ": Expect Failed, " & $err.name & " was thrown.") fail() {.pop.} var errorTypes = newNimNode(nnkBracket) + var hasException = false for exp in exceptions: + if exp.strVal == "Exception": + hasException = true errorTypes.add(exp) - result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body)) + if hasException: + result = getAst(expectException(errorTypes, errorTypes.lineInfo, body)) + else: + result = getAst(expectBody(errorTypes, errorTypes.lineInfo, body)) proc disableParamFiltering* = ## disables filtering tests with the command line params diff --git a/lib/system.nim b/lib/system.nim index 4a9d8cc0b8..64682b56f4 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2312,8 +2312,16 @@ when notJSnotNims and hostOS != "standalone": ## ## .. warning:: Only use this if you know what you are doing. currException = exc + + proc raiseDefect() {.compilerRtl.} = + let e = getCurrentException() + if e of Defect: + reportUnhandledError(e) + rawQuit(1) + elif defined(nimscript): proc getCurrentException*(): ref Exception {.compilerRtl.} = discard + proc raiseDefect*() {.compilerRtl.} = discard when notJSnotNims: {.push stackTrace: off, profiler: off.} diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index ea6776f58a..b3febe7849 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -42,6 +42,9 @@ proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring proc reraiseException() {.compilerRtl.} = sysFatal(ReraiseDefect, "no exception to reraise") +proc raiseDefect() {.compilerRtl.} = + sysFatal(ReraiseDefect, "exception handling is not available") + proc writeStackTrace() = discard proc unsetControlCHook() = discard diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index ec1af2ea57..3b995f69b1 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -154,6 +154,16 @@ proc raiseException(e: ref Exception, ename: cstring) {. e.trace = rawWriteStackTrace() {.emit: "throw `e`;".} +proc raiseDefect() {.compilerproc, asmNoStackFrame.} = + if isNimException(): + let e = getCurrentException() + if e of Defect: + if excHandler == 0: + unhandledException(e) + when NimStackTrace: + e.trace = rawWriteStackTrace() + {.emit: "throw `e`;".} + proc reraiseException() {.compilerproc, asmNoStackFrame.} = if lastJSError == nil: raise newException(ReraiseDefect, "no exception to reraise") diff --git a/testament/important_packages.nim b/testament/important_packages.nim index 2471a2d113..5233ec7f4d 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -42,7 +42,7 @@ pkg "asyncthreadpool", "nimble test --mm:refc" pkg "awk" pkg "bigints" pkg "binaryheap", "nim c -r binaryheap.nim" -pkg "BipBuffer" +pkg "BipBuffer", url = "https://github.com/nim-lang/BipBuffer" pkg "bncurve" pkg "brainfuck", "nim c -d:release -r tests/compile.nim" pkg "c2nim", "nim c testsuite/tester.nim" @@ -66,7 +66,7 @@ pkg "delaunay" pkg "docopt" pkg "dotenv" pkg "easygl", "nim c -o:egl -r src/easygl.nim", "https://github.com/jackmott/easygl" -pkg "elvis" +pkg "elvis", url = "https://github.com/nim-lang/elvis" pkg "eth", "nim c -o:common -r tests/common/all_tests" pkg "faststreams" pkg "fidget" diff --git a/tests/async/tasynctry.nim b/tests/async/tasynctry.nim index 25eab87fbe..c4c66204c6 100644 --- a/tests/async/tasynctry.nim +++ b/tests/async/tasynctry.nim @@ -21,7 +21,7 @@ proc catch() {.async.} = # TODO: Create a test for when exceptions are not caught. try: await foobar() - except: + except Exception: echo("Generic except: ", getCurrentExceptionMsg().splitLines[0]) try: diff --git a/tests/ccgbugs/t21995.nim b/tests/ccgbugs/t21995.nim index 0ec88aa59a..12598347eb 100644 --- a/tests/ccgbugs/t21995.nim +++ b/tests/ccgbugs/t21995.nim @@ -5,5 +5,5 @@ discard """ try: raise -except: +except ReraiseDefect: echo "Hi!" \ No newline at end of file diff --git a/tests/ccgbugs/t9286.nim b/tests/ccgbugs/t9286.nim index 2fec233079..06ec52adf3 100644 --- a/tests/ccgbugs/t9286.nim +++ b/tests/ccgbugs/t9286.nim @@ -1,5 +1,5 @@ discard """ - action: run + matrix: "--legacy:noPanicOnExcept" """ import options diff --git a/tests/float/tfloatrange.nim b/tests/float/tfloatrange.nim index d345166f4f..02af9dd1e3 100644 --- a/tests/float/tfloatrange.nim +++ b/tests/float/tfloatrange.nim @@ -32,7 +32,7 @@ doAssert(sqrt(x) == 3.0) var z = -10.0 try: myoverload(StrictPositive(z)) -except: +except Exception: echo "range fail expected" @@ -45,6 +45,6 @@ doAssert(strictOnlyProc(x2)) try: let x4 = 0.0.Positive discard strictOnlyProc(x4) -except: +except Exception: echo "range fail expected" diff --git a/tests/iter/titer_issues.nim b/tests/iter/titer_issues.nim index c82b3902d4..2452102bd3 100644 --- a/tests/iter/titer_issues.nim +++ b/tests/iter/titer_issues.nim @@ -385,6 +385,9 @@ iterator tryFinally() {.closure.} = try: echo "trying" raise + except ReraiseDefect: + echo "exception caught" + break route except: echo "exception caught" break route diff --git a/tests/js/tarrayboundscheck.nim b/tests/js/tarrayboundscheck.nim index d8bf8de97c..2e6c789e3f 100644 --- a/tests/js/tarrayboundscheck.nim +++ b/tests/js/tarrayboundscheck.nim @@ -35,9 +35,9 @@ proc test_arrayboundscheck() = let idx = indices[i] try: echo months[idx] - except: + except IndexDefect: echo "month out of bounds: ", idx - except: + except IndexDefect: echo "idx out of bounds: ", i # #13966 From 10c9ebad9303d9c4be393da913f4e12650783539 Mon Sep 17 00:00:00 2001 From: Miran Date: Thu, 3 Apr 2025 17:43:27 +0200 Subject: [PATCH 056/119] test `stint` more thoroughly (#24832) --- testament/important_packages.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testament/important_packages.nim b/testament/important_packages.nim index 5233ec7f4d..b0ef47f9bf 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -155,7 +155,7 @@ pkg "smtp", "nimble compileExample" pkg "snip", "nimble test", "https://github.com/genotrance/snip" pkg "ssostrings", "nim c -r tests/tssostrings.nim" pkg "stew" -pkg "stint", "nim c stint.nim" +pkg "stint", "nimble test_internal" pkg "strslice" pkg "strunicode", "nim c -r --mm:refc src/strunicode.nim" pkg "supersnappy" From 052ceca3c19ba9a9c59820f40394d28188e59865 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 8 Apr 2025 20:07:24 +0800 Subject: [PATCH 057/119] bump to windows 2025 (#24853) --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9696c2086d..7fa0c3911d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -36,16 +36,16 @@ jobs: CPU: amd64 NIM_COMPILE_TO_CPP: true Windows_amd64_batch0_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 # see also: `NIM_TEST_PACKAGES` NIM_TESTAMENT_BATCH: "0_3" Windows_amd64_batch1_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 NIM_TESTAMENT_BATCH: "1_3" Windows_amd64_batch2_3: - vmImage: 'windows-2019' + vmImage: 'windows-2025' CPU: amd64 NIM_TESTAMENT_BATCH: "2_3" From a625fab098ec41ce763f5dec37441b0496c6276a Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 8 Apr 2025 17:00:58 +0300 Subject: [PATCH 058/119] make `fillObjectFields` recur over base type (#24854) fixes #24847 Object constructors call `fillObjectFields` when a field inside the constructor does not have a location, however when the field is from a base type this does not process it. Now `fillObjectFields` also calls itself for the base type to fix this but not sure if this is a good solution as `fillObjectFields` is used in other places too. --- compiler/ccgtypes.nim | 2 ++ tests/objects/t24847.nim | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/objects/t24847.nim diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a49ea802ac..9cb80baef8 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -759,6 +759,8 @@ proc fillObjectFields*(m: BModule; typ: PType) = var check = initIntSet() var ignored = newBuilder("") addRecordFields(ignored, m, typ, check) + if typ.baseClass != nil: + fillObjectFields(m, typ.baseClass.skipTypes(skipPtrs)) proc mangleDynLibProc(sym: PSym): Rope diff --git a/tests/objects/t24847.nim b/tests/objects/t24847.nim new file mode 100644 index 0000000000..667a4520fa --- /dev/null +++ b/tests/objects/t24847.nim @@ -0,0 +1,30 @@ +# issue #24847 + +block: # original issue test + type + R[C] = ref object of RootObj + b: C + K[S] = ref object of R[S] + W[J] = object + case y: bool + of false, true: discard + + proc e[T]() = discard K[T]() + iterator h(): int {.closure.} = e[W[int]]() + let _ = h + type U = distinct int + e[W[U]]() + +block: # simplified + type + R[C] = ref object of RootObj + b: C + K[S] = ref object of R[S] + W[J] = object + case y: bool + of false, true: discard + + type U = distinct int + + discard K[W[int]]() + discard K[W[U]]() From 29a2e25d1e47deaa7fbaaf5aaf78ab5be430c731 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 8 Apr 2025 23:54:31 +0800 Subject: [PATCH 059/119] =?UTF-8?q?fixes=20#24850;=20macro-generated=20if/?= =?UTF-8?q?else=20and=20when/else=20statements=20have=20m=E2=80=A6=20(#248?= =?UTF-8?q?52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …ismatched indentation with repr fixes #24850 --- compiler/renderer.nim | 60 ++++++++++++++++++++---------------- tests/arc/topt_cursor.nim | 3 +- tests/arc/topt_no_cursor.nim | 3 +- tests/stdlib/trepr.nim | 24 +++++++++++++++ 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index a598a0ae5e..08f2562b9d 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -565,8 +565,16 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkIfExpr: result = lsub(g, n[0][0]) + lsub(g, n[0][1]) + lsons(g, n, 1) + len("if_:_") - of nkElifExpr: result = lsons(g, n) + len("_elif_:_") - of nkElseExpr: result = lsub(g, n[0]) + len("_else:_") # type descriptions + of nkElifExpr, nkElifBranch: + if isEmptyType(n[1].typ): + result = lsons(g, n) + len("elif_:_") + else: + result = lsons(g, n) + len("_elif_:_") + of nkElseExpr, nkElse: + if isEmptyType(n[0].typ): + result = lsub(g, n[0]) + len("else:_") + else: + result = lsub(g, n[0]) + len("_else:_") # type descriptions of nkTypeOfExpr: result = (if n.len > 0: lsub(g, n[0]) else: 0)+len("typeof()") of nkRefTy: result = (if n.len > 0: lsub(g, n[0])+1 else: 0) + len("ref") of nkPtrTy: result = (if n.len > 0: lsub(g, n[0])+1 else: 0) + len("ptr") @@ -609,8 +617,6 @@ proc lsub(g: TSrcGen; n: PNode): int = of nkCommentStmt: result = n.comment.len of nkOfBranch: result = lcomma(g, n, 0, - 2) + lsub(g, lastSon(n)) + len("of_:_") of nkImportAs: result = lsub(g, n[0]) + len("_as_") + lsub(g, n[1]) - of nkElifBranch: result = lsons(g, n) + len("elif_:_") - of nkElse: result = lsub(g, n[0]) + len("else:_") of nkFinally: result = lsub(g, n[0]) + len("finally:_") of nkGenericParams: result = lcomma(g, n) + 2 of nkFormalParams: @@ -1469,15 +1475,30 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) = putWithSpace(g, tkColon, ":") if n.len > 0: gsub(g, n[0], 1) gsons(g, n, emptyContext, 1) - of nkElifExpr: - putWithSpace(g, tkElif, " elif") - gcond(g, n[0]) - putWithSpace(g, tkColon, ":") - gsub(g, n, 1) - of nkElseExpr: - put(g, tkElse, " else") - putWithSpace(g, tkColon, ":") - gsub(g, n, 0) + of nkElifExpr, nkElifBranch: + if isEmptyType(n[1].typ): + optNL(g) + putWithSpace(g, tkElif, "elif") + gsub(g, n, 0) + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[1], c) + else: + putWithSpace(g, tkElif, " elif") + gcond(g, n[0]) + putWithSpace(g, tkColon, ":") + gsub(g, n, 1) + of nkElseExpr, nkElse: + if isEmptyType(n[0].typ): + optNL(g) + put(g, tkElse, "else") + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[0], c) + else: + put(g, tkElse, " else") + putWithSpace(g, tkColon, ":") + gsub(g, n, 0) of nkTypeOfExpr: put(g, tkType, "typeof") put(g, tkParLe, "(") @@ -1739,19 +1760,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) = of nkMixinStmt: putWithSpace(g, tkMixin, "mixin") gcomma(g, n, c) - of nkElifBranch: - optNL(g) - putWithSpace(g, tkElif, "elif") - gsub(g, n, 0) - putWithSpace(g, tkColon, ":") - gcoms(g) - gstmts(g, n[1], c) - of nkElse: - optNL(g) - put(g, tkElse, "else") - putWithSpace(g, tkColon, ":") - gcoms(g) - gstmts(g, n[0], c) of nkFinally, nkDefer: optNL(g) if n.kind == nkFinally: diff --git a/tests/arc/topt_cursor.nim b/tests/arc/topt_cursor.nim index 7941329219..9a9552c837 100644 --- a/tests/arc/topt_cursor.nim +++ b/tests/arc/topt_cursor.nim @@ -9,7 +9,8 @@ var try: x_cursor = ("hi", 5) if cond: - x_cursor = ("different", 54) else: + x_cursor = ("different", 54) + else: x_cursor = ("string here", 80) echo [ :tmpD = `$$`(x_cursor) diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim index 9d59fc66c2..59bbd99660 100644 --- a/tests/arc/topt_no_cursor.nim +++ b/tests/arc/topt_no_cursor.nim @@ -129,7 +129,8 @@ if dirExists(this.value): var :tmpD par = (dir: :tmpD = `=dup`(this.value) - :tmpD, front: "") else: + :tmpD, front: "") +else: var :tmpD_1 :tmpD_2 diff --git a/tests/stdlib/trepr.nim b/tests/stdlib/trepr.nim index 3956b98f95..d70319a7ed 100644 --- a/tests/stdlib/trepr.nim +++ b/tests/stdlib/trepr.nim @@ -326,3 +326,27 @@ do: static: main() main() + +import std/macros + +# bug #24850 +macro a() = + let + y = quote do: discard + b = nnkIfStmt.newTree( + nnkElifExpr.newTree(ident "true", y), nnkElseExpr.newTree(y)) + d = nnkWhenStmt.newTree( + nnkElifExpr.newTree(ident "true", y), nnkElseExpr.newTree(y)) + doAssert repr(b) == """ +if true: + discard +else: + discard""" + + doAssert repr(d) == """ +when true: + discard +else: + discard""" + +a() From 40a1ec21d78d48a0f012d552047e24326b04fc7a Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 10 Apr 2025 15:24:19 +0800 Subject: [PATCH 060/119] overhaul hook injections (#24841) ref https://github.com/nim-lang/Nim/issues/24764 To keep destructors injected consistently, we need to transform `mAsgn` properly into `nkSinkAsgn` and `nkAsgn`. This PR is the first step towards overhauling hook injections. In this PR, hooks (except mAsgn) are treated consistently whether it is resolved in matching or instantiated by sempass2. It also fixes a spelling `=wasMoved` to its normalized version, which caused no replacing generic hook calls with lifted hook calls. --- compiler/injectdestructors.nim | 6 +- compiler/liftdestructors.nim | 29 +++--- compiler/semcall.nim | 8 -- compiler/semdata.nim | 165 ++++++++++++++++++++++++++++++++- compiler/semexprs.nim | 100 -------------------- compiler/semmagic.nim | 37 +------- compiler/sempass2.nim | 39 +++++--- lib/pure/streamwrapper.nim | 3 +- lib/system.nim | 4 +- 9 files changed, 213 insertions(+), 178 deletions(-) diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 90c83124b1..932851a3ca 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -17,14 +17,14 @@ import ast, astalgo, msgs, renderer, magicsys, types, idents, options, lowerings, modulegraphs, lineinfos, parampatterns, sighashes, liftdestructors, optimizer, - varpartitions, aliasanalysis, dfa, wordrecg, trees + varpartitions, aliasanalysis, dfa, wordrecg import std/[strtabs, tables, strutils, intsets] when defined(nimPreviewSlimSystem): import std/assertions -from trees import exprStructuralEquivalent, getRoot, whichPragma +from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites type Con = object @@ -400,7 +400,7 @@ proc genWasMoved(c: var Con, n: PNode): PNode = result = genOp(c, op, n) else: result = newNodeI(nkCall, n.info) - result.add(newSymNode(createMagic(c.graph, c.idgen, "`=wasMoved`", mWasMoved))) + result.add(newSymNode(createMagic(c.graph, c.idgen, "wasMoved", mWasMoved))) result.add copyTree(n) #mWasMoved does not take the address #if n.kind != nkSym: # message(c.graph.config, n.info, warnUser, "wasMoved(" & $n & ")") diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index 49c06ce1d5..e6b2979dbd 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -91,7 +91,7 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = call.typ() = t body.add newAsgnStmt(x, call) elif c.kind == attachedWasMoved: - body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc genAddr(c: var TLiftCtx; x: PNode): PNode = if x.kind == nkHiddenDeref: @@ -148,7 +148,7 @@ proc destructorCall(c: var TLiftCtx; op: PSym; x: PNode): PNode = if sfNeverRaises notin op.flags: c.canRaise = true if c.addMemReset: - result = newTree(nkStmtList, destroy, genBuiltin(c, mWasMoved, "`=wasMoved`", x)) + result = newTree(nkStmtList, destroy, genBuiltin(c, mWasMoved, "wasMoved", x)) else: result = destroy @@ -168,7 +168,7 @@ proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode; enforceDefaultOp: bool, defaultOp(c, f.typ, body, x.dotField(f), b) else: if enforceWasMoved: - body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x.dotField(f)) + body.add genBuiltin(c, mWasMoved, "wasMoved", x.dotField(f)) fillBody(c, f.typ, body, x.dotField(f), b) of nkNilLit: discard of nkRecCase: @@ -277,7 +277,8 @@ proc fillBodyObjT(c: var TLiftCtx; t: PType, body, x, y: PNode) = #body.add newAsgnStmt(blob, x) var wasMovedCall = newNodeI(nkCall, c.info) - wasMovedCall.add(newSymNode(createMagic(c.g, c.idgen, "`=wasMoved`", mWasMoved))) + wasMovedCall.add(newSymNode(createMagic(c.g, c.idgen, "wasMoved", mWasMoved))) + wasMovedCall.add x # mWasMoved does not take the address body.add wasMovedCall @@ -612,7 +613,7 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = if canFormAcycle(c.g, t.elemType): # follow all elements: forallElements(c, t, body, x, y) - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = createTypeBoundOps(c.g, c.c, t, body.info, c.idgen) @@ -650,7 +651,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = if op == nil: return # protect from recursion body.add newHookCall(c, op, x, y) - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) of attachedDup: # XXX: replace these with assertions. let op = getAttachedOp(c.g, t, c.kind) @@ -672,7 +673,7 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add genBuiltin(c, mDestroy, "destroy", x) of attachedTrace: discard "strings are atomic and have no inner elements that are to trace" - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc cyclicType*(g: ModuleGraph, t: PType): bool = case t.kind @@ -771,7 +772,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = # If the ref is polymorphic we have to account for this body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(x, c.idgen), y) #echo "can follow ", elemType, " static ", isFinal(elemType) - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) of attachedDup: if isCyclic: body.add newAsgnStmt(x, y) @@ -838,7 +839,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(xenv, c.idgen), y) - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = case c.kind @@ -866,7 +867,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.sons.insert(des, 0) of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: discard - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = var actions = newNodeI(nkStmtList, c.info) @@ -894,7 +895,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add genIf(c, x, actions) of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: discard - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = if c.kind == attachedDeepCopy: @@ -934,7 +935,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.sons.insert(des, 0) of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: discard - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = let xx = genBuiltin(c, mAccessEnv, "accessEnv", x) @@ -952,7 +953,7 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = body.add genIf(c, xx, actions) of attachedDeepCopy: assert(false, "cannot happen") of attachedTrace: discard - of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x) proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = case t.kind @@ -1021,7 +1022,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = of {attachedAsgn, attachedSink, attachedDup}: body.add newAsgnStmt(x, y) of attachedWasMoved: - body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x) + body.add genBuiltin(c, mWasMoved, "wasMoved", x) else: fillBodyObjT(c, t, body, x, y) else: diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 0b1236b254..1ffe5aed4a 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -244,14 +244,6 @@ proc effectProblem(f, a: PType; result: var string; c: PContext) = if not c.graph.compatibleProps(c.graph, f, a): result.add "\n The `.requires` or `.ensures` properties are incompatible." -proc renderNotLValue(n: PNode): string = - result = $n - let n = if n.kind == nkHiddenDeref: n[0] else: n - if n.kind == nkHiddenCallConv and n.len > 1: - result = $n[0] & "(" & result & ")" - elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: - result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")" - proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): (TPreferedDesc, string) = var prefer = preferName diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 6e256b3d32..1719d75404 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -9,14 +9,15 @@ ## This module contains the data structures for the semantic checking phase. -import std/[tables, intsets, sets] +import std/[tables, intsets, sets, strutils] when defined(nimPreviewSlimSystem): import std/assertions import options, ast, msgs, idents, renderer, - magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable + magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable, + types, lowerings, trees, parampatterns import ic / ic @@ -635,3 +636,163 @@ proc rememberExpansion*(c: PContext; info: TLineInfo; expandedSym: PSym) = ## delegated to the "rod" file mechanism. if c.config.symbolFiles != disabledSf: storeExpansion(c.encoder, c.packedRepr, info, expandedSym) + +const + errVarForOutParamNeededX = "for a 'var' type a variable needs to be passed; but '$1' is immutable" + errXStackEscape = "address of '$1' may not escape its stack frame" + +proc renderNotLValue*(n: PNode): string = + result = $n + let n = if n.kind == nkHiddenDeref: n[0] else: n + if n.kind == nkHiddenCallConv and n.len > 1: + result = $n[0] & "(" & result & ")" + elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: + result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")" + +proc isAssignable(c: PContext, n: PNode): TAssignableResult = + result = parampatterns.isAssignable(c.p.owner, n) + +proc newHiddenAddrTaken(c: PContext, n: PNode, isOutParam: bool): PNode = + if n.kind == nkHiddenDeref and not (c.config.backend == backendCpp or + sfCompileToCpp in c.module.flags): + checkSonsLen(n, 1, c.config) + result = n[0] + else: + result = newNodeIT(nkHiddenAddr, n.info, makeVarType(c, n.typ)) + result.add n + let aa = isAssignable(c, n) + let sym = getRoot(n) + if aa notin {arLValue, arLocalLValue}: + if aa == arDiscriminant and c.inUncheckedAssignSection > 0: + discard "allow access within a cast(unsafeAssign) section" + 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: + localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) + +proc analyseIfAddressTaken(c: PContext, n: PNode, isOutParam: bool): PNode = + result = n + case n.kind + of nkSym: + # n.sym.typ can be nil in 'check' mode ... + if n.sym.typ != nil and + skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: + incl(n.sym.flags, sfAddrTaken) + result = newHiddenAddrTaken(c, n, isOutParam) + of nkDotExpr: + checkSonsLen(n, 2, c.config) + if n[1].kind != nkSym: + internalError(c.config, n.info, "analyseIfAddressTaken") + return + if skipTypes(n[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: + incl(n[1].sym.flags, sfAddrTaken) + result = newHiddenAddrTaken(c, n, isOutParam) + of nkBracketExpr: + checkMinSonsLen(n, 1, c.config) + if skipTypes(n[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: + if n[0].kind == nkSym: incl(n[0].sym.flags, sfAddrTaken) + result = newHiddenAddrTaken(c, n, isOutParam) + else: + result = newHiddenAddrTaken(c, n, isOutParam) + +proc analyseIfAddressTakenInCall*(c: PContext, n: PNode, isConverter = false) = + checkMinSonsLen(n, 1, c.config) + if n[0].typ == nil: + # n[0] might be erroring node in nimsuggest + return + const + FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, + mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, + mAppendSeqElem, mNewSeq, mShallowCopy, mDeepCopy, mMove, mWasMoved} + + template checkIfConverterCalled(c: PContext, n: PNode) = + ## Checks if there is a converter call which wouldn't be checked otherwise + # Call can sometimes be wrapped in a deref + let node = if n.kind == nkHiddenDeref: n[0] else: n + if node.kind == nkHiddenCallConv: + analyseIfAddressTakenInCall(c, node, true) + # get the real type of the callee + # it may be a proc var with a generic alias type, so we skip over them + var t = n[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}) + if n[0].kind == nkSym and n[0].sym.magic in FakeVarParams: + # BUGFIX: check for L-Value still needs to be done for the arguments! + # note sometimes this is eval'ed twice so we check for nkHiddenAddr here: + for i in 1.. 0: + discard "allow access within a cast(unsafeAssign) section" + else: + localError(c.config, it.info, errVarForOutParamNeededX % $it) + # Make sure to still check arguments for converters + c.checkIfConverterCalled(n[i]) + # bug #5113: disallow newSeq(result) where result is a 'var T': + if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}: + var arg = n[1] #.skipAddr + if arg.kind == nkHiddenDeref: arg = arg[0] + if arg.kind == nkSym and arg.sym.kind == skResult and + arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}: + localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments})) + + return + for i in 1.. 0: - discard "allow access within a cast(unsafeAssign) section" - 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: - localError(c.config, n.info, errVarForOutParamNeededX % renderNotLValue(n)) - -proc analyseIfAddressTaken(c: PContext, n: PNode, isOutParam: bool): PNode = - result = n - case n.kind - of nkSym: - # n.sym.typ can be nil in 'check' mode ... - if n.sym.typ != nil and - skipTypes(n.sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: - incl(n.sym.flags, sfAddrTaken) - result = newHiddenAddrTaken(c, n, isOutParam) - of nkDotExpr: - checkSonsLen(n, 2, c.config) - if n[1].kind != nkSym: - internalError(c.config, n.info, "analyseIfAddressTaken") - return - if skipTypes(n[1].sym.typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: - incl(n[1].sym.flags, sfAddrTaken) - result = newHiddenAddrTaken(c, n, isOutParam) - of nkBracketExpr: - checkMinSonsLen(n, 1, c.config) - if skipTypes(n[0].typ, abstractInst-{tyTypeDesc}).kind notin {tyVar, tyLent}: - if n[0].kind == nkSym: incl(n[0].sym.flags, sfAddrTaken) - result = newHiddenAddrTaken(c, n, isOutParam) - else: - result = newHiddenAddrTaken(c, n, isOutParam) - -proc analyseIfAddressTakenInCall(c: PContext, n: PNode, isConverter = false) = - checkMinSonsLen(n, 1, c.config) - if n[0].typ == nil: - # n[0] might be erroring node in nimsuggest - return - const - FakeVarParams = {mNew, mNewFinalize, mInc, ast.mDec, mIncl, mExcl, - mSetLengthStr, mSetLengthSeq, mAppendStrCh, mAppendStrStr, mSwap, - mAppendSeqElem, mNewSeq, mShallowCopy, mDeepCopy, mMove, - mWasMoved} - - template checkIfConverterCalled(c: PContext, n: PNode) = - ## Checks if there is a converter call which wouldn't be checked otherwise - # Call can sometimes be wrapped in a deref - let node = if n.kind == nkHiddenDeref: n[0] else: n - if node.kind == nkHiddenCallConv: - analyseIfAddressTakenInCall(c, node, true) - # get the real type of the callee - # it may be a proc var with a generic alias type, so we skip over them - var t = n[0].typ.skipTypes({tyGenericInst, tyAlias, tySink}) - if n[0].kind == nkSym and n[0].sym.magic in FakeVarParams: - # BUGFIX: check for L-Value still needs to be done for the arguments! - # note sometimes this is eval'ed twice so we check for nkHiddenAddr here: - for i in 1.. 0: - discard "allow access within a cast(unsafeAssign) section" - else: - localError(c.config, it.info, errVarForOutParamNeededX % $it) - # Make sure to still check arguments for converters - c.checkIfConverterCalled(n[i]) - # bug #5113: disallow newSeq(result) where result is a 'var T': - if n[0].sym.magic in {mNew, mNewFinalize, mNewSeq}: - var arg = n[1] #.skipAddr - if arg.kind == nkHiddenDeref: arg = arg[0] - if arg.kind == nkSym and arg.sym.kind == skResult and - arg.typ.skipTypes(abstractInst).kind in {tyVar, tyLent}: - localError(c.config, n.info, errXStackEscape % renderTree(n[1], {renderNoComments})) - - return - for i in 1.. 0 and a.sym.name.s[0] == '=' and tracked.owner.kind != skMacro: - var opKind = find(AttachedOpToStr, a.sym.name.s.normalize) - if a.sym.name.s == "=": opKind = attachedAsgn.int - if opKind != -1: + var (isHook, opKind) = findHookKind(a.sym.name.s) + if isHook: # rebind type bounds operations after createTypeBoundOps call let t = n[1].typ.skipTypes({tyAlias, tyVar}) - if a.sym != getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)): + if a.sym != getAttachedOp(tracked.graph, t, opKind): createTypeBoundOps(tracked, t, n.info, explicit = true) - let op = getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)) - if op != nil: - n[0].sym = op - if TTypeAttachedOp(opKind) == attachedDestructor and - op.typ.len == 2 and op.typ.firstParamType.kind != tyVar: - if n[1].kind == nkSym and n[1].sym.kind == skParam and - n[1].typ.kind == tyVar: - n[1] = genDeref(n[1]) - else: - n[1] = skipAddr(n[1]) + # replace builtin hooks with lifted ones + n = replaceHookMagic(tracked.c, n, opKind) if op != nil and op.kind == tyProc: for i in 1.. Date: Fri, 11 Apr 2025 09:28:53 +0800 Subject: [PATCH 061/119] fixes `=copy` is transformed into `nkFastAsgn` and unify `mAsgn` handling (#24857) `=copy` should be treated like `=` instead of `shallowCopy`, i.e., `nkFastAsgn` by default. `mAsgn` is treated similar in sempass2 too --- compiler/sem.nim | 1 + compiler/semdata.nim | 10 ++++++++-- compiler/semmagic.nim | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/sem.nim b/compiler/sem.nim index f4b6d06b82..3392db7a9d 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -755,6 +755,7 @@ proc preparePContext*(graph: ModuleGraph; module: PSym; idgen: IdGenerator): PCo result.semTypeNode = semTypeNode result.instTypeBoundOp = sigmatch.instTypeBoundOp result.hasUnresolvedArgs = hasUnresolvedArgs + result.semAsgnOpr = semAsgnOpr result.templInstCounter = new int pushProcCon(result, module) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 1719d75404..5eb8086f45 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -174,6 +174,9 @@ type importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id]) skipTypes*: seq[PNode] # used to skip types between passes in type section. So far only used for inheritance, sets and generic bodies. inTypeofContext*: int + + semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.} + TBorrowState* = enum bsNone, bsReturnNotMatch, bsNoDistinct, bsGeneric, bsNotSupported, bsMatch @@ -789,8 +792,11 @@ proc replaceHookMagic*(c: PContext, n: PNode, kind: TTypeAttachedOp): PNode = if op != nil: result[0] = newSymNode(op) analyseIfAddressTakenInCall(c, result, false) - of attachedSink, attachedAsgn, attachedDeepCopy: - # TODO: `nkSinkAsgn`, `nkAsgn` + of attachedSink: + result = c.semAsgnOpr(c, n, nkSinkAsgn) + of attachedAsgn: + result = c.semAsgnOpr(c, n, nkAsgn) + of attachedDeepCopy: result = n let t = n[1].typ.skipTypes(abstractVar) let op = getAttachedOp(c.graph, t, kind) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 2a2efc3971..b42e6e26ec 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -612,9 +612,10 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, of mArrPut: result = semArrPut(c, n, flags) of mAsgn: - if n[0].sym.name.s == "=": + case n[0].sym.name.s + of "=", "=copy": result = semAsgnOpr(c, n, nkAsgn) - elif n[0].sym.name.s == "=sink": + of "=sink": result = semAsgnOpr(c, n, nkSinkAsgn) else: result = semShallowCopy(c, n, flags) From 918f972369e62d6aa07e173aa1e70aa5c4714fc8 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 11 Apr 2025 04:29:20 +0300 Subject: [PATCH 062/119] skip semicolon in stmtlist expr parsing (#24855) Previously it would try to parse the semicolon as its own statement and produce an `nkEmpty` node Also more than 1 semicolon in an expression list i.e. `(a;; b)` gives an "expression expected" error in `semiStmtList` when multiple semicolons are allowed in normal statements, this could be fixed by changing the `if tok.kind == tokSemicolon` check to a `while` but it does not match the grammar so not done here. --- compiler/parser.nim | 2 ++ tests/parser/tstmtlistexprempty.nim | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 tests/parser/tstmtlistexprempty.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index 7475050974..7f438f4208 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -699,10 +699,12 @@ proc parsePar(p: var Parser): PNode = asgn.add b result.add(asgn) if p.tok.tokType == tkSemiColon: + getTok(p) semiStmtList(p, result) elif p.tok.tokType == tkSemiColon: # stmt context: result.add(a) + getTok(p) semiStmtList(p, result) else: a = colonOrEquals(p, a) diff --git a/tests/parser/tstmtlistexprempty.nim b/tests/parser/tstmtlistexprempty.nim new file mode 100644 index 0000000000..1d71c1aeb6 --- /dev/null +++ b/tests/parser/tstmtlistexprempty.nim @@ -0,0 +1,23 @@ +discard """ + nimout: ''' +StmtList + ReturnStmt + StmtListExpr + Call + DotExpr + Ident "x" + Ident "add" + StrLit "123" + Call + DotExpr + Ident "x" + Ident "add" + StrLit "123" + Ident "x" +''' +""" + +import std/macros + +dumpTree: + return (x.add("123"); x.add("123"); x) From d4098e6ca031aba9825980f9a17d3b54a9990577 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Fri, 11 Apr 2025 00:54:52 -0400 Subject: [PATCH 063/119] new-style concept bugfix (#24858) Combining two small PRs in one here. The test case explains what was wrong with the concepts and for naitivesockets, it's typical to adjust `ai_flags` so I opened that up. --- compiler/concepts.nim | 3 ++- tests/concepts/tconceptsv2.nim | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index b18956c3b0..1c8860bd5f 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -347,10 +347,11 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = k1 = f.kidsLen - ord(f.kind == tyGenericInst) k2 = ea.kidsLen - ord(ea.kind == tyGenericInst) if sameType(f.genericHead, ea.genericHead) and k1 == k2: + result = true for i in 1 ..< k2: if not matchType(c, f[i], ea[i], m): + result = false break - result = true of tyOrdinal: result = isOrdinalType(a, allowEnumWithHoles = false) or a.kind == tyGenericParam of tyStatic: diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index afa66eda33..83a19348b1 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -459,6 +459,32 @@ block: var s = ArrayBuffer[1500]() spring(s, 8.uint8) +block: + type + Future[T] = object + SyncType = concept + proc p(s: Self) + AsyncType = concept + proc p(s: Self) : Future[void] + SyncImpl = object + AsyncImpl = object + Container[T] = object + + proc p(x: SyncImpl) = discard + proc p(x: AsyncImpl): Future[void] = discard + + proc p(x: Container[SyncType]) = discard + proc p(x: Container[AsyncImpl]): Future[void] = discard + + assert SyncImpl is SyncType + assert SyncImpl isnot AsyncType + assert AsyncImpl isnot SyncType + assert AsyncImpl is AsyncType + assert Container[SyncImpl] is SyncType + assert Container[SyncImpl] isnot AsyncType + assert Container[AsyncImpl] isnot SyncType + assert Container[AsyncImpl] is AsyncType + # this code fails inside a block for some reason type Indexable[T] = concept proc `[]`(t: Self, i: int): T From 897126a7117c5bed90ec9c29a8792ee878278f55 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 11 Apr 2025 19:38:35 +0300 Subject: [PATCH 064/119] fix array/set/tuple literals with generic expression elements (#24497) fixes #24484, fixes #24672 When an array, set or tuple constructor has an element that resolves to `tyFromExpr`, the type of the entire literal is now set to `tyFromExpr` and the subsequent elements are not matched to any type. The remaining expressions are still typed (a version of the PR before this called `semGenericStmt` on them instead), however elements with int literal types have their types set to `nil`, since generic instantiation removes int literal types and the int literal type is required for implicitly converting the int literal element to the set type. Tuples should not really need this but it is done for them anyway in case it messes up some type inference --------- Co-authored-by: Andreas Rumpf --- compiler/semexprs.nim | 74 ++++++++++++++++++++++++----- tests/proc/tgenericdefaultparam.nim | 37 +++++++++++++++ 2 files changed, 100 insertions(+), 11 deletions(-) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 98f2950c90..de4ea2cc2b 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -724,7 +724,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp # nkBracket nodes can also be produced by the VM as seq constant nodes # in which case, we cannot produce a new array type for the node, # as this might lose type info even when the node has array type - let constructType = n.typ.isNil + let constructType = n.typ.isNil or n.typ.kind == tyFromExpr var expectedElementType, expectedIndexType: PType = nil var expectedBase: PType = nil if constructType: @@ -773,7 +773,11 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp let yy = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) var typ: PType - if constructType: + var isGeneric = false + if yy.typ != nil and yy.typ.kind == tyFromExpr: + isGeneric = true + typ = nil # will not be used + elif constructType: typ = yy.typ if expectedElementType == nil: expectedElementType = typ @@ -798,11 +802,21 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PTyp let xx = semExprWithType(c, x, {efTypeAllowed}, expectedElementType) result.add xx - if constructType: + if xx.typ != nil and xx.typ.kind == tyFromExpr: + isGeneric = true + elif constructType: typ = commonType(c, typ, xx.typ) #n[i] = semExprWithType(c, x, {}) #result.add fitNode(c, typ, n[i]) inc(lastIndex) + if isGeneric: + for i in 0.. Date: Fri, 11 Apr 2025 23:50:13 +0300 Subject: [PATCH 065/119] ignore typeof in closure iterators (#24861) fixes #24859 --- compiler/closureiters.nim | 2 +- tests/iter/ttypeofclosureiter.nim | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 tests/iter/ttypeofclosureiter.nim diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index dd6eb986ee..7e0f54b12e 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -164,7 +164,7 @@ type const nkSkip = {nkEmpty..nkNilLit, nkTemplateDef, nkTypeSection, nkStaticStmt, - nkCommentStmt, nkMixinStmt, nkBindStmt} + procDefs + nkCommentStmt, nkMixinStmt, nkBindStmt, nkTypeOfExpr} + procDefs emptyStateLabel = -1 localNotSeen = -1 localRequiresLifting = -2 diff --git a/tests/iter/ttypeofclosureiter.nim b/tests/iter/ttypeofclosureiter.nim new file mode 100644 index 0000000000..3ea3c1d442 --- /dev/null +++ b/tests/iter/ttypeofclosureiter.nim @@ -0,0 +1,7 @@ +# issue #24859 + +template u(): int = + yield 0 + 0 +iterator s(): int {.closure.} = discard default(typeof(u())) +let _ = s From 520bbaf38428608284d7928f8666f2fb042a1e8f Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Sat, 12 Apr 2025 00:47:09 -0400 Subject: [PATCH 066/119] split `nativesockets` bindAddr into two procs (#24860) #24858 --- lib/pure/nativesockets.nim | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index 765be085d0..c7868b74c6 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -285,6 +285,19 @@ proc listen*(socket: SocketHandle, backlog = SOMAXCONN): cint {.tags: [ else: result = posix.listen(socket, cint(backlog)) +proc getAddrInfo*(address: string, port: Port, hints: AddrInfo): ptr AddrInfo = + ## + ## + ## .. warning:: The resulting `ptr AddrInfo` must be freed using `freeAddrInfo`! + result = nil + let socketPort = if hints.ai_socktype == toInt(SOCK_RAW): "" else: $port + var gaiResult = getaddrinfo(address, socketPort.cstring, addr(hints), result) + if gaiResult != 0'i32: + when useWinVersion or defined(freertos) or defined(nuttx): + raiseOSError(osLastError()) + else: + raiseOSError(osLastError(), $gai_strerror(gaiResult)) + proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, sockType: SockType = SOCK_STREAM, protocol: Protocol = IPPROTO_TCP): ptr AddrInfo = @@ -296,7 +309,7 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, ai_socktype: toInt(sockType), ai_protocol: toInt(protocol) ) - result = nil + # OpenBSD doesn't support AI_V4MAPPED and doesn't define the macro AI_V4MAPPED. # FreeBSD, Haiku don't support AI_V4MAPPED but defines the macro. # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198092 @@ -305,13 +318,7 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, not defined(android) and not defined(haiku): if domain == AF_INET6: hints.ai_flags = AI_V4MAPPED - let socketPort = if sockType == SOCK_RAW: "" else: $port - var gaiResult = getaddrinfo(address, socketPort.cstring, addr(hints), result) - if gaiResult != 0'i32: - when useWinVersion or defined(freertos) or defined(nuttx): - raiseOSError(osLastError()) - else: - raiseOSError(osLastError(), $gai_strerror(gaiResult)) + result = getAddrInfo(address, port, hints) proc ntohl*(x: uint32): uint32 = ## Converts 32-bit unsigned integers from network to host byte order. From 42df731a2db6b971631780b4185f0f70ac8b3e7a Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sat, 12 Apr 2025 12:47:57 +0800 Subject: [PATCH 067/119] fixes #24764; cross-module sink analysis broken (#24862) fixes #24764 It now consumes the `conv(x)` arg for the explicit sinking. So the explicit sinking is kept as it is. Follows up https://github.com/nim-lang/Nim/pull/20585 Related issues: https://github.com/nim-lang/Nim/issues/20572 Probably the same needs to be applied to explicit `copy` to prevent a copy turning into a sink --- compiler/injectdestructors.nim | 5 ++++- tests/arc/m24764.nim | 4 ++++ tests/arc/t24764.nim | 22 ++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/arc/m24764.nim create mode 100644 tests/arc/t24764.nim diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 932851a3ca..cacb3305eb 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -1197,7 +1197,10 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, flags: set[MoveOrCopy result.add p(ri, c, s, consumed) c.finishCopy(result, dest, flags, isFromSink = false) of nkHiddenSubConv, nkHiddenStdConv, nkConv, nkObjDownConv, nkObjUpConv, nkCast: - result = c.genSink(s, dest, p(ri, c, s, sinkArg), flags) + if IsExplicitSink in flags: + result = c.genSink(s, dest, p(ri, c, s, consumed), flags) + else: + result = c.genSink(s, dest, p(ri, c, s, sinkArg), flags) of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt, nkPragmaBlock: template process(child, s): untyped = moveOrCopy(dest, child, c, s, flags) # We know the result will be a stmt so we use that fact to optimize diff --git a/tests/arc/m24764.nim b/tests/arc/m24764.nim new file mode 100644 index 0000000000..d10809a594 --- /dev/null +++ b/tests/arc/m24764.nim @@ -0,0 +1,4 @@ +type QObject* {.inheritable.} = object +proc `=destroy`(self: var QObject) = discard +proc `=sink`(dest: var QObject, source: QObject) = discard +proc `=copy`(dest: var QObject, source: QObject) {.error.} \ No newline at end of file diff --git a/tests/arc/t24764.nim b/tests/arc/t24764.nim new file mode 100644 index 0000000000..d7aa900c0d --- /dev/null +++ b/tests/arc/t24764.nim @@ -0,0 +1,22 @@ +discard """ + matrix: "--mm:arc" +""" + +import m24764 + +type QWidget* = object of QObject +proc `=copy`(dest: var QWidget, source: QWidget) {.error.} +proc `=sink`(dest: var QWidget, source: QWidget) = + `=sink`(QObject(dest), QObject(source)) + +proc show(v: QWidget) = discard + +proc main() = + let btn = QWidget() + + let tmp = proc() = + btn.show() + + btn.show() + +main() \ No newline at end of file From b961ee69aa5e9bd205976e80de78593abaffe775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=8F=A1=E7=8C=AB=E7=8C=AB?= <164346864@qq.com> Date: Sat, 12 Apr 2025 13:16:13 +0800 Subject: [PATCH 068/119] Update winlean.nim, import `AddrInfo` from `ws2tcpip.h` (#24828) [ADDRINFOA](https://learn.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-addrinfoa#remarks). --- lib/windows/winlean.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 9b6b9a28eb..99f46fc6fb 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -437,7 +437,7 @@ type fd_count*: cint # unsigned fd_array*: array[0..FD_SETSIZE-1, SocketHandle] - AddrInfo* = object + AddrInfo* {.importc: "ADDRINFOA", header: "ws2tcpip.h".} = object ai_flags*: cint ## Input flags. ai_family*: cint ## Address family of socket. ai_socktype*: cint ## Socket type. From 97d819a25173840d6abdb249b73fe629578fd918 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 12 Apr 2025 09:37:36 +0300 Subject: [PATCH 069/119] add bit type overloads of `$` and `repr` (#24865) fixes #24864 --- lib/system/dollars.nim | 28 ++++++++++++++++------------ lib/system/repr_v2.nim | 29 +++++++++++++++++------------ tests/system/treprconverter.nim | 5 +++++ 3 files changed, 38 insertions(+), 24 deletions(-) create mode 100644 tests/system/treprconverter.nim diff --git a/lib/system/dollars.nim b/lib/system/dollars.nim index c26dad5b76..e33d6bbc8a 100644 --- a/lib/system/dollars.nim +++ b/lib/system/dollars.nim @@ -14,20 +14,24 @@ when not defined(nimPreviewSlimSystem): result = "" result.addFloat(x) -proc `$`*(x: int): string {.raises: [].} = - ## Outplace version of `addInt`. - result = "" - result.addInt(x) +template addIntAlias(T: typedesc) = + proc `$`*(x: T): string {.raises: [].} = + ## Outplace version of `addInt`. + result = "" + result.addInt(x) -proc `$`*(x: int64): string {.raises: [].} = - ## Outplace version of `addInt`. - result = "" - result.addInt(x) +# need to declare for bit types as well to not clash with converters: +addIntAlias int +addIntAlias int8 +addIntAlias int16 +addIntAlias int32 +addIntAlias int64 -proc `$`*(x: uint64): string {.raises: [].} = - ## Outplace version of `addInt`. - result = "" - addInt(result, x) +addIntAlias uint +addIntAlias uint8 +addIntAlias uint16 +addIntAlias uint32 +addIntAlias uint64 # same as old `ctfeWhitelist` behavior, whether or not this is a good idea. template gen(T) = diff --git a/lib/system/repr_v2.nim b/lib/system/repr_v2.nim index 1c21c06470..efbbdab721 100644 --- a/lib/system/repr_v2.nim +++ b/lib/system/repr_v2.nim @@ -14,21 +14,26 @@ proc rangeBase(T: typedesc): typedesc {.magic: "TypeTrait".} proc repr*(x: NimNode): string {.magic: "Repr", noSideEffect.} -proc repr*(x: int): string = - ## Same as $x - $x +template dollarAlias(T: typedesc) = + proc repr*(x: T): string {.noSideEffect.} = + ## Same as $x + $x -proc repr*(x: int64): string = - ## Same as $x - $x +# need to declare for bit types as well to not clash with converters: +dollarAlias int +dollarAlias int8 +dollarAlias int16 +dollarAlias int32 +dollarAlias int64 -proc repr*(x: uint64): string {.noSideEffect.} = - ## Same as $x - $x +dollarAlias uint +dollarAlias uint8 +dollarAlias uint16 +dollarAlias uint32 +dollarAlias uint64 -proc repr*(x: float): string = - ## Same as $x - $x +dollarAlias float +dollarAlias float32 proc repr*(x: bool): string {.magic: "BoolToStr", noSideEffect.} ## repr for a boolean argument. Returns `x` diff --git a/tests/system/treprconverter.nim b/tests/system/treprconverter.nim new file mode 100644 index 0000000000..545fa54bd7 --- /dev/null +++ b/tests/system/treprconverter.nim @@ -0,0 +1,5 @@ +# issue #24864 + +type S = distinct uint16 +converter d(field: uint8 | uint16): S = discard +discard (repr(0'u16), repr(0'u8)) From 4d075dc3017c967a48ddc9f1ef92e72516ff39cb Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 12 Apr 2025 09:39:11 +0300 Subject: [PATCH 070/119] clean up opensym encounters in compiler (#24866) To protect against crashes when this stops being experimental, in most places handled the exact same as normal symchoices (not encountered in typed ast) --- compiler/ast.nim | 3 +-- compiler/lookups.nim | 4 +--- compiler/patterns.nim | 2 +- compiler/renderer.nim | 9 +++------ compiler/reorder.nim | 2 +- compiler/semexprs.nim | 4 +--- compiler/trees.nim | 6 +++--- compiler/vm.nim | 4 ++-- compiler/vmgen.nim | 2 +- 9 files changed, 14 insertions(+), 22 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 3ed9a7c675..e35a0b2031 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -933,8 +933,7 @@ proc getPIdent*(a: PNode): PIdent {.inline.} = case a.kind of nkSym: a.sym.name of nkIdent: a.ident - of nkOpenSymChoice, nkClosedSymChoice: a.sons[0].sym.name - of nkOpenSym: getPIdent(a.sons[0]) + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name else: nil const diff --git a/compiler/lookups.nim b/compiler/lookups.nim index e452da959d..ec5fdd69b0 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -58,13 +58,11 @@ proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent = of nkLiterals - nkFloatLiterals: id.add(x.renderTree) else: handleError(n, origin) result = getIdent(c.cache, id) - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: if n[0].kind == nkSym: result = n[0].sym.name else: handleError(n, origin) - of nkOpenSym: - result = considerQuotedIdent(c, n[0], origin) else: handleError(n, origin) diff --git a/compiler/patterns.nim b/compiler/patterns.nim index 32ec7fb537..17e5a86cf9 100644 --- a/compiler/patterns.nim +++ b/compiler/patterns.nim @@ -77,7 +77,7 @@ proc inSymChoice(sc, x: PNode): bool = result = false for i in 0.. 0: result = bracketKind(g, n[0]) else: result = bkNone of nkSym: @@ -1421,10 +1421,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) = of nkPrefix: gsub(g, n, 0) if n.len > 1: - let opr = if n[0].kind == nkIdent: n[0].ident - elif n[0].kind == nkSym: n[0].sym.name - elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name - else: nil + let opr = getPIdent(n[0]) let nNext = skipHiddenNodes(n[1]) if nNext.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) diff --git a/compiler/reorder.nim b/compiler/reorder.nim index 2f7c04af10..dac316fb75 100644 --- a/compiler/reorder.nim +++ b/compiler/reorder.nim @@ -93,7 +93,7 @@ proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLev of nkIdent: uses.incl n.ident.id of nkSym: uses.incl n.sym.name.id of nkAccQuoted: uses.incl accQuoted(cache, n).id - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: uses.incl n[0].sym.name.id of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt: for i in 0.. Date: Sat, 12 Apr 2025 16:40:25 +1000 Subject: [PATCH 071/119] Allow specifiying path to use for stdin error messages (#24595) Implements #24569 Adds `--stdinfile` flag for specifying the file to use in place of `stdinfile.nim` in error messages. Will enable easier integration of tooling with nim check --- changelog.md | 2 +- compiler/commands.nim | 6 +++++- compiler/options.nim | 2 ++ compiler/pipelines.nim | 3 ++- tests/tools/tloadstdin.nim | 16 ++++++++++++++++ tests/tools/tloadstdin.nims | 1 + 6 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 tests/tools/tloadstdin.nim create mode 100644 tests/tools/tloadstdin.nims diff --git a/changelog.md b/changelog.md index 14e37490e5..6529a26f1f 100644 --- a/changelog.md +++ b/changelog.md @@ -80,4 +80,4 @@ errors. ## Tool changes - +- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`) diff --git a/compiler/commands.nim b/compiler/commands.nim index 879a995882..ba3d5eadc8 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -459,7 +459,7 @@ template handleStdinOrCmdInput = conf.outDir = getNimcacheDir(conf) proc handleStdinInput*(conf: ConfigRef) = - conf.projectName = "stdinfile" + conf.projectName = conf.stdinFile.string conf.projectIsStdin = true handleStdinOrCmdInput() @@ -935,6 +935,10 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; var value: int = 0 discard parseSaturatedNatural(arg, value) conf.errorMax = if value == 0: high(int) else: value + of "stdinfile": + expectArg(conf, switch, arg, pass, info) + conf.stdinFile = if os.isAbsolute(arg): AbsoluteFile(arg) + else: AbsoluteFile(getCurrentDir() / arg) of "verbosity": expectArg(conf, switch, arg, pass, info) let verbosity = parseInt(arg) diff --git a/compiler/options.nim b/compiler/options.nim index af2334a39d..f9c9f9a8be 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -404,6 +404,7 @@ type projectPath*: AbsoluteDir # holds a path like /home/alice/projects/nim/compiler/ projectFull*: AbsoluteFile # projectPath/projectName projectIsStdin*: bool # whether we're compiling from stdin + stdinFile*: AbsoluteFile # Filename to use in messages for stdin lastMsgWasDot*: set[StdOrrKind] # the last compiler message was a single '.' projectMainIdx*: FileIndex # the canonical path id of the main module projectMainIdx2*: FileIndex # consider merging with projectMainIdx @@ -580,6 +581,7 @@ proc newConfigRef*(): ConfigRef = projectPath: AbsoluteDir"", # holds a path like /home/alice/projects/nim/compiler/ projectFull: AbsoluteFile"", # projectPath/projectName projectIsStdin: false, # whether we're compiling from stdin + stdinFile: AbsoluteFile"stdinfile", projectMainIdx: FileIndex(0'i32), # the canonical path id of the main module command: "", # the main command (e.g. cc, check, scan, etc) commandArgs: @[], # any arguments after the main command diff --git a/compiler/pipelines.nim b/compiler/pipelines.nim index 5fddb046f0..94268c4cae 100644 --- a/compiler/pipelines.nim +++ b/compiler/pipelines.nim @@ -234,7 +234,8 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF result = moduleFromRodFile(graph, fileIdx, cachedModules) let path = toFullPath(graph.config, fileIdx) let filename = AbsoluteFile path - if fileExists(filename): # it could be a stdinfile + # it could be a stdinfile/cmdfile + if fileExists(filename) and not graph.config.projectIsStdin: graph.cachedFiles[path] = $secureHashFile(path) if result == nil: result = newModule(graph, fileIdx) diff --git a/tests/tools/tloadstdin.nim b/tests/tools/tloadstdin.nim new file mode 100644 index 0000000000..27e62b4da4 --- /dev/null +++ b/tests/tools/tloadstdin.nim @@ -0,0 +1,16 @@ +discard """ + action: "compile" + cmd: "cat $file | $nim check --stdinfile:$file -" + # Don't believe cat and pipes works on windows + disabled: "win" +""" + +import std/[assertions, paths] + +# Test the nimscript config is loaded +assert defined(nimscriptConfigLoaded) + +assert currentSourcePath() == $(getCurrentDir()/Path"tloadstdin.nim") + +{.warning: "Hello".} #[tt.Warning + ^ Hello]# diff --git a/tests/tools/tloadstdin.nims b/tests/tools/tloadstdin.nims new file mode 100644 index 0000000000..58b9142ca0 --- /dev/null +++ b/tests/tools/tloadstdin.nims @@ -0,0 +1 @@ +--d:nimscriptConfigLoaded From 334f96c05a92985ad5ab737a92c5f0db1330b061 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 12 Apr 2025 18:53:18 +0300 Subject: [PATCH 072/119] isolate and rematch generic converters to get bindings (#24867) fixes #4554, fixes #10900, fixes #13843, fixes #19471, fixes #19517 Instead of matching generic converters to their arguments using the full call match bindings, a new match is created for them (from which the bindings are used to instantiate the converter return type). Then when instantiating generic converters, they are matched to their argument again to get their bindings again instead of using the call bindings. This prevents generic converters which match more than once from interfering with each other's bindings. --- compiler/semcall.nim | 7 +++- compiler/sigmatch.nim | 5 ++- .../converter/tgenericconverterbindings1.nim | 37 +++++++++++++++++ .../converter/tgenericconverterbindings2.nim | 10 +++++ .../converter/tgenericconverterbindings3.nim | 38 +++++++++++++++++ .../converter/tgenericconverterbindings4.nim | 19 +++++++++ .../converter/tgenericconverterbindings5.nim | 41 +++++++++++++++++++ .../converter/tgenericconverterbindings6.nim | 36 ++++++++++++++++ 8 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 tests/converter/tgenericconverterbindings1.nim create mode 100644 tests/converter/tgenericconverterbindings2.nim create mode 100644 tests/converter/tgenericconverterbindings3.nim create mode 100644 tests/converter/tgenericconverterbindings4.nim create mode 100644 tests/converter/tgenericconverterbindings5.nim create mode 100644 tests/converter/tgenericconverterbindings6.nim diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 1ffe5aed4a..90376214db 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -686,7 +686,12 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = if a.kind == nkHiddenCallConv and a[0].kind == nkSym: let s = a[0].sym if s.isGenericRoutineStrict: - let finalCallee = generateInstance(c, s, x.bindings, a.info) + var src = s.typ.firstParamType + var convMatch = newCandidate(c, src) + let srca = typeRel(convMatch, src, a[1].typ) + if srca notin {isEqual, isGeneric, isSubtype}: + internalError(c.config, a.info, "generic converter failed rematch") + let finalCallee = generateInstance(c, s, convMatch.bindings, a.info) a[0].sym = finalCallee a[0].typ() = finalCallee.typ #a.typ = finalCallee.typ.returnType diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 0393d8ec65..e486f3a47f 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2296,7 +2296,8 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, # for generic type converters we need to check 'src <- a' before # 'f <- dest' in order to not break the unification: # see tests/tgenericconverter: - let srca = typeRel(m, src, a) + var convMatch = newCandidate(c, src) + let srca = typeRel(convMatch, src, a) if srca notin {isEqual, isGeneric, isSubtype}: continue # What's done below matches the logic in ``matchesAux`` @@ -2308,7 +2309,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, let destIsGeneric = containsGenericType(dest) if destIsGeneric: - dest = generateTypeInstance(c, m.bindings, arg, dest) + dest = generateTypeInstance(c, convMatch.bindings, arg, dest) let fdest = typeRel(m, f, dest) if fdest in {isEqual, isGeneric} and not (dest.kind == tyLent and f.kind in {tyVar}): # can't fully mark used yet, may not be used in final call diff --git a/tests/converter/tgenericconverterbindings1.nim b/tests/converter/tgenericconverterbindings1.nim new file mode 100644 index 0000000000..04d397bde0 --- /dev/null +++ b/tests/converter/tgenericconverterbindings1.nim @@ -0,0 +1,37 @@ +discard """ + output: ''' +p 1 20 +p 1000 200 +p 1 1 +p 1000 1000 +p 1 1000 +p 1000 1 +p 1 200 +p 1000 20 +''' +""" + +# issue #4554 + +type + G[N:static[int]] = object + v: int + F[N:static[int]] = object + v: int + +converter G2int[N:static[int]](x:G[N]):int = x.v +converter F2int[N:static[int]](x:F[N]):int = x.v +proc p(x,y:int) = echo "p ",x," ",y +var + g1 = G[1](v:1) + g2 = G[2](v:20) + f1 = F[1](v:1000) + f2 = F[2](v:200) +p(g1,g2) # Error: type mismatch: got (G[1], G[2]) +p(f1,f2) # Error: type mismatch: got (F[1], F[2]) +p(g1,g1) # compiles +p(f1,f1) # compiles +p(g1,f1) # compiles +p(f1,g1) # compiles +p(g1,f2) # compiles +p(f1,g2) # compiles diff --git a/tests/converter/tgenericconverterbindings2.nim b/tests/converter/tgenericconverterbindings2.nim new file mode 100644 index 0000000000..b2d9ba3d14 --- /dev/null +++ b/tests/converter/tgenericconverterbindings2.nim @@ -0,0 +1,10 @@ +# issue #4554 comment + +type Obj[T] = object + b: T + +converter test1[T](a: Obj[T]): T = a.b + +proc doStuff(a: int, b: float) = discard + +doStuff(Obj[int](b: 1), Obj[float](b: 1.2)) # Error: type mismatch: got diff --git a/tests/converter/tgenericconverterbindings3.nim b/tests/converter/tgenericconverterbindings3.nim new file mode 100644 index 0000000000..df37c9dd08 --- /dev/null +++ b/tests/converter/tgenericconverterbindings3.nim @@ -0,0 +1,38 @@ +# issue #10900 + +import std/options + +type + AllTypesInModule = + bool | string | seq[int] + +converter toOptional[T: AllTypesInModule](x: T): Option[T] = + some(x) + +proc foo( + a: Option[bool] = none[bool](), + b: Option[string] = none[string](), + c: Option[seq[int]] = none[seq[int]]()) = + discard + +# works: +foo(a = true) +foo(true) +foo(b = "asdf") +foo(c = @[1, 2, 3]) + +# fails: +foo( + a = true, + b = "asdf") +foo(true, "asdf") +foo( + a = true, + c = @[1, 2, 3]) +foo( + b = "asdf", + c = @[1, 2, 3]) +foo( + a = true, + b = "asdf", + c = @[1, 2, 3]) diff --git a/tests/converter/tgenericconverterbindings4.nim b/tests/converter/tgenericconverterbindings4.nim new file mode 100644 index 0000000000..abf210b062 --- /dev/null +++ b/tests/converter/tgenericconverterbindings4.nim @@ -0,0 +1,19 @@ +# issue #13843 + +type + IdLayer {.pure, size: int.sizeof.} = enum + Core + Ui + IdScene {.pure, size: int.sizeof.} = enum + Game + Shop + SomeIds = IdLayer|IdScene + +converter toint*(x: SomeIds): int = x.int + +var IdGame : int = IdScene.Game #works + +proc bind_scene(a, b: int) = discard +bind_scene(Core,Game) # doesn't work, type mismatch for Game, doesnt convert to int +bind_scene(Core,IdScene.Game) # doesn't work, type mismatch for Game, doesnt convert to int +bind_scene(Core,IdGame) # works diff --git a/tests/converter/tgenericconverterbindings5.nim b/tests/converter/tgenericconverterbindings5.nim new file mode 100644 index 0000000000..4298a17787 --- /dev/null +++ b/tests/converter/tgenericconverterbindings5.nim @@ -0,0 +1,41 @@ +discard """ + output: ''' +Converting (int, int) to A +Converting (int, int) to A +Checked: A +Checked: A +Checked: A +Converting (A, A) to A +Converting (int, int) to A +Checked: A +Checked: A +Checked: A +Converting (A, A) to A +Converting (A, A) to A +Checked: A +Checked: A +Checked: A +''' +""" + +# issue #19471 + +type A = ref object + +converter toA(x: tuple): A = + echo "Converting ", x.type, " to A" + A() + +proc check(a: A) = + echo "Checked: ", a.type + +proc mux(a: A, b: A, c: A) = + check(a) + check(b) + check(c) + +let a = A() + +mux(a, (0, 0), (1, 1)) # both tuples are (int, int) +mux(a, (a, a), (1, 1)) # one tuple is (A, A), another (int, int) +mux(a, (a, a), (a, a)) # both tuples are (A, A) diff --git a/tests/converter/tgenericconverterbindings6.nim b/tests/converter/tgenericconverterbindings6.nim new file mode 100644 index 0000000000..4ae0069aa1 --- /dev/null +++ b/tests/converter/tgenericconverterbindings6.nim @@ -0,0 +1,36 @@ +discard """ + output: ''' +int | int +int | string +int | string +''' +""" + +# issue #19517 + +type thing [T] = object + value: T + +converter asValue[T](o: thing[T]): T = + o.value + +proc mycall(num, num2: int) = + echo ($(num.type) & " | " & $(num2.type)) + +proc mycall(num: int, str: string) = + echo ($(num.type) & " | " & $(str.type)) + +mycall( # This call uses asValue[int] converter automatically fine + thing[int](value: 1), + thing[int](value: 42), +) + +mycall( # This gives a type error as if the converter was not defined and I tried to pass in a thing directly + thing[int](value: 2), + thing[string](value: "foo"), +) + +mycall( # This can be fixed by calling the converter explicitly for everything but the first use + thing[int](value: 2), + thing[string](value: "foo").asValue, +) From 1ef9a656d25f71dec6066e68ce6e9a518d5e9f16 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 12 Apr 2025 18:55:11 +0300 Subject: [PATCH 073/119] allow setting arbitrary size for importc types (#24868) split from #24204, closes #7674 The `{.size.}` pragma no longer restricts the given size to 1, 2, 4 or 8 if it is used for an imported type. This is not tested very thoroughly but there's no obvious reason to disallow it. --- compiler/pragmas.nim | 20 ++++++++++++-------- compiler/types.nim | 11 +++++++++++ doc/manual.md | 4 ++-- tests/c/timportedsize.nim | 10 ++++++++++ 4 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 tests/c/timportedsize.nim diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 51e044ce0b..8cf547c9be 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -947,15 +947,19 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wSize: if sym.typ == nil: invalidPragma(c, it) var size = expectIntLit(c, it) - case size - of 1, 2, 4: - sym.typ.size = size - sym.typ.align = int16 size - of 8: - sym.typ.size = 8 - sym.typ.align = floatInt64Align(c.config) + if sfImportc in sym.flags: + # no restrictions on size for imported types + setImportedTypeSize(c.config, sym.typ, size) else: - localError(c.config, it.info, "size may only be 1, 2, 4 or 8") + case size + of 1, 2, 4: + sym.typ.size = size + sym.typ.align = int16 size + of 8: + sym.typ.size = 8 + sym.typ.align = floatInt64Align(c.config) + else: + localError(c.config, it.info, "size may only be 1, 2, 4 or 8") of wAlign: let alignment = expectIntLit(c, it) if isPowerOfTwo(alignment) and alignment > 0: diff --git a/compiler/types.nim b/compiler/types.nim index 9853cf1222..914f57fc8e 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1515,6 +1515,17 @@ proc getSize*(conf: ConfigRef; typ: PType): BiggestInt = computeSizeAlign(conf, typ) result = typ.size +proc setImportedTypeSize*(conf: ConfigRef, t: PType, size: int) = + t.size = size + if tfPacked in t.flags or size <= 1: + t.align = 1 + elif size <= 2: + t.align = 2 + elif size <= 4: + t.align = 4 + else: + t.align = floatInt64Align(conf) + proc isConcept*(t: PType): bool= case t.kind of tyConcept: true diff --git a/doc/manual.md b/doc/manual.md index 8eab6683d5..9abd0e762c 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -7815,6 +7815,8 @@ The `size pragma` allows specifying the size of the enum type. doAssert sizeof(EventType) == sizeof(uint32) ``` +When used for enum types, the `size pragma` accepts only the values 1, 2, 4 or 8. + The `size pragma` can also specify the size of an `importc` incomplete object type so that one can get the size of it at compile time even if it was declared without fields. @@ -7827,8 +7829,6 @@ so that one can get the size of it at compile time even if it was declared witho echo sizeof(AtomicFlag) ``` -The `size pragma` accepts only the values 1, 2, 4 or 8. - Align pragma ------------ diff --git a/tests/c/timportedsize.nim b/tests/c/timportedsize.nim new file mode 100644 index 0000000000..4541ac51d3 --- /dev/null +++ b/tests/c/timportedsize.nim @@ -0,0 +1,10 @@ +{.emit: """ +typedef struct Foo { + NI64 a; + NI64 b; +} Foo; +""".} + +type Foo {.importc: "Foo", size: 16.} = object + +var x: Foo From 4d9e5e8b6d15107c3de5e7fc2b1c974437ef1cab Mon Sep 17 00:00:00 2001 From: metagn Date: Sun, 13 Apr 2025 20:21:33 +0300 Subject: [PATCH 074/119] fix field setter fallback that never worked (#24871) refs https://forum.nim-lang.org/t/12785, refs #4711 The code was already there that when `propertyWriteAccess` returns `nil` (i.e. cannot find a setter), `semAsgn` turns the [LHS into a call and semchecks it](https://github.com/nim-lang/Nim/blob/1ef9a656d25f71dec6066e68ce6e9a518d5e9f16/compiler/semexprs.nim#L1941-L1948), meaning if a setter cannot be found a getter will be assigned to instead. However `propertyWriteAccess` never returned nil, because `semOverloadedCallAnalyseEffects` was not called with `efNoUndeclared` and so produced an error directly. So `efNoUndeclared` is passed to this call so this code works as intended. This fixes the issue described in #4711 which was closed because subscripts do not have the same behavior implemented. However we can implement this for subscripts as well (I have an implementation ready), it just changes the error message from the failed overloads of `[]=` to the failed overloads of `[]` for the LHS, which might be misleading but is consistent with the error messages for any other assignment. I can do this in this PR or another one. --- compiler/semexprs.nim | 2 +- tests/specialops/terrmsgs.nim | 3 +-- tests/specialops/tmismatch.nim | 2 +- tests/specialops/tsetterfallback1.nim | 24 ++++++++++++++++++++++++ tests/specialops/tsetterfallback2.nim | 8 ++++++++ 5 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 tests/specialops/tsetterfallback1.nim create mode 100644 tests/specialops/tsetterfallback2.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 1dc952be51..55a58c7f04 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1787,7 +1787,7 @@ proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = result = newTreeI(nkCall, n.info, setterId, a[0], n[1]) result.flags.incl nfDotSetter let orig = newTreeI(nkCall, n.info, setterId, aOrig[0], nOrig[1]) - result = semOverloadedCallAnalyseEffects(c, result, orig, {}) + result = semOverloadedCallAnalyseEffects(c, result, orig, {efNoUndeclared}) if result != nil: result = afterCallActions(c, result, nOrig, {}) diff --git a/tests/specialops/terrmsgs.nim b/tests/specialops/terrmsgs.nim index 081bca4510..534c0c4543 100644 --- a/tests/specialops/terrmsgs.nim +++ b/tests/specialops/terrmsgs.nim @@ -26,8 +26,7 @@ block: block: template `.=`(a: Foo, b: untyped, c: untyped) = b = c b.x = 123 #[tt.Error - ^ undeclared field: 'x=' for type terrmsgs.Bar [type declared in terrmsgs.nim(15, 8)]]# - # yeah it says x= but does it matter in practice + ^ undeclared field: 'x' for type terrmsgs.Bar [type declared in terrmsgs.nim(15, 8)]]# block: template `()`(a: Foo, b: untyped, c: untyped) = echo "something" diff --git a/tests/specialops/tmismatch.nim b/tests/specialops/tmismatch.nim index 76c921b14a..7d0a4229dc 100644 --- a/tests/specialops/tmismatch.nim +++ b/tests/specialops/tmismatch.nim @@ -14,4 +14,4 @@ template `.=`*(flags: Flags, key: Flag, val: bool) = var flags: Flags flags.A = 123 #[tt.Error - ^ undeclared field: 'A=' for type tmismatch.Flags [type declared in tmismatch.nim(9, 5)]]# + ^ undeclared field: 'A' for type tmismatch.Flags [type declared in tmismatch.nim(9, 5)]]# diff --git a/tests/specialops/tsetterfallback1.nim b/tests/specialops/tsetterfallback1.nim new file mode 100644 index 0000000000..6a0b1104e3 --- /dev/null +++ b/tests/specialops/tsetterfallback1.nim @@ -0,0 +1,24 @@ +# issue #4711 + +type + Vec4 = object + x,y,z,w : float32 + + Vec3 = object + x,y,z : float32 + +proc `+=`(v0: var Vec3; v1: Vec3) = + v0.x += v1.x + v0.y += v1.y + v0.z += v1.z + +proc xyz(v: var Vec4): var Vec3 = + cast[ptr Vec3](v.x.addr)[] + +let tmp = Vec3(x: 1, y:2, z:3) +var dst = Vec4(x: 4, y:4, z:4, w:4) + +xyz(dst) = tmp # works +dst.xyz() = tmp # works +dst.xyz += tmp # works +dst.xyz = tmp # attempting to call undeclared routine `xyz=` diff --git a/tests/specialops/tsetterfallback2.nim b/tests/specialops/tsetterfallback2.nim new file mode 100644 index 0000000000..8458b2dc8b --- /dev/null +++ b/tests/specialops/tsetterfallback2.nim @@ -0,0 +1,8 @@ +# https://forum.nim-lang.org/t/12785 + +proc x(pt: var array[2, float]): var float = pt[0] + +var pt = [0.0, 0.0] +pt.x += 1.0 # <-- fine +x(pt) = 1.0 # <-- fine +pt.x = 1.0 # <-- does not compile From c06bb6cc03f1a42d515949967c0c9f267e971d04 Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 15 Apr 2025 20:29:46 +0300 Subject: [PATCH 075/119] don't traverse inner procs to lift locals in closure iters (#24876) fixes #24863, refs #23787 and #24316 Working off the minimized example, my understanding of the issue is: `n` captures `r` as `:envP.r1` where `:envP` is the environment of `b`, then `proc () = n()` does the lambda lifting of `n` again (which isn't done if the `proc ()` is marked `{.closure.}`, hence the workaround) which then captures the `:envP` as another field inside the `:envP`, so it generates `:envP.:envP_2.r1` but the `.:envP_2` field is `nil`, so it causes a segfault. The problem is that the capture of `r` in `n` is done inside `detectCapturedVars` for the surrounding closure iterator: inner procs are not special cased and traversed as regular nodes, so it thinks it's inside the iterator and generates a field access of `:envP` freely. The lambda lifting version of `detectCapturedVars` ignores inner procs and works off of symbol uses (anonymous iterator and lambda declarations pretend their symbol is used). As a naive solution, closure iterators now also ignore inner proc declarations same as `lambdalifting.detectCapturedVars`, but unlike it they also don't do anything for the inner proc symbols. Lambdalifting seems to properly handle the lifted variables but in the worst case we can also make sure `closureiters.detectCapturedVars` traverses inner procs by marking every local of the closure iter used in them as needing lifting (but not doing the lifting). This does not seem necessary for now so it's not done (was done and reverted in [this commit](https://github.com/nim-lang/Nim/pull/24876/commits/9bb39a9259ecf7d93c64a096138f8a2d108333d5)), but regressions are still possible --- compiler/closureiters.nim | 14 ++++++++++++++ compiler/lambdalifting.nim | 2 +- tests/iter/t24863.nim | 30 ++++++++++++++++++++++++++++++ tests/iter/tnestedclosures.nim | 20 ++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/iter/t24863.nim diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index 7e0f54b12e..835cbf0ca7 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -1451,6 +1451,14 @@ proc detectCapturedVars(c: var Ctx, n: PNode, stateIdx: int) = detectCapturedVars(c, n[0][1], stateIdx) else: detectCapturedVars(c, n[0], stateIdx) + of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, + nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef, + nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt, + nkTypeOfExpr, nkMixinStmt, nkBindStmt: + discard + of nkLambdaKinds, nkIteratorDef: + if n.typ != nil: + detectCapturedVars(c, n[namePos], stateIdx) else: for i in 0 ..< n.safeLen: detectCapturedVars(c, n[i], stateIdx) @@ -1481,6 +1489,12 @@ proc liftLocals(c: var Ctx, n: PNode): PNode = n[0][1] = liftLocals(c, n[0][1]) else: n[0] = liftLocals(c, n[0]) + of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, + nkTemplateDef, nkTypeSection, nkProcDef, nkMethodDef, + nkConverterDef, nkMacroDef, nkFuncDef, nkCommentStmt, + nkTypeOfExpr, nkMixinStmt, nkBindStmt, + nkLambdaKinds, nkIteratorDef: + discard else: for i in 0 ..< n.safeLen: n[i] = liftLocals(c, n[i]) diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 640bb4b2f8..c8c5acf974 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -199,7 +199,7 @@ proc interestingVar(s: PSym): bool {.inline.} = proc illegalCapture(s: PSym): bool {.inline.} = result = classifyViewType(s.typ) != noView or s.kind == skResult -proc isInnerProc(s: PSym): bool = +proc isInnerProc*(s: PSym): bool = if s.kind in {skProc, skFunc, skMethod, skConverter, skIterator} and s.magic == mNone: result = s.skipGenericOwner.kind in routineKinds else: diff --git a/tests/iter/t24863.nim b/tests/iter/t24863.nim new file mode 100644 index 0000000000..96ddbcef7c --- /dev/null +++ b/tests/iter/t24863.nim @@ -0,0 +1,30 @@ +# issue #24863 + +type M = object + p: iterator(): M {.gcsafe.} + +template h(f: M): int = + yield f + 456 + +proc s(): M = + iterator g(): M {.closure.} = discard + let v = M(p: g) + doAssert(not isNil(v.p)) + discard v.p() + v + +proc c(): M = + iterator b(): M {.closure.} = + let r = h(s()) + doAssert r == 456 + proc n(): M = + iterator y(): M {.closure.} = + let _ = r + let _ = y + let _ = proc () = discard n() + let j = M(p: b) + doAssert(not isNil(j.p)) + discard j.p() + +let _ = c() diff --git a/tests/iter/tnestedclosures.nim b/tests/iter/tnestedclosures.nim index e23fa1355f..f2dc7a51d4 100644 --- a/tests/iter/tnestedclosures.nim +++ b/tests/iter/tnestedclosures.nim @@ -25,6 +25,9 @@ Test 7: 0 1 2 +Test 8: +123 +456 ''' """ @@ -156,3 +159,20 @@ block: # issue #12487 doAssert s == @["something"] main() + +block: # minimized issue #24863 + echo "Test 8:" + proc c() = + iterator b(): int {.closure.} = + let r = 456 + yield 123 + proc n() = + echo r + let a = proc () = n() + a() + + let j = b + echo j() + discard j() + + c() From e7f73bfebee41c597f5e37b5e635e413944324b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20M=20G=C3=B3mez?= Date: Wed, 16 Apr 2025 11:11:33 +0100 Subject: [PATCH 076/119] Fixes a nimsuggest crash (#24873) --- compiler/vmgen.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 4afe01a7e3..e8612000a3 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1885,6 +1885,10 @@ proc genCheckedObjAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = c.freeTemp(objR) proc genArrAccess(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags) = + if n[0].typ == nil: + globalError(c.config, n.info, "cannot access array with nil type") + return + let arrayType = n[0].typ.skipTypes(abstractVarRange-{tyTypeDesc}).kind case arrayType of tyString, tyCstring: From 11e4bd668cdc6e1ee1f99839515afcd2c0835992 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 16 Apr 2025 15:17:26 +0200 Subject: [PATCH 077/119] update the tooling versions (#24878) --- koch.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/koch.nim b/koch.nim index 12aaff9c0a..457ab5e832 100644 --- a/koch.nim +++ b/koch.nim @@ -11,13 +11,13 @@ const # examples of possible values for repos: Head, ea82b54 - NimbleStableCommit = "123f97a5e4ee9ba35720c0869e19a047c43c797e" # 0.16.4 - AtlasStableCommit = "5faec3e9a33afe99a7d22377dd1b45a5391f5504" - ChecksumsStableCommit = "bd9bf4eaea124bf8d01e08f92ac1b14c6879d8d3" + NimbleStableCommit = "b1dc28450f028aead0b7cf5da8adf2267db65f89" # 0.18.2 + AtlasStableCommit = "dd9961b1f8da8d1e8759860bc24c1bf3b1df423e" # 0.9 + ChecksumsStableCommit = "f8f6bd34bfa3fe12c64b919059ad856a96efcba0" # 2.0.1 SatStableCommit = "faf1617f44d7632ee9601ebc13887644925dcc01" # examples of possible values for fusion: #head, #ea82b54, 1.2.3 - FusionStableHash = "#372ee4313827ef9f2ea388840f7d6b46c2b1b014" + FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734" HeadHash = "#head" when not defined(windows): const From 3f9c269013298003aaeef3a83682cd98b4b5356d Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 17 Apr 2025 01:44:53 +0800 Subject: [PATCH 078/119] fixes nimsugget with Checksums deps (#24882) ref https://github.com/nim-lang/Nim/issues/24881 --- koch.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/koch.nim b/koch.nim index 457ab5e832..9d15ff18bd 100644 --- a/koch.nim +++ b/koch.nim @@ -176,7 +176,12 @@ proc bundleAtlasExe(latest: bool, args: string) = nimCompile("dist/atlas/src/atlas.nim", options = "-d:release --noNimblePath -d:nimAtlasBootstrap " & args) +proc bundleChecksums(latest: bool) = + let commit = if latest: "HEAD" else: ChecksumsStableCommit + cloneDependency(distDir, "https://github.com/nim-lang/checksums.git", commit, allowBundled = true) + proc bundleNimsuggest(args: string) = + bundleChecksums(false) nimCompileFold("Compile nimsuggest", "nimsuggest/nimsuggest.nim", options = "-d:danger " & args) @@ -205,10 +210,6 @@ proc bundleWinTools(args: string) = nimCompile(r"tools\downloader.nim", options = r"--cc:vcc --app:gui -d:ssl --noNimblePath --path:..\ui " & args) -proc bundleChecksums(latest: bool) = - let commit = if latest: "HEAD" else: ChecksumsStableCommit - cloneDependency(distDir, "https://github.com/nim-lang/checksums.git", commit, allowBundled = true) - proc zip(latest: bool; args: string) = bundleChecksums(latest) bundleNimbleExe(latest, args) From 9f359e8d6d51a9742c8e3a5816a2a8de8497f4c7 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 17 Apr 2025 04:51:12 +0800 Subject: [PATCH 079/119] fixes #24879; Data getting wiped on copy with iterators and =copy on refc (#24880) fixes #24879 --- compiler/liftdestructors.nim | 6 +++++- tests/arc/t19457.nim | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index e6b2979dbd..a9eb0263e9 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -1003,9 +1003,13 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) = # 'selectedGC' here to determine if we have the new runtime. discard considerUserDefinedOp(c, t, body, x, y) elif tfHasAsgn in t.flags: + # seqs with elements using custom hooks in refc if c.kind in {attachedAsgn, attachedSink, attachedDeepCopy}: body.add newSeqCall(c, x, y) - forallElements(c, t, body, x, y) + if c.kind == attachedWasMoved: + body.add genBuiltin(c, mWasMoved, "wasMoved", x) + else: + forallElements(c, t, body, x, y) else: defaultOp(c, t, body, x, y) of tyString: diff --git a/tests/arc/t19457.nim b/tests/arc/t19457.nim index 78447ce82a..05e2ae732b 100644 --- a/tests/arc/t19457.nim +++ b/tests/arc/t19457.nim @@ -1,5 +1,5 @@ discard """ - matrix: "--gc:refc; --gc:arc" + matrix: "--mm:refc; --mm:arc" """ # bug #19457 @@ -13,4 +13,36 @@ proc gcd(x, y: seq[int]): seq[int] = b = c return a -doAssert gcd(@[1], @[2]) == @[1] \ No newline at end of file +doAssert gcd(@[1], @[2]) == @[1] + + + +import std/sequtils + +type IrrelevantType* = object + +proc `=copy`*(dest: var IrrelevantType, src: IrrelevantType) = + discard + +type + Inner* = object + value*: string + someField*: IrrelevantType + + Outer* = object + inner*: Inner + +iterator valueIt(self: Outer): Inner = + yield self.inner + +proc getValues*(self: var Outer): seq[Inner] = + var peers = self.valueIt().toSeq + return peers + +var outer = Outer() + +outer.inner = Inner(value: "hello, world") + +doAssert (outer.valueIt().toSeq)[0].value == "hello, world" # Passes +doAssert outer.inner.value == "hello, world" # Passes too, original value is doing fine +doAssert outer.getValues()[0].value == "hello, world" # Fails, value is empty From 3d14381473fd478432cb8fab04d0501b26db775b Mon Sep 17 00:00:00 2001 From: metagn Date: Thu, 17 Apr 2025 00:44:31 +0300 Subject: [PATCH 080/119] fix stmtlist expression indent regression (#24883) follows up #24855 Before #24855, the test would work because the indentation of the `;` token would be passed to `semiStmtList` and so its indentation of `-1` would be used. Now the `;` token is skipped and the indentation of the first `discard` is used which is > -1. However the second discard has an indentation of -1 because it's on the same line: this fails the `sameInd(p) or realInd(p)` check since -1 is never >= the indent of the first discard. For compatibility with the parser up to this point this indent check is entirely removed, meaning the indent is ignored. Because the `;` is basically never on a separate line, this was already the case for basically every use. `semiStmtList` is wrapped in a `withInd` anyway which resets the indent after it's done, since the entire statement list is wrapped in a `()`. To disallow dedents, the above check could be fixed to use `sameOrNoInd` instead of `sameInd`, which is done in the commented version of this check. --- compiler/parser.nim | 5 +++-- tests/parser/tstmtlistexprindent.nim | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 tests/parser/tstmtlistexprindent.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index 7f438f4208..03c3ac2648 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -638,8 +638,9 @@ proc semiStmtList(p: var Parser, result: PNode) = getTok(p) if p.tok.tokType == tkParRi: break - elif not (sameInd(p) or realInd(p)): - parMessage(p, errInvalidIndentation) + # ignore indent: + #elif not (sameOrNoInd(p) or realInd(p)): + # parMessage(p, errInvalidIndentation) let a = complexOrSimpleStmt(p) if a.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) diff --git a/tests/parser/tstmtlistexprindent.nim b/tests/parser/tstmtlistexprindent.nim new file mode 100644 index 0000000000..5c6c25151c --- /dev/null +++ b/tests/parser/tstmtlistexprindent.nim @@ -0,0 +1,7 @@ +type E = enum A, B, C +proc junk(e: E) = + case e + of A: (echo "a"; + discard; discard; + discard) + else: discard From af9219ada72078c4cbc294168374a373d2b3c8e3 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 18 Apr 2025 00:04:03 +0800 Subject: [PATCH 081/119] fixes #24881; build_all.sh koch tools fails to build atlas (#24884) fixes #24881 To test: `nim c koch.nim` + delete the `dist` directory --- koch.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/koch.nim b/koch.nim index 9d15ff18bd..0703ea61b0 100644 --- a/koch.nim +++ b/koch.nim @@ -12,7 +12,7 @@ const # examples of possible values for repos: Head, ea82b54 NimbleStableCommit = "b1dc28450f028aead0b7cf5da8adf2267db65f89" # 0.18.2 - AtlasStableCommit = "dd9961b1f8da8d1e8759860bc24c1bf3b1df423e" # 0.9 + AtlasStableCommit = "26cecf4d0cc038d5422fc1aa737eec9c8803a82b" # 0.9 ChecksumsStableCommit = "f8f6bd34bfa3fe12c64b919059ad856a96efcba0" # 2.0.1 SatStableCommit = "faf1617f44d7632ee9601ebc13887644925dcc01" From 5aaba213d426e67a0761c6dcb7a37d8663026d92 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 18 Apr 2025 06:32:49 +0300 Subject: [PATCH 082/119] account for invalid data in enum `$` on arc/orc (#24886) closes #24875 Refc gives `0 (invalid data!)`, but since enum `$` procs on arc are generated during enum declarations we might not have access to string concatenation and integer `$`, so it generates a static string. Just chose an empty string for this. --- compiler/enumtostr.nim | 4 ++++ tests/arc/tinvalidenumtostr.nim | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/arc/tinvalidenumtostr.nim diff --git a/compiler/enumtostr.nim b/compiler/enumtostr.nim index dc516d2e52..2223be2ffb 100644 --- a/compiler/enumtostr.nim +++ b/compiler/enumtostr.nim @@ -33,6 +33,10 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener caseStmt.add newTree(nkOfBranch, newIntTypeNode(field.position, t), newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), newStrNode(val, info)))) #newIntTypeNode(nkIntLit, field.position, t) + # safety branch for invalid data: + caseStmt.add newTree(nkElse, + newTree(nkStmtList, newTree(nkFastAsgn, newSymNode(res), + newStrNode("", info)))) body.add(caseStmt) diff --git a/tests/arc/tinvalidenumtostr.nim b/tests/arc/tinvalidenumtostr.nim new file mode 100644 index 0000000000..b053e30993 --- /dev/null +++ b/tests/arc/tinvalidenumtostr.nim @@ -0,0 +1,9 @@ +# issue #24875 + +type + MyEnum = enum + One = 1 + +var x = cast[MyEnum](0) +let s = $x +doAssert s == "" From 032da90ed1eda03b837145d711b756cb897c099e Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 18 Apr 2025 06:34:21 +0300 Subject: [PATCH 083/119] implement parser for new case objects (#24885) refs https://github.com/nim-lang/RFCs/issues/559 Parses as an `nkIdentDefs` with an `nkEmpty` name. Pragma is allowed, can remove this if necessary. Fine to close and postpone for later --- compiler/parser.nim | 22 ++++++-- doc/grammar.txt | 2 +- tests/parser/tparsenewcaseobject.nim | 81 ++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 tests/parser/tparsenewcaseobject.nim diff --git a/compiler/parser.nim b/compiler/parser.nim index 03c3ac2648..4af56f2103 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -2110,12 +2110,28 @@ proc parseObjectCase(p: var Parser): PNode = #| objectBranches = objectBranch (IND{=} objectBranch)* #| (IND{=} 'elif' expr colcom objectPart)* #| (IND{=} 'else' colcom objectPart)? - #| objectCase = 'case' declColonEquals ':'? COMMENT? + #| objectCase = 'case' (declColonEquals / pragma)? ':'? COMMENT? #| (IND{>} objectBranches DED #| | IND{=} objectBranches) result = newNodeP(nkRecCase, p) - getTokNoInd(p) - var a = parseIdentColonEquals(p, {withPragma}) + getTok(p) + if p.tok.tokType != tkOf: + # of case will be handled later + if p.tok.indent >= 0: parMessage(p, errInvalidIndentation) + var a: PNode + if p.tok.tokType in {tkSymbol, tkAccent}: + a = parseIdentColonEquals(p, {withPragma}) + else: + a = newNodeP(nkIdentDefs, p) + if p.tok.tokType == tkCurlyDotLe: + var prag = newNodeP(nkPragmaExpr, p) + prag.add(p.emptyNode) + prag.add(parsePragma(p)) + a.add(prag) + else: + a.add(p.emptyNode) + a.add(p.emptyNode) + a.add(p.emptyNode) result.add(a) if p.tok.tokType == tkColon: getTok(p) flexComment(p, result) diff --git a/doc/grammar.txt b/doc/grammar.txt index 51b3e0053c..7d430019b1 100644 --- a/doc/grammar.txt +++ b/doc/grammar.txt @@ -181,7 +181,7 @@ objectBranch = 'of' exprList colcom objectPart objectBranches = objectBranch (IND{=} objectBranch)* (IND{=} 'elif' expr colcom objectPart)* (IND{=} 'else' colcom objectPart)? -objectCase = 'case' declColonEquals ':'? COMMENT? +objectCase = 'case' (declColonEquals / pragma)? ':'? COMMENT? (IND{>} objectBranches DED | IND{=} objectBranches) objectPart = IND{>} objectPart^+IND{=} DED diff --git a/tests/parser/tparsenewcaseobject.nim b/tests/parser/tparsenewcaseobject.nim new file mode 100644 index 0000000000..884b85dff7 --- /dev/null +++ b/tests/parser/tparsenewcaseobject.nim @@ -0,0 +1,81 @@ +discard """ + nimout: ''' +StmtList + TypeSection + TypeDef + Ident "Node" + Empty + RefTy + ObjectTy + Empty + Empty + RecList + RecCase + IdentDefs + Empty + Empty + Empty + OfBranch + Ident "AddOpr" + Ident "SubOpr" + Ident "MulOpr" + Ident "DivOpr" + RecList + IdentDefs + Ident "a" + Ident "b" + Ident "Node" + Empty + OfBranch + Ident "Value" + RecList + NilLit + IdentDefs + Ident "info" + Ident "LineInfo" + Empty + RecCase + IdentDefs + PragmaExpr + Empty + Pragma + ExprColonExpr + Ident "size" + IntLit 1 + Empty + Empty + OfBranch + Ident "Foo" + NilLit + +type + Node = ref object + case + of AddOpr, SubOpr, MulOpr, DivOpr: + a, b: Node + of Value: + nil + info: LineInfo + case {.size: 1.} + of Foo: + nil +''' +""" + +import std/macros + +macro foo(x: untyped) = + echo x.treeRepr + echo x.repr + +foo: + type + Node = ref object + case + of AddOpr, SubOpr, MulOpr, DivOpr: + a, b: Node + of Value: + discard + info: LineInfo + case {.size: 1.} + of Foo: discard From 8bc8d40778ce0a2adbc6ba97068b729179a3ffc3 Mon Sep 17 00:00:00 2001 From: lit Date: Mon, 21 Apr 2025 03:22:03 +0800 Subject: [PATCH 084/119] fix(docgen): export for imported symbols missing; closes #24890 (#24891) --- compiler/docgen.nim | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 2b25ded7df..4149edcbc8 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -1406,11 +1406,14 @@ proc generateDoc*(d: PDoc, n, orig: PNode, config: ConfigRef, docFlags: DocFlags for it in n: traceDeps(d, it) of nkExportStmt: for it in n: - # bug #23051; don't generate documentation for exported symbols again - if it.kind == nkSym and sfExported notin it.sym.flags: - if d.module != nil and d.module == it.sym.owner: - generateDoc(d, it.sym.ast, orig, config, kForceExport) + if it.kind == nkSym: + if d.module != nil and d.module == it.sym.owner: # in current module + # bug #23051; don't generate documentation for exported symbols again + if sfExported notin it.sym.flags: + generateDoc(d, it.sym.ast, orig, config, kForceExport) + # else it's to be handled in `of XxxSection` branch elif it.sym.ast != nil: + # only export symbols in imported modules, not in current module exportSym(d, it.sym) of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept" of nkFromStmt, nkImportExceptStmt: traceDeps(d, n[0]) From 525d64fe883f9294da39f0cae1fd23e3695e5797 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 21 Apr 2025 08:56:14 +0300 Subject: [PATCH 085/119] leave type section symbols unchanged on resem, fix overly general double semcheck for forward types (#24888) fixes #24887 (really just this [1 line commit](https://github.com/nim-lang/Nim/pull/24888/commits/632c7b3397bae635f81073520cbc446c5af529e8) would have been enough to fix the issue but it would ignore the general problem) When a type definition is encountered where the symbol already has a type (not a forward type), the type is left alone (not reset to `tyForward`) and the RHS is handled differently: The RHS is still semchecked, but the type of the symbol is not updated, and nominal type nodes are ignored entirely (specifically if they are the same kind as the symbol's existing type but this restriction is not really needed). If the existing type of the symbol is an enum and and the RHS has a nominal enum type node, the enum fields of the existing type are added to scope rather than creating a new type from the RHS and adding its symbols instead. The goal is to prevent any incompatible nominal types from being generated during resem as in #24887. But it also restricts what macros can do if they generate type section AST, for example if we have: ```nim type Foo = int ``` and a macro modifies the type section while keeping the symbol node for `Foo` like: ```nim type Foo = float ``` Then the type of `Foo` will still remain `int`, while it previously became `float`. While we could maybe allow this and make it so only nominal types cannot be changed, it gets even more complex when considering generic params and whether or not they get updated. So to keep it as simple as possible the rule is that the symbol type does not change, but maybe this behavior was useful for macros. Only nominal type nodes are ignored for semchecking on the RHS, so that cases like this do not cause a regression: ```nim template foo(): untyped = proc bar() {.inject.} = discard int type Foo = foo() bar() # normally works ``` However this specific code exposed a problem with forward type handling: --- In specific cases, when the type section is undergoing the final pass, if the type fits some overly general criteria (it is not an object, enum, alias or a sink type and its node is not a nominal type node), the entire RHS is semchecked for a 2nd time as a standalone type (with `nil` prev) and *maybe* reassigned to the new semchecked type, depending on its type kind. (for some reason including nominal types when we excluded them before?) This causes a redefinition error if the RHS defines a symbol. This code goes all the way back to the first commit and I could not find the reason why it was there, but removing it showed a failure in `thard_tyforward`: If a generic forward type is invoked, it is left as an unresolved `tyGenericInvocation` on the first run. Semchecking it again at the end turns it into a `tyGenericInst`. So my understanding is that it exists to handle these loose forward types, but it is way too general and there is a similar mechanism `c.skipTypes` which is supposed to do the same thing but doesn't. So this is no longer done, and `c.skipTypes` is revamped (and renamed): It is now a list of types and the nodes that are supposed to evaluate to them, such that types needing to be updated later due to containing forward types are added to it along with their nodes. When finishing the type section, these types are reassigned to the semchecked value of their nodes so that the forward types in them are fully resolved. The "reassigning" here works due to updating the data inside the type pointer directly, and is how forward types work by themselves normally (`tyForward` types are modified in place as `s.typ`). For example, as mentioned before, generic invocations of forward types are first created as `tyGenericInvocation` and need to become `tyGenericInst` later. So they are now added to this list along with their node. Object types with forward types as their base types also need to be updated later to check that the base type is correct/inherit fields from it: For this the entire object type and its node are added to the list. Similarly, any case where whether a component type is `tyGenericInst` or `tyGenericInvocation` matters also needs to cascade this (`set` does presumably to check the instantiated type). This is not complete: Generic invocations with forward types only check that their base type is a forward type, but not any of their arguments, which causes #16754 and #24133. The generated invocations also need to cascade properly: `Foo[Bar[ForwardType]]` for example would see that `Bar[ForwardType]` is a generic invocation and stay as a generic invocation itself, but it might not queue itself to be updated later. Even if it did, only the entire type `Foo[Bar[ForwardType]]` needs to be queued, updating `Bar[ForwardType]` by itself would be redundant or it would not change anything at all. But these can be done later. --- compiler/semdata.nim | 4 +- compiler/semstmts.nim | 72 +++++++++++++++++----------- compiler/semtypes.nim | 41 ++++++++++++++-- compiler/types.nim | 2 +- nimsuggest/tests/ttype_highlight.nim | 4 -- tests/types/tresemtypesection.nim | 55 +++++++++++++++++++++ 6 files changed, 141 insertions(+), 37 deletions(-) create mode 100644 tests/types/tresemtypesection.nim diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 5eb8086f45..fa697f90cd 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -172,7 +172,9 @@ type sideEffects*: Table[int, seq[(TLineInfo, PSym)]] # symbol.id index inUncheckedAssignSection*: int importModuleLookup*: Table[int, seq[int]] # (module.ident.id, [module.id]) - skipTypes*: seq[PNode] # used to skip types between passes in type section. So far only used for inheritance, sets and generic bodies. + forwardTypeUpdates*: seq[(PType, PNode)] + # types that need to be updated in a type section + # due to containing forward types, and their corresponding nodes inTypeofContext*: int semAsgnOpr*: proc (c: PContext; n: PNode; k: TNodeKind): PNode {.nimcall.} diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 1ca9ebefff..01307cc516 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1460,8 +1460,13 @@ proc typeDefLeftSidePass(c: PContext, typeSection: PNode, i: int) = else: s = semIdentDef(c, name, skType) onDef(name.info, s) - s.typ = newTypeS(tyForward, c) - s.typ.sym = s # process pragmas: + if s.typ != nil: + # name node is a symbol with a type already, probably in resem, don't touch it + discard + else: + s.typ = newTypeS(tyForward, c) + s.typ.sym = s + # process pragmas: if name.kind == nkPragmaExpr: let rewritten = applyTypeSectionPragmas(c, name[1], typeDef) if rewritten != nil: @@ -1599,7 +1604,26 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = localError(c.config, a.info, errImplOfXexpected % s.name.s) if s.magic != mNone: processMagicType(c, s) let oldFlags = s.typ.flags - if a[1].kind != nkEmpty: + let preserveSym = s.typ != nil and s.typ.kind != tyForward and sfForward notin s.flags and + s.magic == mNone # magic might have received type above but still needs processing + if preserveSym: + # symbol already has a type, probably in resem, do not modify it + # but still semcheck the RHS to handle any defined symbols + # nominal type nodes are still ignored in semtypes + if a[1].kind != nkEmpty: + openScope(c) + pushOwner(c, s) + a[1] = semGenericParamList(c, a[1], nil) + inc c.inGenericContext + discard semTypeNode(c, a[2], s.typ) + dec c.inGenericContext + popOwner(c) + closeScope(c) + elif a[2].kind != nkEmpty: + pushOwner(c, s) + discard semTypeNode(c, a[2], s.typ) + popOwner(c) + elif a[1].kind != nkEmpty: # We have a generic type declaration here. In generic types, # symbol lookup needs to be done here. openScope(c) @@ -1689,7 +1713,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = localError(c.config, name.info, "only a 'distinct' type can borrow `.`") let aa = a[2] if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and - aa[0].kind == nkObjectTy: + aa[0].kind == nkObjectTy and not preserveSym: # give anonymous object a dummy symbol: var st = s.typ if st.kind == tyGenericBody: st = st.typeBodyImpl @@ -1730,9 +1754,6 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = obj.flags.incl sfPure obj.typ = objTy objTy.sym = obj - for sk in c.skipTypes: - discard semTypeNode(c, sk, nil) - c.skipTypes = @[] proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) = proc checkMeta(c: PContext; n: PNode; t: PType; hasError: var bool; parent: PType) = @@ -1768,6 +1789,15 @@ proc checkForMetaFields(c: PContext; n: PNode; hasError: var bool) = internalAssert c.config, false proc typeSectionFinalPass(c: PContext, n: PNode) = + for (typ, typeNode) in c.forwardTypeUpdates: + # types that need to be updated due to containing forward types + # and their corresponding type nodes + # for example generic invocations of forward types end up here + var reified = semTypeNode(c, typeNode, nil) + assert reified != nil + assignType(typ, reified) + typ.itemId = reified.itemId # same id + c.forwardTypeUpdates = @[] for i in 0.. 0: x = x.lastSon - # we need the 'safeSkipTypes' here because illegally recursive types - # can enter at this point, see bug #13763 - if x.kind notin {nkObjectTy, nkDistinctTy, nkEnumTy, nkEmpty} and - s.typ.safeSkipTypes(abstractPtrs).kind notin {tyObject, tyEnum}: - # type aliases are hard: - var t = semTypeNode(c, x, nil) - assert t != nil - if s.typ != nil and s.typ.kind notin {tyAlias, tySink}: - if t.kind in {tyProc, tyGenericInst} and not t.isMetaType: - assignType(s.typ, t) - s.typ.itemId = t.itemId - elif t.kind in {tyObject, tyEnum, tyDistinct}: - assert s.typ != nil - assignType(s.typ, t) - s.typ.itemId = t.itemId # same id var hasError = false - let baseType = s.typ.safeSkipTypes(abstractPtrs) - if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil and - (x.kind in {nkObjectTy, nkTupleTy} or + if x.kind in {nkObjectTy, nkTupleTy} or (x.kind in {nkRefTy, nkPtrTy} and x.len == 1 and - x[0].kind in {nkObjectTy, nkTupleTy}) - ): - checkForMetaFields(c, baseType.n, hasError) + x[0].kind in {nkObjectTy, nkTupleTy}): + # we need the 'safeSkipTypes' here because illegally recursive types + # can enter at this point, see bug #13763 + let baseType = s.typ.safeSkipTypes(abstractPtrs) + if baseType.kind in {tyObject, tyTuple} and not baseType.n.isNil: + checkForMetaFields(c, baseType.n, hasError) if not hasError: checkConstructedType(c.config, s.info, s.typ) #instAllTypeBoundOp(c, n.info) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 8bca77add5..41189fc7f8 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -59,11 +59,31 @@ proc newConstraint(c: PContext, k: TTypeKind): PType = result.flags.incl tfCheckedForDestructor result.addSonSkipIntLit(newTypeS(k, c), c.idgen) +proc skipGenericPrev(prev: PType): PType = + result = prev + if prev.kind == tyGenericBody and prev.last.kind != tyNone: + result = prev.last + +proc prevIsKind(prev: PType, kind: TTypeKind): bool {.inline.} = + result = prev != nil and skipGenericPrev(prev).kind == kind + proc semEnum(c: PContext, n: PNode, prev: PType): PType = if n.len == 0: return newConstraint(c, tyEnum) elif n.len == 1: # don't create an empty tyEnum; fixes #3052 return errorType(c) + if prevIsKind(prev, tyEnum): + # the symbol already has an enum type (likely resem), don't define a new enum + # but add the enum fields to scope from the original type + let isPure = sfPure in prev.sym.flags + for enumField in prev.n: + assert enumField.kind == nkSym + let e = enumField.sym + if not isPure: + addInterfaceOverloadableSymAt(c, c.currentScope, e) + else: + declarePureEnumField(c, e) + return prev var counter, x: BiggestInt = 0 e: PSym = nil @@ -197,7 +217,7 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType = if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base) if base.kind notin {tyGenericParam, tyGenericInvocation}: if base.kind == tyForward: - c.skipTypes.add n + c.forwardTypeUpdates.add (base, n[1]) elif not isOrdinalType(base, allowEnumWithHoles = true): localError(c.config, n.info, errOrdinalTypeExpected % typeToString(base, preferDesc)) elif lengthOrd(c.config, base) > MaxSetElements: @@ -307,6 +327,9 @@ proc addSonSkipIntLitChecked(c: PContext; father, son: PType; it: PNode, id: IdG proc semDistinct(c: PContext, n: PNode, prev: PType): PType = if n.len == 0: return newConstraint(c, tyDistinct) + if prevIsKind(prev, tyDistinct): + # the symbol already has a distinct type (likely resem), don't create a new type + return skipGenericPrev(prev) result = newOrPrevType(tyDistinct, prev, c) addSonSkipIntLitChecked(c, result, semTypeNode(c, n[0], nil), n[0], c.idgen) if n.len > 1: result.n = n[1] @@ -994,11 +1017,15 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType result = nil if n.len == 0: return newConstraint(c, tyObject) + if prevIsKind(prev, tyObject) and sfForward notin prev.sym.flags: + # the symbol already has an object type (likely resem), don't create a new type + return skipGenericPrev(prev) var check = initIntSet() var pos = 0 var base, realBase: PType = nil # n[0] contains the pragmas (if any). We process these later... checkSonsLen(n, 3, c.config) + var needsForwardUpdate = false if n[1].kind != nkEmpty: realBase = semTypeNode(c, n[1][0], nil) base = skipTypesOrNil(realBase, skipPtrs) @@ -1020,7 +1047,7 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType return newType(tyError, c.idgen, result.owner) elif concreteBase.kind == tyForward: - c.skipTypes.add n #we retry in the final pass + needsForwardUpdate = true else: if concreteBase.kind != tyError: localError(c.config, n[1].info, "inheritance only works with non-final objects; " & @@ -1030,6 +1057,10 @@ proc semObjectNode(c: PContext, n: PNode, prev: PType; flags: TTypeFlags): PType realBase = nil if n.kind != nkObjectTy: internalError(c.config, n.info, "semObjectNode") result = newOrPrevType(tyObject, prev, c) + if needsForwardUpdate: + # if the inherited object is a forward type, + # the entire object needs to be checked again + c.forwardTypeUpdates.add (result, n) #we retry in the final pass rawAddSon(result, realBase) if realBase == nil and tfInheritable in flags: result.flags.incl tfInheritable @@ -1056,6 +1087,9 @@ proc semAnyRef(c: PContext; n: PNode; kind: TTypeKind; prev: PType): PType = if n.len < 1: result = newConstraint(c, kind) else: + if prevIsKind(prev, kind) and tfRefsAnonObj in prev.skipTypes({tyGenericBody}).flags: + # the symbol already has an object type (likely resem), don't create a new type + return skipGenericPrev(prev) let isCall = int ord(n.kind in nkCallKinds+{nkBracketExpr}) let n = if n[0].kind == nkBracket: n[0] else: n checkMinSonsLen(n, 1, c.config) @@ -1660,6 +1694,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = for i in 1.. Date: Mon, 21 Apr 2025 09:58:45 +0300 Subject: [PATCH 086/119] consider proc return type as weak reference in codegen (#24894) fixes #7706 --- compiler/ccgtypes.nim | 2 +- tests/proc/trecursivereturntype.nim | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/proc/trecursivereturntype.nim diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 9cb80baef8..9b52610f60 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -587,7 +587,7 @@ proc genProcParams(m: BModule; t: PType, rettype: var Rope, params: var Builder, if t.returnType == nil or isInvalidReturnType(m.config, t): rettype = CVoid else: - rettype = getTypeDescAux(m, t.returnType, check, dkResult) + rettype = getTypeDescWeak(m, t.returnType, check, dkResult) var paramBuilder: ProcParamBuilder params.addProcParams(paramBuilder): for i in 1.. Date: Mon, 21 Apr 2025 10:01:44 +0300 Subject: [PATCH 087/119] generally disallow recursive structural types, check proc param types (#24893) fixes #5631, fixes #8938, fixes #18855, fixes #19271, fixes #23885, fixes #24877 `isTupleRecursive`, previously only called to give an error for illegal recursions for: * tuple fields * types declared in type sections * explicitly instantiated generic types did not check for recursions in proc types. It now does, meaning proc types now need a nominal type layer to recurse over themselves. It is renamed to `isRecursiveStructuralType` to better reflect what it does, it is different from a recursive type that cannot exist due to a lack of pointer indirection which is possible for nominal types. It is now also called to check the param/return types of procs, similar to how tuple field types are checked. Pointer indirection checks are not needed since procs are pointers. I wondered if this would lead to a slowdown in the compiler but since it only skips structural types it shouldn't take too many iterations, not to mention only proc types are newly considered and aren't that common. But maybe something in the implementation could be inefficient, like the cycle detector using an IntSet. Note: The name `isRecursiveStructuralType` is not exactly correct because it still checks for `distinct` types. If it didn't, then the compiler would accept this: ```nim type A = distinct B B = ref A ``` But this breaks when attempting to write `var x: A`. However this is not the case for: ```nim type A = object x: B B = ref A ``` So a better description would be "types that are structural on the backend". A future step to deal with #14015 and #23224 might be to check the arguments of `tyGenericInst` as well but I don't know if this makes perfect sense. --- compiler/seminst.nim | 4 +++ compiler/semtypes.nim | 10 ++++-- compiler/semtypinst.nim | 2 +- compiler/types.nim | 23 +++++++++---- tests/errmsgs/trecursiveproctype1.nim | 10 ++++++ tests/errmsgs/trecursiveproctype2.nim | 18 ++++++++++ tests/errmsgs/trecursiveproctype3.nim | 9 +++++ tests/errmsgs/trecursiveproctype4.nim | 10 ++++++ tests/errmsgs/trecursiveproctype5.nim | 49 +++++++++++++++++++++++++++ tests/errmsgs/trecursiveproctype6.nim | 10 ++++++ 10 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 tests/errmsgs/trecursiveproctype1.nim create mode 100644 tests/errmsgs/trecursiveproctype2.nim create mode 100644 tests/errmsgs/trecursiveproctype3.nim create mode 100644 tests/errmsgs/trecursiveproctype4.nim create mode 100644 tests/errmsgs/trecursiveproctype5.nim create mode 100644 tests/errmsgs/trecursiveproctype6.nim diff --git a/compiler/seminst.nim b/compiler/seminst.nim index ab00810f92..c23e3f80d8 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -308,6 +308,8 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable, param.typ = result[i] result.n[i] = newSymNode(param) + if isRecursiveStructuralType(result[i]): + localError(c.config, originalParams[i].sym.info, "illegal recursion in type '" & typeToString(result[i]) & "'") propagateToOwner(result, result[i]) addDecl(c, param) @@ -318,6 +320,8 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable, cl.isReturnType = false result.n[0] = originalParams[0].copyTree if result[0] != nil: + if isRecursiveStructuralType(result[0]): + localError(c.config, originalParams[0].info, "illegal recursion in type '" & typeToString(result[0]) & "'") propagateToOwner(result, result[0]) eraseVoidParams(result) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 41189fc7f8..9ebc930079 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -576,7 +576,7 @@ proc semTuple(c: PContext, n: PNode, prev: PType): PType = styleCheckDef(c, a[j].info, field) onDef(field.info, field) if result.n.len == 0: result.n = nil - if isTupleRecursive(result): + if isRecursiveStructuralType(result): localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(result)) proc semIdentVis(c: PContext, kind: TSymKind, n: PNode, @@ -1500,6 +1500,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if isType: localError(c.config, a.info, "':' expected") if kind in {skTemplate, skMacro}: typ = newTypeS(tyUntyped, c) + elif isRecursiveStructuralType(typ): + localError(c.config, a[^2].info, errIllegalRecursionInTypeX % typeToString(typ)) elif skipTypes(typ, {tyGenericInst, tyAlias, tySink}).kind == tyVoid: continue @@ -1563,7 +1565,9 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if r != nil: # turn explicit 'void' return type into 'nil' because the rest of the # compiler only checks for 'nil': - if skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid: + if isRecursiveStructuralType(r): + localError(c.config, n.info, errIllegalRecursionInTypeX % typeToString(r)) + elif skipTypes(r, {tyGenericInst, tyAlias, tySink}).kind != tyVoid: if kind notin {skMacro, skTemplate} and r.kind in {tyTyped, tyUntyped}: localError(c.config, n[0].info, "return type '" & typeToString(r) & "' is only valid for macros and templates") @@ -1751,7 +1755,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = # special check for generic object with # generic/partial specialized parent let tx = result.skipTypes(abstractPtrs, 50) - if tx.isNil or isTupleRecursive(tx): + if tx.isNil or isRecursiveStructuralType(tx): localError(c.config, n.info, "illegal recursion in type '$1'" % typeToString(result[0])) return errorType(c) if tx != result and tx.kind == tyObject: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 4637ea4046..daee9ba4fc 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -28,7 +28,7 @@ proc checkConstructedType*(conf: ConfigRef; info: TLineInfo, typ: PType) = if t.kind in tyTypeClasses: discard elif t.kind in {tyVar, tyLent} and t.elementType.kind in {tyVar, tyLent}: localError(conf, info, "type 'var var' is not allowed") - elif computeSize(conf, t) == szIllegalRecursion or isTupleRecursive(t): + elif computeSize(conf, t) == szIllegalRecursion or isRecursiveStructuralType(t): localError(conf, info, "illegal recursion in type '" & typeToString(t) & "'") proc searchInstTypes*(g: ModuleGraph; key: PType): PType = diff --git a/compiler/types.nim b/compiler/types.nim index 6f098b1c3e..8744f173ce 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1897,7 +1897,7 @@ proc typeMismatch*(conf: ConfigRef; info: TLineInfo, formal, actual: PType, n: P processPragmaAndCallConvMismatch(msg, a, b, conf) localError(conf, info, msg) -proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool = +proc isRecursiveStructuralType(t: PType, cycleDetector: var IntSet): bool = if t == nil: return false if cycleDetector.containsOrIncl(t.id): @@ -1908,19 +1908,30 @@ proc isTupleRecursive(t: PType, cycleDetector: var IntSet): bool = var cycleDetectorCopy: IntSet for a in t.kids: cycleDetectorCopy = cycleDetector - if isTupleRecursive(a, cycleDetectorCopy): + if isRecursiveStructuralType(a, cycleDetectorCopy): + return true + of tyProc: + result = false + var cycleDetectorCopy: IntSet + if t.returnType != nil: + cycleDetectorCopy = cycleDetector + if isRecursiveStructuralType(t.returnType, cycleDetectorCopy): + return true + for _, a in t.paramTypes: + cycleDetectorCopy = cycleDetector + if isRecursiveStructuralType(a, cycleDetectorCopy): return true of tyRef, tyPtr, tyVar, tyLent, tySink, tyArray, tyUncheckedArray, tySequence, tyDistinct: - return isTupleRecursive(t.elementType, cycleDetector) + return isRecursiveStructuralType(t.elementType, cycleDetector) of tyAlias, tyGenericInst: - return isTupleRecursive(t.skipModifier, cycleDetector) + return isRecursiveStructuralType(t.skipModifier, cycleDetector) else: return false -proc isTupleRecursive*(t: PType): bool = +proc isRecursiveStructuralType*(t: PType): bool = var cycleDetector = initIntSet() - isTupleRecursive(t, cycleDetector) + isRecursiveStructuralType(t, cycleDetector) proc isException*(t: PType): bool = # check if `y` is object type and it inherits from Exception diff --git a/tests/errmsgs/trecursiveproctype1.nim b/tests/errmsgs/trecursiveproctype1.nim new file mode 100644 index 0000000000..0bd5b8e0dc --- /dev/null +++ b/tests/errmsgs/trecursiveproctype1.nim @@ -0,0 +1,10 @@ +discard """ + errormsg: "illegal recursion in type 'Behavior'" + line: 10 +""" + +# issue #5631 + +type + Behavior = proc(): Effect + Effect = proc(behavior: Behavior): Behavior diff --git a/tests/errmsgs/trecursiveproctype2.nim b/tests/errmsgs/trecursiveproctype2.nim new file mode 100644 index 0000000000..60306278dd --- /dev/null +++ b/tests/errmsgs/trecursiveproctype2.nim @@ -0,0 +1,18 @@ +discard """ + errormsg: "illegal recursion in type 'B'" + line: 9 +""" + +# issue #8938 + +type + A = proc(acc, x: int, y: B): int + B = proc(acc, x: int, y: A): int + +proc fact(n: int): int = + proc g(acc, a: int, b: proc(acc, a: int, b: A): int): A = + if a == 0: + acc + else: + b(a * acc, a - 1, b) + g(1, n, g) diff --git a/tests/errmsgs/trecursiveproctype3.nim b/tests/errmsgs/trecursiveproctype3.nim new file mode 100644 index 0000000000..288bb27909 --- /dev/null +++ b/tests/errmsgs/trecursiveproctype3.nim @@ -0,0 +1,9 @@ +discard """ + errormsg: "illegal recursion in type 'ptr MyFunc'" + line: 9 +""" + +# issue #19271 + +type + MyFunc = proc(f: ptr MyFunc) diff --git a/tests/errmsgs/trecursiveproctype4.nim b/tests/errmsgs/trecursiveproctype4.nim new file mode 100644 index 0000000000..860ae313dd --- /dev/null +++ b/tests/errmsgs/trecursiveproctype4.nim @@ -0,0 +1,10 @@ +discard """ + errormsg: "illegal recursion in type 'BB'" + line: 9 +""" + +# issue #23885 + +type + EventHandler = proc(target: BB) + BB = (EventHandler,) diff --git a/tests/errmsgs/trecursiveproctype5.nim b/tests/errmsgs/trecursiveproctype5.nim new file mode 100644 index 0000000000..58237959b2 --- /dev/null +++ b/tests/errmsgs/trecursiveproctype5.nim @@ -0,0 +1,49 @@ +discard """ + errormsg: "illegal recursion in type 'seq[Shape[system.float32]]" + line: 20 +""" + +# issue #24877 + +type + ValT = float32|float64 + Square[T: ValT] = object + inner: seq[Shape[T]] + Circle[T: ValT] = object + inner: seq[Shape[T]] + + InnerShapesProc[T: ValT] = proc(): seq[Shape[T]] + Shape[T: ValT] = tuple[ + innerShapes: InnerShapesProc[T], + ] + +func newSquare[T: ValT](inner: seq[Shape[T]] = @[]): Square[T] = + Square[T](inner: inner) + +proc innerShapes[T: ValT](sq: Square[T]): seq[Shape[T]] = sq.inner +proc iInnerShapes[T: ValT](sq: Square[T]): InnerShapesProc[T] = + proc(): seq[Shape[T]] = sq.innerShapes() + +func toShape[T: ValT](sq: Square[T]): Shape[T] = + (innerShapes: sq.iInnerShapes()) + +func newCircle[T: ValT](inner: seq[Shape[T]] = @[]): Circle[T] = + Circle[T](inner: inner) + +proc innerShapes[T: ValT](c: Circle[T]): seq[Shape[T]] = c.inner +proc iInnerShapes[T: ValT](c: Circle[T]): InnerShapesProc[T] = + proc(): seq[Shape[T]] = c.innerShapes() + +func toShape[T: ValT](c: Circle[T]): Shape[T] = + (innerShapes: c.iInnerShapes()) + +const + sq1 = newSquare[float32]() + sq2 = newSquare[float32]() + sq3 = newSquare[float64]() + c1 = newCircle[float64](@[sq3]) + c2 = newCircle[float32](@[sq1, sq2]) + +let + shapes32 = @[sq1.toShape, sq2.toShape, c2.toShape] + shapes64 = @[sq3.toShape, c1.toShape] diff --git a/tests/errmsgs/trecursiveproctype6.nim b/tests/errmsgs/trecursiveproctype6.nim new file mode 100644 index 0000000000..2e1f2fa78e --- /dev/null +++ b/tests/errmsgs/trecursiveproctype6.nim @@ -0,0 +1,10 @@ +discard """ + errormsg: "illegal recursion in type 'Test" + line: 9 +""" + +# issue #18855 + +type + TestProc = proc(a: Test) + Test = Test From dc100c5caa673b039155e9e5d4c7fc0c239f4eb5 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 21 Apr 2025 19:41:09 +0300 Subject: [PATCH 088/119] update proc type recursion errors after merge (#24897) refs #24893, refs #24888 --- tests/errmsgs/trecursiveproctype2.nim | 2 +- tests/errmsgs/trecursiveproctype3.nim | 2 +- tests/errmsgs/trecursiveproctype4.nim | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/errmsgs/trecursiveproctype2.nim b/tests/errmsgs/trecursiveproctype2.nim index 60306278dd..44a41156d6 100644 --- a/tests/errmsgs/trecursiveproctype2.nim +++ b/tests/errmsgs/trecursiveproctype2.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "illegal recursion in type 'B'" + errormsg: "illegal recursion in type 'A'" line: 9 """ diff --git a/tests/errmsgs/trecursiveproctype3.nim b/tests/errmsgs/trecursiveproctype3.nim index 288bb27909..6991a1aef9 100644 --- a/tests/errmsgs/trecursiveproctype3.nim +++ b/tests/errmsgs/trecursiveproctype3.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "illegal recursion in type 'ptr MyFunc'" + errormsg: "illegal recursion in type 'MyFunc'" line: 9 """ diff --git a/tests/errmsgs/trecursiveproctype4.nim b/tests/errmsgs/trecursiveproctype4.nim index 860ae313dd..4839b77afb 100644 --- a/tests/errmsgs/trecursiveproctype4.nim +++ b/tests/errmsgs/trecursiveproctype4.nim @@ -1,5 +1,5 @@ discard """ - errormsg: "illegal recursion in type 'BB'" + errormsg: "illegal recursion in type 'EventHandler'" line: 9 """ From d966ee3fc3874f63b4e32a7edc7566982bb570ce Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 22 Apr 2025 18:24:22 +0300 Subject: [PATCH 089/119] whitelist prev types to reuse in `newOrPrevType` (#24899) fixes #24898 A type is only overwritten if it is definitely a forward type, partial object (symbol marked `sfForward`) or a magic type. Maybe worse for performance but should be more correct. Another option might be to provide a different value for `prev` for the `preserveSym` case but then we cannot easily ignore only nominal type nodes. --- compiler/semtypes.nim | 18 ++++++++++++------ tests/types/tresemtypesection.nim | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 9ebc930079..a0ea8baac7 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -38,21 +38,27 @@ const errNoGenericParamsAllowedForX = "no generic parameters allowed for $1" errInOutFlagNotExtern = "the '$1' modifier can be used only with imported types" +proc reusePrev(prev: PType): bool {.inline.} = + # only overwrite `prev` if it is a forward type, partial object or magic type + result = prev != nil and (prev.kind == tyForward or (prev.sym != nil and + # partial object marks sym as `sfForward` + (sfForward in prev.sym.flags or prev.sym.magic != mNone))) + proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext, son: sink PType): PType = - if prev == nil or prev.kind == tyGenericBody: - result = newTypeS(kind, c, son) - else: + if reusePrev(prev): result = prev result.setSon(son) if result.kind == tyForward: result.kind = kind + else: + result = newTypeS(kind, c, son) #if kind == tyError: result.flags.incl tfCheckedForDestructor proc newOrPrevType(kind: TTypeKind, prev: PType, c: PContext): PType = - if prev == nil or prev.kind == tyGenericBody: - result = newTypeS(kind, c) - else: + if reusePrev(prev): result = prev if result.kind == tyForward: result.kind = kind + else: + result = newTypeS(kind, c) proc newConstraint(c: PContext, k: TTypeKind): PType = result = newTypeS(tyBuiltInTypeClass, c) diff --git a/tests/types/tresemtypesection.nim b/tests/types/tresemtypesection.nim index ac6c8610ca..255e6c8637 100644 --- a/tests/types/tresemtypesection.nim +++ b/tests/types/tresemtypesection.nim @@ -41,6 +41,16 @@ foo: discard Bar[int](x: 123) discard Bar[string](x: "abc") + type + Generic1[T] = object + Generic2[T] = ref int + Generic3[T] = ref Generic1[T] + Generic4[T] = Generic2[T] + GenericInst1 = Generic1[int] + GenericInst2 = Generic2[int] + GenericInst3 = Generic3[int] + GenericInst4 = Generic4[int] + # regression test: template templ(): untyped = proc injected() {.inject.} = discard @@ -49,7 +59,13 @@ foo: type TestInject = templ() var x1: TestInject injected() # normally works + echo $NONE echo a var x2: TestInject injected() + +block: # issue #24898 + type V[W] = object + template g(d: int) = discard d + g((; type J = V[int]; 0)) From 5dcfd8d7bbe0d10769240fd790b693e112cc3a8d Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Thu, 24 Apr 2025 15:17:42 -0400 Subject: [PATCH 090/119] Add `tySet` to concept matching (#24908) --- compiler/concepts.nim | 4 ++++ tests/concepts/tconceptsv2.nim | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 1c8860bd5f..7c64b5eae9 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -419,6 +419,10 @@ proc matchType(c: PContext; fo, ao: PType; m: var MatchCon): bool = result = matchType(c, ff, a, m) if result: break # and remember the binding! m.bindings.setToPreviousLayer() + of tySet: + result = false + if a.kind == tySet: + result = matchType(c, f.elementType, a.elementType, m) else: result = false if result and ao.kind == tyGenericParam: diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 83a19348b1..369fd3e854 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -485,6 +485,18 @@ block: assert Container[AsyncImpl] isnot SyncType assert Container[AsyncImpl] is AsyncType +block: + type + C1 = concept + proc p(x: typedesc[Self]): int + E1 = enum + One, Two + proc p[E: enum](x: typedesc[set[E]]): int = sizeof(set[E]) + + proc spring(x: C1) = discard + + spring({One,Two}) + # this code fails inside a block for some reason type Indexable[T] = concept proc `[]`(t: Self, i: int): T From 8c9a645bdf8bbd14f7fc9e95c475f7fb963de3f3 Mon Sep 17 00:00:00 2001 From: metagn Date: Thu, 24 Apr 2025 22:18:18 +0300 Subject: [PATCH 091/119] fix generic converter regression with `var`/subtype args (#24902) refs #24867, https://github.com/nim-lang/Nim/pull/24867#issuecomment-2821315971 The argument node of the converter can be wrapped in [hidden `addr` or subtype conversion nodes](https://github.com/nim-lang/Nim/blob/dc100c5caa673b039155e9e5d4c7fc0c239f4eb5/compiler/sigmatch.nim#L2327-L2335) which have to be skipped when matching the type again, since the type of the node is the uninstantiated type taken from the proc parameter. --- compiler/semcall.nim | 4 +++- tests/converter/tvargenericconverter.nim | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/converter/tvargenericconverter.nim diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 90376214db..e3c6ea851b 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -688,7 +688,9 @@ proc instGenericConvertersArg*(c: PContext, a: PNode, x: TCandidate) = if s.isGenericRoutineStrict: var src = s.typ.firstParamType var convMatch = newCandidate(c, src) - let srca = typeRel(convMatch, src, a[1].typ) + var arg = a[1] + if arg.kind in {nkHiddenAddr, nkHiddenSubConv}: arg = arg[^1] + let srca = typeRel(convMatch, src, arg.typ) if srca notin {isEqual, isGeneric, isSubtype}: internalError(c.config, a.info, "generic converter failed rematch") let finalCallee = generateInstance(c, s, convMatch.bindings, a.info) diff --git a/tests/converter/tvargenericconverter.nim b/tests/converter/tvargenericconverter.nim new file mode 100644 index 0000000000..f88779d9d8 --- /dev/null +++ b/tests/converter/tvargenericconverter.nim @@ -0,0 +1,7 @@ +# regression test + +converter toPtr[T](x: var T): ptr T = + result = addr x + +var x = 123 +let y: ptr int = x From eea4ce0e2cf1dfdd2a90c2ab7f93888efc7ccf4e Mon Sep 17 00:00:00 2001 From: Tomohiro Date: Mon, 28 Apr 2025 17:43:53 +0900 Subject: [PATCH 092/119] changes FileHandle type on Windows (#24910) On windows, `HANDLE` type values are converted to `syncio.FileHandle` in `lib/std/syncio.nim`, `lib/pure/memfiles.nim` and `lib/pure/osproc.nim`. `HANDLE` type is `void *` on Windows and its size is larger then `cint`. https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types This PR change `syncio.FileHandle` type so that converting `HANDLE` type to `syncio.FileHandle` doesn't lose bits. We can keep `FileHandle` unchanged and change some of parameter/return type from `FileHandle` to an type same size to `HANDLE`, but it is breaking change. --- lib/pure/memfiles.nim | 4 ++-- lib/pure/os.nim | 6 +++--- lib/pure/osproc.nim | 12 ++++++------ lib/pure/terminal.nim | 8 ++++++-- lib/std/syncio.nim | 35 ++++++++++++++++++++--------------- lib/windows/winlean.nim | 2 +- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 8430dde8b3..2ba26e5c84 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -46,10 +46,10 @@ proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode = when defined(windows): var sizeHigh = int32(newFileSize shr 32) let sizeLow = int32(newFileSize and 0xffffffff) - let status = setFilePointer(fh, sizeLow, addr(sizeHigh), FILE_BEGIN) + let status = setFilePointer(Handle fh, sizeLow, addr(sizeHigh), FILE_BEGIN) let lastErr = osLastError() if (status == INVALID_SET_FILE_POINTER and lastErr.int32 != NO_ERROR) or - setEndOfFile(fh) == 0: + setEndOfFile(Handle fh) == 0: result = lastErr else: if newFileSize > oldSize: # grow the file diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 1fac8f8744..ea8dd1483a 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -845,11 +845,11 @@ when weirdTarget or defined(windows) or defined(posix) or defined(nintendoswitch result = default(FileInfo) when defined(windows): var rawInfo: BY_HANDLE_FILE_INFORMATION - # We have to use the super special '_get_osfhandle' call (wrapped above) + # We have to use the super special '_get_osfhandle' call (wrapped in winlean) # To transform the C file descriptor to a native file handle. - var realHandle = get_osfhandle(handle) + var realHandle = get_osfhandle(handle.cint) if getFileInformationByHandle(realHandle, addr rawInfo) == 0: - raiseOSError(osLastError(), $handle) + raiseOSError(osLastError(), $(int handle)) rawToFormalFileInfo(rawInfo, "", result) else: var rawInfo: Stat = default(Stat) diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 017302dc2a..e7f82faceb 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -546,8 +546,8 @@ when defined(windows) and not defined(useNimRtl): raiseOSError(osLastError()) proc fileClose[T: Handle | FileHandle](h: var T) {.inline.} = - if h > 4: - closeHandleCheck(h) + if h.int > 4: + closeHandleCheck(Handle h) h = INVALID_HANDLE_VALUE.T proc hsClose(s: Stream) = @@ -574,8 +574,8 @@ when defined(windows) and not defined(useNimRtl): addr bytesWritten, nil) if a == 0: raiseOSError(osLastError()) - proc newFileHandleStream(handle: Handle): owned FileHandleStream = - result = FileHandleStream(handle: handle, closeImpl: hsClose, atEndImpl: hsAtEnd, + proc newFileHandleStream(handle: FileHandle): owned FileHandleStream = + result = FileHandleStream(handle: Handle handle, closeImpl: hsClose, atEndImpl: hsAtEnd, readDataImpl: hsReadData, writeDataImpl: hsWriteData) proc buildCommandLine(a: string, args: openArray[string]): string = @@ -888,7 +888,7 @@ when defined(windows) and not defined(useNimRtl): assert readfds.len <= MAXIMUM_WAIT_OBJECTS var rfds: WOHandleArray for i in 0..readfds.len()-1: - rfds[i] = readfds[i].outHandle #fProcessHandle + rfds[i] = readfds[i].outHandle.Handle #fProcessHandle var ret = waitForMultipleObjects(readfds.len.int32, addr(rfds), 0'i32, timeout.int32) @@ -904,7 +904,7 @@ when defined(windows) and not defined(useNimRtl): proc hasData*(p: Process): bool = var x: int32 - if peekNamedPipe(p.outHandle, lpTotalBytesAvail = addr x): + if peekNamedPipe(p.outHandle.Handle, lpTotalBytesAvail = addr x): result = x > 0 elif not defined(useNimRtl): diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index c3ebc76a34..91f0910585 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -805,9 +805,13 @@ proc isatty*(f: File): bool = when defined(posix): proc isatty(fildes: FileHandle): cint {. importc: "isatty", header: "".} - else: - proc isatty(fildes: FileHandle): cint {. + elif defined(windows): + proc c_isatty(fildes: cint): cint {. importc: "_isatty", header: "".} + proc isatty(fildes: FileHandle): cint = + c_isatty(cint(fildes)) + else: + {.error: "isatty is not supported on your operating system!".} result = isatty(getFileHandle(f)) != 0'i32 diff --git a/lib/std/syncio.nim b/lib/std/syncio.nim index 911bff276e..2aafb40e93 100644 --- a/lib/std/syncio.nim +++ b/lib/std/syncio.nim @@ -40,9 +40,6 @@ type ## at the end. If the file does not exist, it ## will be created. - FileHandle* = cint ## The type that represents an OS file handle; this is - ## useful for low-level file access. - FileSeekPos* = enum ## Position relative to which seek should happen. # The values are ordered so that they match with stdio # SEEK_SET, SEEK_CUR and SEEK_END respectively. @@ -50,6 +47,13 @@ type fspCur ## Seek relative to current position fspEnd ## Seek relative to end +when defined(windows): + type FileHandle* = int + ## Windows `HANDLE` type, convertible to `winlean.Handle`. +else: + type FileHandle* = cint ## The type that represents an OS file handle; this is + ## useful for low-level file access. + # text file handling: when not defined(nimscript) and not defined(js): # duplicated between io and ansi_c @@ -310,12 +314,7 @@ elif defined(windows): proc getOsfhandle(fd: cint): int {. importc: "_get_osfhandle", header: "".} - type - IoHandle = distinct pointer - ## Windows' HANDLE type. Defined as an untyped pointer but is **not** - ## one. Named like this to avoid collision with other `system` modules. - - proc setHandleInformation(hObject: IoHandle, dwMask, dwFlags: WinDWORD): + proc setHandleInformation(hObject: FileHandle, dwMask, dwFlags: WinDWORD): WinBOOL {.stdcall, dynlib: "kernel32", importc: "SetHandleInformation".} @@ -361,7 +360,7 @@ proc getFileHandle*(f: File): FileHandle = ## Note that on Windows this doesn't return the Windows-specific handle, ## but the C library's notion of a handle, whatever that means. ## Use `getOsFileHandle` instead. - c_fileno(f) + FileHandle c_fileno(f) proc getOsFileHandle*(f: File): FileHandle = ## Returns the OS file handle of the file `f`. This is only useful for @@ -390,7 +389,7 @@ when defined(nimdoc) or (defined(posix) and not defined(nimscript)) or defined(w flags = if inheritable: flags and not FD_CLOEXEC else: flags or FD_CLOEXEC result = c_fcntl(f, F_SETFD, flags) != -1 else: - result = setHandleInformation(cast[IoHandle](f), HANDLE_FLAG_INHERIT, + result = setHandleInformation(f, HANDLE_FLAG_INHERIT, inheritable.WinDWORD) != 0 proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect], @@ -423,12 +422,18 @@ proc readLine*(f: File, line: var string): bool {.tags: [ReadIOEffect], importc: "LocalFree", stdcall, dynlib: "kernel32".} proc isatty(f: File): bool = + # terminal module also has isatty when defined(posix): proc isatty(fildes: FileHandle): cint {. importc: "isatty", header: "".} - else: - proc isatty(fildes: FileHandle): cint {. + elif defined(windows): + proc c_isatty(fildes: cint): cint {. importc: "_isatty", header: "".} + proc isatty(fildes: FileHandle): cint = + c_isatty(cint(fildes)) + else: + {.error: "isatty is not supported on your operating system!".} + result = isatty(getFileHandle(f)) != 0'i32 # this implies the file is open @@ -769,10 +774,10 @@ proc open*(f: var File, filehandle: FileHandle, ## The passed file handle will no longer be inheritable. when not defined(nimInheritHandles) and declared(setInheritable): let oshandle = when defined(windows): FileHandle getOsfhandle( - filehandle) else: filehandle + cint filehandle) else: filehandle if not setInheritable(oshandle, false): return false - f = c_fdopen(filehandle, RawFormatOpen[mode]) + f = c_fdopen(cint filehandle, RawFormatOpen[mode]) result = f != nil proc open*(filename: string, diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 99f46fc6fb..39ee582ee4 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -815,7 +815,7 @@ proc WSASendTo*(s: SocketHandle, buf: ptr TWSABuf, bufCount: DWORD, completionProc: POVERLAPPED_COMPLETION_ROUTINE): cint {. stdcall, importc: "WSASendTo", dynlib: "Ws2_32.dll".} -proc get_osfhandle*(fd:FileHandle): Handle {. +proc get_osfhandle*(fd: cint): Handle {. importc: "_get_osfhandle", header:"".} proc getSystemTimes*(lpIdleTime, lpKernelTime, From d7b1f0a99ab9acff13d1accbfeedc2345f9a19d5 Mon Sep 17 00:00:00 2001 From: lit Date: Tue, 29 Apr 2025 12:45:20 +0800 Subject: [PATCH 093/119] fix(js): nonvar destructor was disallowed; closes #24914 (#24915) --- compiler/semstmts.nim | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 01307cc516..7039062306 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2153,13 +2153,17 @@ proc bindDupHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = let t = s.typ var noError = false + template notRefc: bool = + # fixes refc with non-var destructor; cancel warnings (#23156) + c.config.backend == backendJs or + c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc} let cond = case op of attachedWasMoved: t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar of attachedTrace: t.len == 3 and t.returnType == nil and t.firstParamType.kind == tyVar and t[2].kind == tyPointer of attachedDestructor: - if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: + if notRefc: t.len == 2 and t.returnType == nil else: t.len == 2 and t.returnType == nil and t.firstParamType.kind == tyVar @@ -2192,7 +2196,7 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) = localError(c.config, n.info, errGenerated, "signature for '=trace' must be proc[T: object](x: var T; env: pointer)") of attachedDestructor: - if c.config.selectedGC in {gcArc, gcAtomicArc, gcOrc}: + if notRefc: localError(c.config, n.info, errGenerated, "signature for '=destroy' must be proc[T: object](x: var T) or proc[T: object](x: T)") else: From 8518cf079f5be9d6f3af906ca03efc18723ec9b2 Mon Sep 17 00:00:00 2001 From: Esteban C Borsani Date: Tue, 29 Apr 2025 06:07:01 -0300 Subject: [PATCH 094/119] asyncnet ssl overhaul (#24896) Fixes #24895 - Remove all bio handling - Remove all `sendPendingSslData` which only seems to make things work by chance - Wrap the client socket on `acceptAddr` (std/net does this) - Do the SSL handshake on accept (std/net does this) The only concern is if addWrite/addRead works well on Windows. --- lib/pure/asyncnet.nim | 182 +++++++++++++++++++---------------------- tests/async/t24895.nim | 79 ++++++++++++++++++ 2 files changed, 165 insertions(+), 96 deletions(-) create mode 100644 tests/async/t24895.nim diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index fb37afa427..76bacb162e 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -126,8 +126,6 @@ type when defineSsl: sslHandle: SslPtr sslContext: SslContext - bioIn: BIO - bioOut: BIO sslNoShutdown: bool domain: Domain sockType: SockType @@ -210,7 +208,7 @@ when defineSsl: proc raiseSslHandleError = raiseSSLError("The SSL Handle is closed/unset") - proc getSslError(socket: AsyncSocket, err: cint): cint = + proc getSslError(socket: AsyncSocket, flags: set[SocketFlag], err: cint): cint = assert socket.isSsl assert err < 0 var ret = SSL_get_error(socket.sslHandle, err.cint) @@ -223,47 +221,49 @@ when defineSsl: return ret of SSL_ERROR_WANT_X509_LOOKUP: raiseSSLError("Function for x509 lookup has been called.") - of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: + of SSL_ERROR_SYSCALL: + socket.sslNoShutdown = true + let osErr = osLastError() + if not flags.isDisconnectionError(osErr): + var errStr = "IO error has occurred" + let sslErr = ERR_peek_last_error() + if sslErr == 0 and err == 0: + errStr.add ' ' + errStr.add "because an EOF was observed that violates the protocol" + elif sslErr == 0 and err == -1: + errStr.add ' ' + errStr.add "in the BIO layer" + else: + let errStr = $ERR_error_string(sslErr, nil) + raiseSSLError(errStr & ": " & errStr) + raiseOSError(osErr, errStr) + else: + return ret + of SSL_ERROR_SSL: socket.sslNoShutdown = true raiseSSLError() else: raiseSSLError("Unknown Error") - proc sendPendingSslData(socket: AsyncSocket, - flags: set[SocketFlag]) {.async.} = - if socket.sslHandle == nil: - raiseSslHandleError() - let len = bioCtrlPending(socket.bioOut) - if len > 0: - var data = newString(len) - let read = bioRead(socket.bioOut, cast[cstring](addr data[0]), len) - assert read != 0 - if read < 0: - raiseSSLError() - data.setLen(read) - await socket.fd.AsyncFD.send(data, flags) - - proc appeaseSsl(socket: AsyncSocket, flags: set[SocketFlag], - sslError: cint): owned(Future[bool]) {.async.} = + proc handleSslFailure(socket: AsyncSocket, flags: set[SocketFlag], sslError: cint): Future[bool] = ## Returns `true` if `socket` is still connected, otherwise `false`. - result = true + let retFut = newFuture[bool]("asyncnet.handleSslFailure") case sslError - of SSL_ERROR_WANT_WRITE: - await sendPendingSslData(socket, flags) + of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: + addWrite(socket.fd.AsyncFD, proc (sock: AsyncFD): bool = + retFut.complete(true) + return true + ) of SSL_ERROR_WANT_READ: - var data = await recv(socket.fd.AsyncFD, BufferSize, flags) - if socket.sslHandle == nil: - raiseSslHandleError() - let length = len(data) - if length > 0: - let ret = bioWrite(socket.bioIn, cast[cstring](addr data[0]), length.cint) - if ret < 0: - raiseSSLError() - elif length == 0: - # connection not properly closed by remote side or connection dropped - SSL_set_shutdown(socket.sslHandle, SSL_RECEIVED_SHUTDOWN) - result = false + addRead(socket.fd.AsyncFD, proc (sock: AsyncFD): bool = + retFut.complete(true) + return true + ) + of SSL_ERROR_SYSCALL: + assert flags.isDisconnectionError(osLastError()) + retFut.complete(false) else: - raiseSSLError("Cannot appease SSL.") + raiseSSLError("Cannot handle SSL failure.") + return retFut template sslLoop(socket: AsyncSocket, flags: set[SocketFlag], op: untyped) = @@ -274,20 +274,12 @@ when defineSsl: ErrClearError() # Call the desired operation. opResult = op - let err = - if opResult < 0: - getSslError(socket, opResult.cint) - else: - SSL_ERROR_NONE - # Send any remaining pending SSL data. - await sendPendingSslData(socket, flags) - # If the operation failed, try to see if SSL has some data to read # or write. if opResult < 0: - let fut = appeaseSsl(socket, flags, err.cint) - yield fut - if not fut.read(): + let err = getSslError(socket, flags, opResult.cint) + let connected = await handleSslFailure(socket, flags, err.cint) + if not connected: # Socket disconnected. if SocketFlag.SafeDisconn in flags: opResult = 0.cint @@ -323,8 +315,7 @@ proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} = discard SSL_set_tlsext_host_name(socket.sslHandle, address) let flags = {SocketFlag.SafeDisconn} - sslSetConnectState(socket.sslHandle) - sslLoop(socket, flags, sslDoHandshake(socket.sslHandle)) + sslLoop(socket, flags, SSL_connect(socket.sslHandle)) template readInto(buf: pointer, size: int, socket: AsyncSocket, flags: set[SocketFlag]): int = @@ -461,7 +452,6 @@ proc send*(socket: AsyncSocket, buf: pointer, size: int, when defineSsl: sslLoop(socket, flags, sslWrite(socket.sslHandle, cast[cstring](buf), size.cint)) - await sendPendingSslData(socket, flags) else: await send(socket.fd.AsyncFD, buf, size, flags) @@ -475,52 +465,9 @@ proc send*(socket: AsyncSocket, data: string, var copy = data sslLoop(socket, flags, sslWrite(socket.sslHandle, cast[cstring](addr copy[0]), copy.len.cint)) - await sendPendingSslData(socket, flags) else: await send(socket.fd.AsyncFD, data, flags) -proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}, - inheritable = defined(nimInheritHandles)): - owned(Future[tuple[address: string, client: AsyncSocket]]) = - ## Accepts a new connection. Returns a future containing the client socket - ## corresponding to that connection and the remote address of the client. - ## - ## If `inheritable` is false (the default), the resulting client socket will - ## not be inheritable by child processes. - ## - ## The future will complete when the connection is successfully accepted. - var retFuture = newFuture[tuple[address: string, client: AsyncSocket]]("asyncnet.acceptAddr") - var fut = acceptAddr(socket.fd.AsyncFD, flags, inheritable) - fut.callback = - proc (future: Future[tuple[address: string, client: AsyncFD]]) = - assert future.finished - if future.failed: - retFuture.fail(future.readError) - else: - let resultTup = (future.read.address, - newAsyncSocket(future.read.client, socket.domain, - socket.sockType, socket.protocol, socket.isBuffered, inheritable)) - retFuture.complete(resultTup) - return retFuture - -proc accept*(socket: AsyncSocket, - flags = {SocketFlag.SafeDisconn}): owned(Future[AsyncSocket]) = - ## Accepts a new connection. Returns a future containing the client socket - ## corresponding to that connection. - ## If `inheritable` is false (the default), the resulting client socket will - ## not be inheritable by child processes. - ## The future will complete when the connection is successfully accepted. - var retFut = newFuture[AsyncSocket]("asyncnet.accept") - var fut = acceptAddr(socket, flags) - fut.callback = - proc (future: Future[tuple[address: string, client: AsyncSocket]]) = - assert future.finished - if future.failed: - retFut.fail(future.readError) - else: - retFut.complete(future.read.client) - return retFut - proc recvLineInto*(socket: AsyncSocket, resString: FutureVar[string], flags = {SocketFlag.SafeDisconn}, maxLength = MaxLineLength) {.async.} = ## Reads a line of data from `socket` into `resString`. @@ -776,9 +723,8 @@ when defineSsl: if socket.sslHandle == nil: raiseSSLError() - socket.bioIn = bioNew(bioSMem()) - socket.bioOut = bioNew(bioSMem()) - sslSetBio(socket.sslHandle, socket.bioIn, socket.bioOut) + if SSL_set_fd(socket.sslHandle, socket.fd) != 1: + raiseSSLError() socket.sslNoShutdown = true @@ -795,6 +741,8 @@ when defineSsl: ## ## **Disclaimer**: This code is not well tested, may be very unsafe and ## prone to security vulnerabilities. + if socket.isSsl: + return wrapSocket(ctx, socket) case handshake @@ -818,6 +766,48 @@ when defineSsl: else: result = getPeerCertificates(socket.sslHandle) +proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}, + inheritable = defined(nimInheritHandles)): + owned(Future[tuple[address: string, client: AsyncSocket]]) {.async.} = + ## Accepts a new connection. Returns a future containing the client socket + ## corresponding to that connection and the remote address of the client. + ## + ## If `inheritable` is false (the default), the resulting client socket will + ## not be inheritable by child processes. + ## + ## The future will complete when the connection is successfully accepted. + let (address, fd) = await acceptAddr(socket.fd.AsyncFD, flags, inheritable) + let client = newAsyncSocket(fd, socket.domain, socket.sockType, + socket.protocol, socket.isBuffered, inheritable) + result = (address, client) + if socket.isSsl: + when defineSsl: + if socket.sslContext == nil: + raiseSSLError("The SSL Context is closed/unset") + wrapSocket(socket.sslContext, result.client) + if result.client.sslHandle == nil: + raiseSslHandleError() + let flags = {SocketFlag.SafeDisconn} + sslLoop(result.client, flags, SSL_accept(result.client.sslHandle)) + +proc accept*(socket: AsyncSocket, + flags = {SocketFlag.SafeDisconn}): owned(Future[AsyncSocket]) = + ## Accepts a new connection. Returns a future containing the client socket + ## corresponding to that connection. + ## If `inheritable` is false (the default), the resulting client socket will + ## not be inheritable by child processes. + ## The future will complete when the connection is successfully accepted. + var retFut = newFuture[AsyncSocket]("asyncnet.accept") + var fut = acceptAddr(socket, flags) + fut.callback = + proc (future: Future[tuple[address: string, client: AsyncSocket]]) = + assert future.finished + if future.failed: + retFut.fail(future.readError) + else: + retFut.complete(future.read.client) + return retFut + proc getSockOpt*(socket: AsyncSocket, opt: SOBool, level = SOL_SOCKET): bool {. tags: [ReadIOEffect].} = ## Retrieves option `opt` as a boolean value. diff --git a/tests/async/t24895.nim b/tests/async/t24895.nim new file mode 100644 index 0000000000..56d0d1268c --- /dev/null +++ b/tests/async/t24895.nim @@ -0,0 +1,79 @@ +discard """ + cmd: "nim $target --hints:on --define:ssl $options $file" +""" + +{.define: ssl.} + +import std/[asyncdispatch, asyncnet, net, openssl] + +var port0: Port +var checked = 0 + +proc server {.async.} = + let sock = newAsyncSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, buffered = true) + doAssert sock != nil + defer: sock.close() + let sslCtx = newContext( + protSSLv23, + verifyMode = CVerifyNone, + certFile = "tests/testdata/mycert.pem", + keyFile = "tests/testdata/mycert.pem" + ) + doAssert sslCtx != nil + defer: sslCtx.destroyContext() + wrapSocket(sslCtx, sock) + #sock.bindAddr(Port 8181) + sock.bindAddr() + port0 = getLocalAddr(sock)[1] + sock.listen() + echo "accept" + let clientSocket = await sock.accept() + defer: clientSocket.close() + wrapConnectedSocket( + sslCtx, clientSocket, handshakeAsServer, "localhost" + ) + let sdata = "x" & newString(41) + let sfut = clientSocket.send(sdata) + let rdata = newString(42) + let rfut = clientSocket.recvInto(addr rdata[0], rdata.len) + echo "send" + await sfut + echo "recv" + let rLen = await rfut # it hang here until the client closes the connection or sends more data + doAssert rLen == 42, $rLen + doAssert rdata[0] == 'x', $rdata[0] + echo "ok" + inc checked + +proc client {.async.} = + let sock = newAsyncSocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, buffered = true) + doAssert sock != nil + defer: sock.close() + let sslCtx = newContext( + protSSLv23, + verifyMode = CVerifyNone + ) + doAssert sslCtx != nil + defer: sslCtx.destroyContext() + wrapSocket(sslCtx, sock) + #await sock.connect("127.0.0.1", Port 8181) + await sock.connect("localhost", port0) + let sdata = "x" & newString(41) + echo "send" + await sock.send(sdata) + let rdata = newString(42) + echo "recv" + let rLen = await sock.recvInto(addr rdata[0], rdata.len) + doAssert rLen == 42, $rLen + doAssert rdata[0] == 'x', $rdata[0] + #await sleepAsync(10_000) + #await sock.send("x") + echo "ok" + inc checked + +discard getGlobalDispatcher() +let serverFut = server() +waitFor client() +waitFor serverFut +doAssert checked == 2 +doAssert not hasPendingOperations() From 0506d5b973ee5bc2dfb1a1001e634c88aa15a2ad Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:08:10 +0800 Subject: [PATCH 095/119] don't warn/error symbols in semGenericStmt/templates (#24907) fixes #24905 fixes #24903 fixes https://github.com/nim-lang/Nim/issues/11805 fixes https://github.com/nim-lang/Nim/issues/15650 In the first phase of generic checking, we cannot warn/error symbols because they can belong a false branch of `when` or there is a `push/pop` options using open symbols. So we cannot decide whether to warn/error or not --- compiler/semexprs.nim | 3 ++ compiler/semgnrc.nim | 3 +- compiler/semtempl.nim | 9 ++---- nimsuggest/tests/tqualified_highlight.nim | 3 -- tests/generics/toptions.nim | 39 +++++++++++++++++++++++ 5 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 tests/generics/toptions.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 55a58c7f04..2824f32f68 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -113,6 +113,8 @@ proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = proc semSymGenericInstantiation(c: PContext, n: PNode, s: PSym): PNode = result = symChoice(c, n, s, scClosed) + if result.kind == nkSym: + markUsed(c, n.info, s) proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode @@ -3288,6 +3290,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType #performProcvarCheck(c, n, s) result = symChoice(c, n, s, scClosed) if result.kind == nkSym: + markUsed(c, n.info, s) markIndirect(c, result.sym) # if isGenericRoutine(result.sym): # localError(c.config, n.info, errInstantiateXExplicitly, s.name.s) diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 135d26ba56..9268498040 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -274,7 +274,8 @@ proc semGenericStmt(c: PContext, n: PNode, result = lookup(c, n, flags, ctx) if result != nil and result.kind == nkSym: assert result.sym != nil - markUsed(c, n.info, result.sym) + incl result.sym.flags, sfUsed + markOwnerModuleAsUsed(c, result.sym) of nkDotExpr: #let luf = if withinMixin notin flags: {checkUndeclared} else: {} #var s = qualifiedLookUp(c, n, luf) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 0fa9a8f067..c424b801f5 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -67,12 +67,9 @@ 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) - if isField: - # possibly not final field sym - incl(s.flags, sfUsed) - markOwnerModuleAsUsed(c, s) - else: - markUsed(c, info, s) + # possibly not final field sym + incl(s.flags, sfUsed) + markOwnerModuleAsUsed(c, s) onUse(info, s) else: result = n diff --git a/nimsuggest/tests/tqualified_highlight.nim b/nimsuggest/tests/tqualified_highlight.nim index b83669e72b..67cb583176 100644 --- a/nimsuggest/tests/tqualified_highlight.nim +++ b/nimsuggest/tests/tqualified_highlight.nim @@ -6,9 +6,6 @@ discard """ $nimsuggest --tester $file >highlight $1 highlight;;skProc;;1;;7;;4 -highlight;;skProc;;1;;7;;4 -highlight;;skTemplate;;2;;7;;4 -highlight;;skTemplate;;2;;7;;4 highlight;;skTemplate;;2;;7;;4 highlight;;skFunc;;3;;8;;1 """ diff --git a/tests/generics/toptions.nim b/tests/generics/toptions.nim new file mode 100644 index 0000000000..5bd7e0dfa0 --- /dev/null +++ b/tests/generics/toptions.nim @@ -0,0 +1,39 @@ +discard """ + matrix: "--warningAsError:Deprecated" +""" + +block: # bug #24905 + proc y() {.deprecated.} = discard + proc v(_: int | int) = + {.push warning[Deprecated]: off.} + y() + {.pop.} + + v(1) + +block: # bug #24903 + block: + proc y() {.deprecated.} = discard + proc m(_: int | int) = + when false: y() + + block: + proc y() {.error.} = discard + proc m(_: int | int) = + when false: y() + + block: + proc y() {.error.} = discard + proc m(_: int | int) = + when true: y() + +block: # bug #15650 + proc bar() {.deprecated.} = discard + + template foo() = + when false: + bar() + else: + discard + + foo() From b61a614e8af731020bb4aecb0e3f41d32c41f46f Mon Sep 17 00:00:00 2001 From: Alfred Morgan Date: Wed, 30 Apr 2025 04:00:23 -0700 Subject: [PATCH 096/119] Patch 24922 (#24923) --- lib/posix/posix.nim | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 15ce82eb32..eb384fb342 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -215,6 +215,11 @@ when defined(osx): # 2001 POSIX evidently does not concern Apple # present size & has no good reason to call this unless it is growing. if fcntl(a1, F_PREALLOCATE, fst.addr) != cint(-1): ftruncate(a1, a2 + a3) else: cint(-1) +elif defined(openbsd): + proc posix_fallocate*(a1: cint, a2, a3: Off): cint = + # above assumption: "has no good reason to call this unless it is growing." + # man ftruncate "it will be extended as if by writing bytes with the value zero." + return ftruncate(a1, a2 + a3) else: proc posix_fallocate*(a1: cint, a2, a3: Off): cint {. importc, header: "".} From b5b7a127fd92349a1517d2c7e7a4f25a532fac59 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Wed, 30 Apr 2025 11:17:11 -0400 Subject: [PATCH 097/119] Fix `warning[Uninit]` triggers in `strutils` (#24921) --- lib/pure/strutils.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4e2ae306f8..c218ac1c53 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1081,7 +1081,7 @@ func fromBin*[T: SomeInteger](s: string): T = doAssert fromBin[uint8](s) == 153 doAssert s.fromBin[:int16] == 0b1110_1110_1001_1001'i16 doAssert s.fromBin[:uint64] == 1216933529'u64 - + result = T(0) let p = parseutils.parseBin(s, result) if p != s.len or p == 0: raise newException(ValueError, "invalid binary integer: " & s) @@ -1104,7 +1104,7 @@ func fromOct*[T: SomeInteger](s: string): T = doAssert fromOct[uint8](s) == 255'u8 doAssert s.fromOct[:int16] == 24063'i16 doAssert s.fromOct[:uint64] == 21913087'u64 - + result = T(0) let p = parseutils.parseOct(s, result) if p != s.len or p == 0: raise newException(ValueError, "invalid oct integer: " & s) @@ -1127,7 +1127,7 @@ func fromHex*[T: SomeInteger](s: string): T = doAssert fromHex[uint8](s) == 246'u8 doAssert s.fromHex[:int16] == -29194'i16 doAssert s.fromHex[:uint64] == 305499638'u64 - + result = T(0) let p = parseutils.parseHex(s, result) if p != s.len or p == 0: raise newException(ValueError, "invalid hex integer: " & s) From f56568d851eb7f859e6e355495c2be28ac9819e9 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 1 May 2025 13:49:46 +0800 Subject: [PATCH 098/119] fixes address of sink parameters (#24924) In `semExprWithType`: `if result.typ.kind in {tyVar, tyLent}: result = newDeref(result)` derefed `var`/`lent`. Since it is not done for `sink`, we need to skip `tySink` in the corresponding procs --- compiler/magicsys.nim | 2 +- compiler/semmagic.nim | 2 +- tests/destructor/tsink.nim | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compiler/magicsys.nim b/compiler/magicsys.nim index 47a71d56cd..57b6a001ef 100644 --- a/compiler/magicsys.nim +++ b/compiler/magicsys.nim @@ -166,4 +166,4 @@ proc makeAddr*(n: PNode; idgen: IdGenerator): PNode = result = n else: result = newTree(nkHiddenAddr, n) - result.typ() = makePtrType(n.typ, idgen) + result.typ() = makePtrType(n.typ.skipTypes({tySink}), idgen) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index b42e6e26ec..0b71783575 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -38,7 +38,7 @@ proc semAddr(c: PContext; n: PNode): PNode = if isAssignable(c, x) notin {arLValue, arLocalLValue, arAddressableConst, arLentValue}: localError(c.config, n.info, errExprHasNoAddress) result.add x - result.typ() = makePtrType(c, x.typ) + result.typ() = makePtrType(c, x.typ.skipTypes({tySink})) proc semTypeOf(c: PContext; n: PNode): PNode = var m = BiggestInt 1 # typeOfIter diff --git a/tests/destructor/tsink.nim b/tests/destructor/tsink.nim index e8750ad7cc..754c737916 100644 --- a/tests/destructor/tsink.nim +++ b/tests/destructor/tsink.nim @@ -68,3 +68,11 @@ block: # bug #24175 static: foo() foo() + +proc create(value: sink int): ptr int = + let s = addr value + result = addr value + result = s + + +let xxx = create(12) \ No newline at end of file From 98ec87d65e678ccf3aee9f59c729607089e7cece Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 4 May 2025 09:29:59 +0800 Subject: [PATCH 099/119] fixes #23355; pop optionStack when exiting scopes (#24926) fixes #23355 --- compiler/ast.nim | 1 + compiler/lookups.nim | 5 ++++- tests/errmsgs/t23355.nim | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/errmsgs/t23355.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index e35a0b2031..3d7dcbdfc7 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -684,6 +684,7 @@ type symbols*: TStrTable parent*: PScope allowPrivateAccess*: seq[PSym] # # enable access to private fields + optionStackLen*: int PScope* = ref TScope diff --git a/compiler/lookups.nim b/compiler/lookups.nim index ec5fdd69b0..34f65973cf 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -75,10 +75,13 @@ proc addUniqueSym*(scope: PScope, s: PSym): PSym = proc openScope*(c: PContext): PScope {.discardable.} = result = PScope(parent: c.currentScope, symbols: initStrTable(), - depthLevel: c.scopeDepth + 1) + depthLevel: c.scopeDepth + 1, + optionStackLen: c.optionStack.len) c.currentScope = result proc rawCloseScope*(c: PContext) = + if c.currentScope.optionStackLen >= 1: + c.optionStack.setLen(c.currentScope.optionStackLen) c.currentScope = c.currentScope.parent proc closeScope*(c: PContext) = diff --git a/tests/errmsgs/t23355.nim b/tests/errmsgs/t23355.nim new file mode 100644 index 0000000000..281d098eb8 --- /dev/null +++ b/tests/errmsgs/t23355.nim @@ -0,0 +1,11 @@ +discard """ + errormsg: "{.pop.} without a corresponding {.push.}" +""" + +block: + {.push raises: [].} + +proc f() = + {.pop.} + +proc g() = raise newException(ValueError, "") \ No newline at end of file From 8b82f5de3848f195305d297a03d0f6796e0ab121 Mon Sep 17 00:00:00 2001 From: Amjad Ben Hedhili Date: Mon, 5 May 2025 07:17:36 +0100 Subject: [PATCH 100/119] Remove horizontal scrolling on mobile (#24927) --- doc/nimdoc.css | 11 ++++++++++- nimdoc/testproject/expected/nimdoc.out.css | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/nimdoc.css b/doc/nimdoc.css index d50f766ed4..2032019c01 100644 --- a/doc/nimdoc.css +++ b/doc/nimdoc.css @@ -120,11 +120,17 @@ Modified by Boyd Greenfield and narimiran } html { + overflow-x: hidden; + max-width: 100%; + box-sizing: border-box; font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { + overflow-x: hidden; + max-width: 100%; + box-sizing: border-box; font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: 400; font-size: 1.125em; @@ -561,6 +567,8 @@ blockquote.markdown-quote { padding-left: 3px; padding-right: 3px; border-radius: 4px; + white-space: normal; + word-break: break-all; } span.tok { @@ -580,13 +588,14 @@ pre { display: inline-block; box-sizing: border-box; min-width: 100%; + max-width: 100%; padding: 0.5em; margin-top: 0.5em; margin-bottom: 0.5em; font-size: 0.85em; white-space: pre !important; overflow-y: hidden; - overflow-x: visible; + overflow-x: auto; background-color: var(--secondary-background); border: 1px solid var(--border); -webkit-border-radius: 6px; diff --git a/nimdoc/testproject/expected/nimdoc.out.css b/nimdoc/testproject/expected/nimdoc.out.css index d50f766ed4..2032019c01 100644 --- a/nimdoc/testproject/expected/nimdoc.out.css +++ b/nimdoc/testproject/expected/nimdoc.out.css @@ -120,11 +120,17 @@ Modified by Boyd Greenfield and narimiran } html { + overflow-x: hidden; + max-width: 100%; + box-sizing: border-box; font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { + overflow-x: hidden; + max-width: 100%; + box-sizing: border-box; font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; font-weight: 400; font-size: 1.125em; @@ -561,6 +567,8 @@ blockquote.markdown-quote { padding-left: 3px; padding-right: 3px; border-radius: 4px; + white-space: normal; + word-break: break-all; } span.tok { @@ -580,13 +588,14 @@ pre { display: inline-block; box-sizing: border-box; min-width: 100%; + max-width: 100%; padding: 0.5em; margin-top: 0.5em; margin-bottom: 0.5em; font-size: 0.85em; white-space: pre !important; overflow-y: hidden; - overflow-x: visible; + overflow-x: auto; background-color: var(--secondary-background); border: 1px solid var(--border); -webkit-border-radius: 6px; From 82553384d150496089ae41cc68778c7f843e0b2a Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 6 May 2025 10:36:20 +0300 Subject: [PATCH 101/119] bring back id table algorithm instead of std table [backport:2.2] (#24930) refs #24929, partially reverts #23403 Instead of using `Table[ItemId, T]`, the old algorithm is brought back into `TIdTable[T]` to prevent a performance regression. The inheritance removal from #23403 still holds, only `ItemId`s are stored. --- compiler/ast.nim | 44 +++++++++++++++++++-------- compiler/astalgo.nim | 64 +++++++++++++++++++++++++++++++++++++++ compiler/layeredtable.nim | 9 +++--- compiler/semcall.nim | 6 ++-- compiler/semdata.nim | 6 ++-- compiler/semtypinst.nim | 4 +-- compiler/transf.nim | 4 +-- 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index 3d7dcbdfc7..13f7890bcd 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -796,6 +796,15 @@ type TPairSeq* = seq[TPair] + TIdPair*[T] = object + key*: ItemId + val*: T + + TIdPairSeq*[T] = seq[TIdPair[T]] + TIdTable*[T] = object + counter*: int + data*: TIdPairSeq[T] + TNodePair* = object h*: Hash # because it is expensive to compute! key*: PNode @@ -940,9 +949,11 @@ proc getPIdent*(a: PNode): PIdent {.inline.} = const moduleShift = when defined(cpu32): 20 else: 24 -template id*(a: PType | PSym): int = +template toId*(a: ItemId): int = let x = a - (x.itemId.module.int shl moduleShift) + x.itemId.item.int + (x.module.int shl moduleShift) + x.item.int + +template id*(a: PType | PSym): int = toId(a.itemId) type IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here. @@ -1269,6 +1280,11 @@ proc copyStrTable*(dest: var TStrTable, src: TStrTable) = setLen(dest.data, src.data.len) for i in 0..high(src.data): dest.data[i] = src.data[i] +proc copyIdTable*[T](dest: var TIdTable[T], src: TIdTable[T]) = + dest.counter = src.counter + newSeq(dest.data, src.data.len) + for i in 0..high(src.data): dest.data[i] = src.data[i] + proc copyObjectSet*(dest: var TObjectSet, src: TObjectSet) = dest.counter = src.counter setLen(dest.data, src.data.len) @@ -1607,6 +1623,16 @@ proc initStrTable*(): TStrTable = result = TStrTable(counter: 0) newSeq(result.data, StartSize) +proc initIdTable*[T](): TIdTable[T] = + result = TIdTable[T](counter: 0) + newSeq(result.data, StartSize) + +proc resetIdTable*[T](x: var TIdTable[T]) = + x.counter = 0 + # clear and set to old initial size: + setLen(x.data, 0) + setLen(x.data, StartSize) + proc initObjectSet*(): TObjectSet = result = TObjectSet(counter: 0) newSeq(result.data, StartSize) @@ -2135,14 +2161,8 @@ proc isTrue*(n: PNode): bool = n.kind == nkIntLit and n.intVal != 0 type - TypeMapping* = Table[ItemId, PType] - SymMapping* = Table[ItemId, PSym] + TypeMapping* = TIdTable[PType] + SymMapping* = TIdTable[PSym] -template idTableGet*(tab: typed; key: PSym | PType): untyped = tab.getOrDefault(key.itemId) -template idTablePut*(tab: typed; key, val: PSym | PType) = tab[key.itemId] = val - -template initSymMapping*(): Table[ItemId, PSym] = initTable[ItemId, PSym]() -template initTypeMapping*(): Table[ItemId, PType] = initTable[ItemId, PType]() - -template resetIdTable*(tab: Table[ItemId, PSym]) = tab.clear() -template resetIdTable*(tab: Table[ItemId, PType]) = tab.clear() +template initSymMapping*(): SymMapping = initIdTable[PSym]() +template initTypeMapping*(): TypeMapping = initIdTable[PType]() diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 7a9892f78a..14dc7c5994 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -713,6 +713,70 @@ iterator items*(tab: TStrTable): PSym = yield s s = nextIter(it, tab) +proc isNil(x: ItemId): bool {.inline.} = + x.module == 0 and x.item == 0 + +proc hasEmptySlot[T](data: TIdPairSeq[T]): bool = + for h in 0..high(data): + if isNil(data[h].key): + return true + result = false + +proc idTableRawGet[T](t: TIdTable[T], key: int): int = + var h: Hash + h = key and high(t.data) # start with real hash value + while not isNil(t.data[h].key): + if toId(t.data[h].key) == key: + return h + h = nextTry(h, high(t.data)) + result = - 1 + +proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T = + var index = idTableRawGet(t, toId(key)) + if index >= 0: result = t.data[index].val + else: result = default(T) + +template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T = + getOrDefault(t, key.itemId) + +proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) = + var h: Hash + let keyId = toId(key) + h = keyId and high(data) + while not isNil(data[h].key): + assert(toId(data[h].key) != keyId) + h = nextTry(h, high(data)) + assert(isNil(data[h].key)) + data[h].key = key + data[h].val = val + +proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) = + var + index: int + n: TIdPairSeq[T] + index = idTableRawGet(t, toId(key)) + if index >= 0: + assert(not isNil(t.data[index].key)) + t.data[index].val = val + else: + if mustRehash(t.data.len, t.counter): + newSeq(n, t.data.len * GrowthFactor) + for i in 0..high(t.data): + if not isNil(t.data[i].key): + idTableRawInsert(n, t.data[i].key, t.data[i].val) + assert(hasEmptySlot(n)) + swap(t.data, n) + idTableRawInsert(t.data, key, val) + inc(t.counter) + +template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) = + t[key.itemId] = val + +iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] = + for i in 0..high(t.data): + if not isNil(t.data[i].key): + yield (t.data[i].key, t.data[i].val) + proc initIITable(x: var TIITable) = x.counter = 0 newSeq(x.data, StartSize) diff --git a/compiler/layeredtable.nim b/compiler/layeredtable.nim index 61a86cff84..248ec4bcf2 100644 --- a/compiler/layeredtable.nim +++ b/compiler/layeredtable.nim @@ -1,5 +1,5 @@ import std/[tables] -import ast +import ast, astalgo type LayeredIdTableObj* {.acyclic.} = object @@ -28,14 +28,15 @@ proc shallowCopy*(pt: LayeredIdTable): LayeredIdTable {.inline.} = ## copies only the type bindings of the current layer, but not any parent layers, ## useful for write-only bindings result = LayeredIdTable(topLayer: pt.topLayer, nextLayer: pt.nextLayer, previousLen: pt.previousLen) + #copyIdTable(result.topLayer, pt.topLayer) proc currentLen*(pt: LayeredIdTable): int = ## the sum of the cached total binding count of the parents and ## the current binding count, just used to track if bindings were added - pt.previousLen + pt.topLayer.len + pt.previousLen + pt.topLayer.counter proc newTypeMapLayer*(pt: LayeredIdTable): LayeredIdTable = - result = LayeredIdTable(topLayer: initTable[ItemId, PType](), previousLen: pt.currentLen) + result = LayeredIdTable(topLayer: initTypeMapping(), previousLen: pt.currentLen) when useRef: result.nextLayer = pt else: @@ -56,7 +57,7 @@ proc setToPreviousLayer*(pt: var LayeredIdTable) {.inline.} = iterator pairs*(pt: LayeredIdTable): (ItemId, PType) = var tm = pt while true: - for (k, v) in pairs(tm.topLayer): + for (k, v) in idTablePairs(tm.topLayer): yield (k, v) if tm.nextLayer == nil: break diff --git a/compiler/semcall.nim b/compiler/semcall.nim index e3c6ea851b..866ddbe68f 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -909,15 +909,15 @@ proc semOverloadedCall(c: PContext, n, nOrig: PNode, if c.inGenericContext > 0 and c.matchedConcept == nil: result = semGenericStmt(c, n) result.typ() = makeTypeFromExpr(c, result.copyTree) + elif efNoUndeclared in flags: + result = nil elif efExplain notin flags: # repeat the overload resolution, # this time enabling all the diagnostic output (this should fail again) result = semOverloadedCall(c, n, nOrig, filter, flags + {efExplain}) - elif efNoUndeclared notin flags: - result = nil - notFoundError(c, n, errors) else: result = nil + notFoundError(c, n, errors) proc explicitGenericInstError(c: PContext; n: PNode): PNode = localError(c.config, getCallLineInfo(n), errCannotInstantiateX % renderTree(n)) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index fa697f90cd..14ca22dcc5 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -17,7 +17,7 @@ when defined(nimPreviewSlimSystem): import options, ast, msgs, idents, renderer, magicsys, vmdef, modulegraphs, lineinfos, pathutils, layeredtable, - types, lowerings, trees, parampatterns + types, lowerings, trees, parampatterns, astalgo import ic / ic @@ -42,7 +42,7 @@ type breakInLoop*: bool # whether we are in a loop without block next*: PProcCon # used for stacking procedure contexts mappingExists*: bool - mapping*: Table[ItemId, PSym] + mapping*: SymMapping caseContext*: seq[tuple[n: PNode, idx: int]] localBindStmts*: seq[PNode] @@ -260,7 +260,7 @@ proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next proc put*(p: PProcCon; key, val: PSym) = if not p.mappingExists: - p.mapping = initTable[ItemId, PSym]() + p.mapping = initSymMapping() p.mappingExists = true #echo "put into table ", key.info p.mapping[key.itemId] = val diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index daee9ba4fc..a615aeee94 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -68,8 +68,8 @@ type TReplTypeVars* = object c*: PContext typeMap*: LayeredIdTable # map PType to PType - symMap*: SymMapping # map PSym to PSym - localCache*: TypeMapping # local cache for remembering already replaced + symMap*: SymMapping # map PSym to PSym + localCache*: TypeMapping # local cache for remembering already replaced # types during instantiation of meta types # (they are not stored in the global cache) info*: TLineInfo diff --git a/compiler/transf.nim b/compiler/transf.nim index 89911daf15..a2090af841 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -40,7 +40,7 @@ import closureiters, lambdalifting type PTransCon = ref object # part of TContext; stackable - mapping: Table[ItemId, PNode] # mapping from symbols to nodes + mapping: TIdTable[PNode] # mapping from symbols to nodes owner: PSym # current owner forStmt: PNode # current for stmt forLoopBody: PNode # transformed for loop body @@ -78,7 +78,7 @@ proc newTransNode(kind: TNodeKind, n: PNode, proc newTransCon(owner: PSym): PTransCon = assert owner != nil - result = PTransCon(mapping: initTable[ItemId, PNode](), owner: owner) + result = PTransCon(mapping: initIdTable[PNode](), owner: owner) proc pushTransCon(c: PTransf, t: PTransCon) = t.next = c.transCon From 433b725cbb65eb1b66801a251b75b39011c22984 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 6 May 2025 15:46:18 +0800 Subject: [PATCH 102/119] fixes #21975; Pragma block disabling warning has effect beyond block (#24934) fixes #21975 --- compiler/semstmts.nim | 17 +++++++++++++++++ tests/pragmas/tpragmablock.nim | 11 +++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/pragmas/tpragmablock.nim diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 7039062306..fabf3dee60 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -2821,9 +2821,24 @@ proc recursiveSetFlag(n: PNode, flag: TNodeFlag) = for i in 0.. Date: Tue, 6 May 2025 15:46:45 +0800 Subject: [PATCH 103/119] improvements for semdata (#24933) --- compiler/semdata.nim | 66 +++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 14ca22dcc5..b31395ed55 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -292,22 +292,24 @@ proc considerGenSyms*(c: PContext; n: PNode) = considerGenSyms(c, n[i]) proc newOptionEntry*(conf: ConfigRef): POptionEntry = - new(result) - result.options = conf.options - result.defaultCC = ccNimCall - result.dynlib = nil - result.notes = conf.notes - result.warningAsErrors = conf.warningAsErrors + result = POptionEntry( + options: conf.options, + defaultCC: ccNimCall, + dynlib: nil, + notes: conf.notes, + warningAsErrors: conf.warningAsErrors + ) proc pushOptionEntry*(c: PContext): POptionEntry = - new(result) - var prev = c.optionStack[^1] - result.options = c.config.options - result.defaultCC = prev.defaultCC - result.dynlib = prev.dynlib - result.notes = c.config.notes - result.warningAsErrors = c.config.warningAsErrors - result.features = c.features + let prev = c.optionStack[^1] + result = POptionEntry( + options: c.config.options, + defaultCC: prev.defaultCC, + dynlib: prev.dynlib, + notes: c.config.notes, + warningAsErrors: c.config.warningAsErrors, + features: c.features + ) c.optionStack.add(result) proc popOptionEntry*(c: PContext) = @@ -318,22 +320,23 @@ proc popOptionEntry*(c: PContext) = c.optionStack.setLen(c.optionStack.len - 1) proc newContext*(graph: ModuleGraph; module: PSym): PContext = - new(result) - result.optionStack = @[newOptionEntry(graph.config)] - result.libs = @[] - result.module = module - result.friendModules = @[module] - result.converters = @[] - result.patterns = @[] - result.includedFiles = initIntSet() - result.pureEnumFields = initStrTable() - result.userPragmas = initStrTable() - result.generics = @[] - result.unknownIdents = initIntSet() - result.cache = graph.cache - result.graph = graph - result.signatures = initStrTable() - result.features = graph.config.features + result = PContext( + optionStack: @[newOptionEntry(graph.config)], + libs: @[], + module: module, + friendModules: @[module], + converters: @[], + patterns: @[], + includedFiles: initIntSet(), + pureEnumFields: initStrTable(), + userPragmas: initStrTable(), + generics: @[], + unknownIdents: initIntSet(), + cache: graph.cache, + graph: graph, + signatures: initStrTable(), + features: graph.config.features + ) if graph.config.symbolFiles != disabledSf: let id = module.position if graph.config.cmd != cmdM: @@ -397,8 +400,7 @@ proc reexportSym*(c: PContext; s: PSym) = addReexport(c.encoder, c.packedRepr, s) proc newLib*(kind: TLibKind): PLib = - new(result) - result.kind = kind #result.syms = initObjectSet() + result = PLib(kind: kind) #result.syms = initObjectSet() proc addToLib*(lib: PLib, sym: PSym) = #if sym.annex != nil and not isGenericRoutine(sym): From 59ceff4f1afdbc35cfc1dd679ca31369e71b3873 Mon Sep 17 00:00:00 2001 From: Amjad Ben Hedhili Date: Tue, 6 May 2025 13:09:03 +0100 Subject: [PATCH 104/119] Add min/max overloads with comparison functions (#23595) `min`, `max`, `minmax`, `minIndex` and `maxIndex` --- changelog.md | 2 +- lib/pure/collections/sequtils.nim | 48 ++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 6529a26f1f..dc225844b8 100644 --- a/changelog.md +++ b/changelog.md @@ -34,7 +34,7 @@ errors. [//]: # "Changes:" - `std/math` The `^` symbol now supports floating-point as exponent in addition to the Natural type. - +- `min`, `max`, and `sequtils`' `minIndex`, `maxIndex` and `minmax` for `openArray`s now accept a comparison function. - `system.substr` implementation now uses `copymem` (wrapped C `memcpy`) for copying data, if available at compilation. - `system.newStringUninit` is now considered free of side-effects allowing it to be used with `--experimental:strictFuncs`. diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 983d3101cb..42d54c8392 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -231,6 +231,18 @@ func deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = for itm in items(s): if not result.contains(itm): result.add(itm) +proc min*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} = + ## The minimum value of `x`. + result = x[0] + for i in 1..high(x): + if cmp(x[i], result) < 0: result = x[i] + +proc max*[T](x: openArray[T], cmp: proc(a, b: T): int): T {.effectsOf: cmp.} = + ## The maximum value of `x`. + result = x[0] + for i in 1..high(x): + if cmp(result, x[i]) < 0: result = x[i] + func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} = ## Returns the index of the minimum value of `s`. ## `T` needs to have a `<` operator. @@ -248,6 +260,20 @@ func minIndex*[T](s: openArray[T]): int {.since: (1, 1).} = for i in 1..high(s): if s[i] < s[result]: result = i +func minIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} = + ## Returns the index of the minimum value of `s`. + runnableExamples: + import std/sugar + + let s1 = @["foo","bar", "hello"] + let s2 = @[2..4, 1..3, 6..10] + assert minIndex(s1, proc (a, b: string): int = a.len - b.len) == 0 + assert minIndex(s2, (a, b) => a.a - b.a) == 1 + + for i in 1..high(s): + if cmp(s[i], s[result]) < 0: result = i + + func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} = ## Returns the index of the maximum value of `s`. ## `T` needs to have a `<` operator. @@ -265,15 +291,35 @@ func maxIndex*[T](s: openArray[T]): int {.since: (1, 1).} = for i in 1..high(s): if s[i] > s[result]: result = i +func maxIndex*[T](s: openArray[T], cmp: proc(a, b: T): int): int {.effectsOf: cmp.} = + ## Returns the index of the maximum value of `s`. + runnableExamples: + import std/sugar + + let s1 = @["foo","bar", "hello"] + let s2 = @[2..4, 1..3, 6..10] + assert maxIndex(s1, proc (a, b: string): int = a.len - b.len) == 2 + assert maxIndex(s2, (a, b) => a.a - b.a) == 2 + + for i in 1..high(s): + if cmp(s[result], s[i]) < 0: result = i + func minmax*[T](x: openArray[T]): (T, T) = ## The minimum and maximum values of `x`. `T` needs to have a `<` operator. var l = x[0] var h = x[0] for i in 1..high(x): if x[i] < l: l = x[i] - if h < x[i]: h = x[i] + elif h < x[i]: h = x[i] result = (l, h) +func minmax*[T](x: openArray[T], cmp: proc(a, b: T): int): (T, T) {.effectsOf: cmp.} = + ## The minimum and maximum values of `x`. + result = (x[0], x[0]) + for i in 1..high(x): + if cmp(x[i], result[0]) < 0: result[0] = x[i] + elif cmp(result[1], x[i]) < 0: result[1] = x[i] + template zipImpl(s1, s2, retType: untyped): untyped = proc zip*[S, T](s1: openArray[S], s2: openArray[T]): retType = From 42a4adb4a5a19f338a1c0524fc98546f9116d2e2 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sat, 10 May 2025 14:26:21 +0800 Subject: [PATCH 105/119] fixes #24941; missing < (less than), cmp for cstring (#24942) fixes #24941 now `cmp` can select the correct version of cstring comparsions --- compiler/vmops.nim | 6 ++++++ lib/system.nim | 41 ++++++++++++++++++++++++++++++++++++++-- tests/sets/t15435.nim | 2 +- tests/stdlib/tsystem.nim | 40 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 3 deletions(-) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index 9403fe1e4b..f3d349e803 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -335,6 +335,12 @@ proc registerAdditionalOps*(c: PCtx) = registerCallback c, "stdlib.hashes.hashVmImplByte", hashVmImplByte registerCallback c, "stdlib.hashes.hashVmImplChar", hashVmImplByte + registerCallback c, "stdlib.system.ltCStringVm", proc (a: VmArgs) = + setResult(a, getString(a, 0) < getString(a, 1)) + + registerCallback c, "stdlib.system.leCStringVm", proc (a: VmArgs) = + setResult(a, getString(a, 0) <= getString(a, 1)) + if optBenchmarkVM in c.config.globalOptions or vmopsDanger in c.config.features: wrap0(cpuTime, timesop) else: diff --git a/lib/system.nim b/lib/system.nim index 0f8e062978..a77b59e74e 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2722,16 +2722,53 @@ proc procCall*(x: untyped) {.magic: "ProcCall", compileTime.} = ## ``` discard +proc strcmp(a, b: cstring): cint {.noSideEffect, + importc, header: "".} proc `==`*(x, y: cstring): bool {.magic: "EqCString", noSideEffect, inline.} = ## Checks for equality between two `cstring` variables. - proc strcmp(a, b: cstring): cint {.noSideEffect, - importc, header: "".} if pointer(x) == pointer(y): result = true elif pointer(x) == nil or pointer(y) == nil: result = false else: result = strcmp(x, y) == 0 +func ltCStringVm(x, y: cstring): bool {.inline.} = + discard "implemented in the vm ops" + +func leCStringVm(x, y: cstring): bool {.inline.} = + discard "implemented in the vm ops" + +func `<`*(x, y: cstring): bool {.inline.} = + if x == y: + result = false + elif x == nil: + result = true + elif y == nil: + result = false + else: + when nimvm: + result = ltCStringVm(x, y) + else: + when defined(js): + result = pointer(x) < pointer(y) + else: + result = strcmp(x, y) < 0 + +func `<=`*(x, y: cstring): bool {.inline.} = + if x == y: result = true + elif x == nil: + result = true + elif y == nil: + result = false + else: + when nimvm: + result = leCStringVm(x, y) + else: + when defined(js): + result = pointer(x) <= pointer(y) + else: + result = strcmp(x, y) <= 0 + template closureScope*(body: untyped): untyped = ## Useful when creating a closure in a loop to capture local loop variables by ## their current iteration values. diff --git a/tests/sets/t15435.nim b/tests/sets/t15435.nim index 5ead7e641b..46a7342226 100644 --- a/tests/sets/t15435.nim +++ b/tests/sets/t15435.nim @@ -7,7 +7,7 @@ proc `<`[T](x, y: set[T]): bool first type mismatch at position: 2 required type for y: set[T] but expression 'x' is of type: set[range 1..5(uint8)] -20 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them +21 other mismatching symbols have been suppressed; compile with --showAllMismatches:on to see them expression: {1'u8, 5} < x''' """ diff --git a/tests/stdlib/tsystem.nim b/tests/stdlib/tsystem.nim index f634ce0c23..343021bd3d 100644 --- a/tests/stdlib/tsystem.nim +++ b/tests/stdlib/tsystem.nim @@ -198,3 +198,43 @@ block: # bug #6549 doAssert $v == "18446744073709551615" doAssert $float32(v) == "1.8446744e+19" doAssert $float64(v) == "1.8446744073709552e+19" + +proc bar2() = + var a = cstring"1233" + var b = cstring"1233" + + if a == b: doAssert not(a b) + doAssert a >= b + doAssert not (a < b) + + var c = cstring"a1345" + var d = cstring"hwr" + doAssert c < d + doAssert c <= d + doAssert not (c > d) + doAssert not (c > d) + doAssert c != d + doAssert not (c == d) + + when not defined(js): + doAssert cstring(nil) < cstring"" + doAssert cstring(nil) <= cstring"" + doAssert not (cstring"" < cstring(nil)) + doAssert not (cstring"" <= cstring(nil)) + doAssert not (cstring(nil) > cstring"") + doAssert not (cstring(nil) >= cstring"") + doAssert cstring"" > cstring(nil) + doAssert cstring"" >= cstring(nil) + doAssert not (cstring"" == cstring(nil)) + doAssert cstring(nil) != cstring"" + doAssert cstring(nil) == cstring(nil) + doAssert cstring(nil) >= cstring(nil) + doAssert cstring("") >= cstring("") + doAssert cstring(nil) <= cstring(nil) + doAssert cstring("") <= cstring("") + +static: bar2() +bar2() From 6f5e5811fc876fb713ac34608b5a40b27c96d01f Mon Sep 17 00:00:00 2001 From: bptato <60043228+bptato@users.noreply.github.com> Date: Sat, 10 May 2025 13:26:00 +0200 Subject: [PATCH 106/119] Correct nfds_t size on Android (#24647) Turns out bionic uses an unsigned int (unlike other Linux libcs). (See .) --- lib/posix/posix.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index eb384fb342..9239ca1482 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -1104,7 +1104,9 @@ when not defined(lwip): # Meanwhile, BSD derivatives had used unsigned int; we will use this # for the else case, because it is more widely cloned than SVR4's # behavior. - when defined(linux) or defined(haiku): + # Finally, bionic libc (Android) also uses unsigned int, despite being + # a Linux. + when defined(linux) and not defined(android) or defined(haiku): type Tnfds* {.importc: "nfds_t", header: "".} = culong elif defined(zephyr): From 6c2f78a19f7ee32a8ae17360f1d59044334b224b Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 11 May 2025 12:40:46 +0800 Subject: [PATCH 107/119] rework tags (#24944) recent ctags changes: https://github.com/nim-lang/Nim/pull/24317 ref https://forum.nim-lang.org/t/12879 --- compiler/docgen.nim | 7 ++++++- tests/tools/tctags2.nim | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/tools/tctags2.nim diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 4149edcbc8..1ea8eafd5d 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -1892,6 +1892,9 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = else: #echo getOutFile(gProjectFull, JsonExt) let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt) + conf.outFile = filename.relativeTo(conf.outDir) + let dir = filename.splitFile.dir + createDir(dir) try: writeFile(filename, content) except IOError: @@ -1912,8 +1915,10 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) = if optStdout in d.conf.globalOptions: write(stdout, content) else: - #echo getOutFile(gProjectFull, TagsExt) let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt) + conf.outFile = filename.relativeTo(conf.outDir) + let dir = filename.splitFile.dir + createDir(dir) try: writeFile(filename, content) except IOError: diff --git a/tests/tools/tctags2.nim b/tests/tools/tctags2.nim new file mode 100644 index 0000000000..16299544f3 --- /dev/null +++ b/tests/tools/tctags2.nim @@ -0,0 +1,11 @@ +discard """ + cmd: '''nim ctags $file''' + action: "compile" +""" + +type + Foo = object + +proc hello() = discard + +proc `$`(x: Foo): string = "foo" From 808061024833070f98e875020b4e3461e7b0d936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20M=20G=C3=B3mez?= Date: Sun, 11 May 2025 05:41:09 +0100 Subject: [PATCH 108/119] Initial implementation for `nimsuggest` `import` support (#24937) Co-authored-by: Andreas Rumpf --- compiler/semexprs.nim | 1 + compiler/suggest.nim | 127 +++++++++++++++++++++++++++++++++- nimsuggest/tests/timport1.nim | 7 ++ nimsuggest/tests/timport2.nim | 9 +++ nimsuggest/tests/timport3.nim | 9 +++ 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 nimsuggest/tests/timport1.nim create mode 100644 nimsuggest/tests/timport2.nim create mode 100644 nimsuggest/tests/timport3.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 2824f32f68..2a3a4d13d4 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -3540,6 +3540,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType of nkMacroDef: result = semMacroDef(c, n) of nkTemplateDef: result = semTemplateDef(c, n) of nkImportStmt: + trySuggestModuleNames(c, n) # this particular way allows 'import' in a 'compiles' context so that # template canImport(x): bool = # compiles: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index a5213086bd..a1a477ec8a 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -35,7 +35,7 @@ import prefixmatches, suggestsymdb from wordrecg import wDeprecated, wError, wAddr, wYield -import std/[algorithm, sets, parseutils, tables] +import std/[algorithm, sets, parseutils, tables, os] when defined(nimsuggest): import pathutils # importer @@ -43,6 +43,12 @@ when defined(nimsuggest): const sep = '\t' +type + ImportContext = object + isMultiImport: bool # True if we're in a [...] context + baseDir: string # e.g., "folder/" in "import folder/[..." + partialModule: string # The actual module name being typed + #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n" template origModuleName(m: PSym): string = m.name.s @@ -746,6 +752,123 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = let prefix = if c.config.m.trackPosAttached: nil else: n suggestEverything(c, n, prefix, outputs) +proc extractImportContextFromAst(n: PNode, cursorCol: int): ImportContext = + result = ImportContext() + if n.kind != nkImportStmt: return + for child in n: + case child.kind + of nkIdent: + # Single import, e.g. import foo + if child.info.col <= cursorCol: + result.baseDir = "" + result.partialModule = child.ident.s + result.isMultiImport = false + of nkInfix: + # Directory or multi-import, e.g. import std/[os, strutils] + if child.len == 3 and child[0].kind == nkIdent and child[0].ident.s == "/": + let dir = child[1].ident.s + if child[2].kind == nkBracket: + result.baseDir = dir + result.isMultiImport = true + for modNode in child[2]: + if modNode.kind == nkIdent and modNode.info.col <= cursorCol: + result.partialModule = modNode.ident.s + elif child[2].kind == nkIdent: + if child[2].info.col <= cursorCol: + result.baseDir = dir + result.partialModule = child[2].ident.s + result.isMultiImport = false + else: + discard + +proc findModuleFile(c: PContext, partialPath: string): seq[string] = + result = @[] + let currentModuleDir = parentDir(toFullPath(c.config, FileIndex(c.module.position))) + + proc tryAddModule(path, baseName: string) = + if fileExists(path & ".nim"): + result.add(baseName) + + proc addModulesFromDir(dir, file: string; result: var seq[string]) = + if dirExists(dir): + for kind, path in walkDir(dir): + if kind in {pcFile, pcDir}: + let (_, name, ext) = splitFile(path) + if kind == pcFile: + if ext == ".nim" and name.startsWith(file): + result.add(name) + + proc collectImportModulesFromDir(dir: string, result: var seq[string]) = + for kind, path in walkDir(dir): + if kind in {pcFile, pcDir}: + let (_, name, ext) = splitFile(path) + if kind == pcFile: + if ext == ".nim" and name.startsWith(partialPath): + result.add(name) + else: + if name.startsWith(partialPath): + result.add(name) + + if '/' in partialPath: + let parts = partialPath.split('/') + let dir = parts[0] + let file = parts[1] + addModulesFromDir(currentModuleDir / dir, file, result) + for searchPath in c.config.searchPaths: + let searchDir = searchPath.string / dir + addModulesFromDir(searchDir, file, result) + else: + collectImportModulesFromDir(currentModuleDir, result) + for searchPath in c.config.searchPaths: + collectImportModulesFromDir(searchPath.string, result) + +proc suggestModuleNames(c: PContext, n: PNode) = + var suggestions: Suggestions = @[] + let partialPath = if n.kind == nkIdent: n.ident.s else: "" + proc addModuleSuggestion(path: string) = + var suggest = Suggest( + section: ideSug, + qualifiedPath: @[path], + name: addr path, + filePath: path, + line: n.info.line.int, + column: n.info.col.int, + doc: "", + quality: 100, + contextFits: true, + prefix: if partialPath.len > 0: prefixMatch(path, partialPath) + else: PrefixMatch.None, + symkind: byte skModule + ) + suggestions.add(suggest) + + let importCtx = extractImportContextFromAst(n, c.config.m.trackPos.col) + var searchPath = "" + if importCtx.baseDir.len > 0: + searchPath = importCtx.baseDir & "/" + + let possibleModules = findModuleFile(c, searchPath & importCtx.partialModule) + for moduleName in possibleModules: + if moduleName != c.module.name.s: + addModuleSuggestion(moduleName) + + produceOutput(suggestions, c.config) + suggestQuit() + +proc findImportStmtOnLine(n: PNode, line: uint16): PNode = + if n.kind in {nkImportStmt, nkFromStmt} and n.info.line == line: + return n + for i in 0.. 0: return @@ -774,7 +897,7 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) = if outputs.len > 0 and c.config.ideCmd in {ideSug, ideCon, ideDef}: produceOutput(outputs, c.config) suggestQuit() - + proc suggestExpr*(c: PContext, n: PNode) = if exactEquals(c.config.m.trackPos, n.info): suggestExprNoCheck(c, n) diff --git a/nimsuggest/tests/timport1.nim b/nimsuggest/tests/timport1.nim new file mode 100644 index 0000000000..d3847b83fa --- /dev/null +++ b/nimsuggest/tests/timport1.nim @@ -0,0 +1,7 @@ +import bito#[!]# + +discard """ +$nimsuggest --tester --v4 --maxresults:1 $file +>sug $1 +sug;;skModule;;bitops;;;;bitops;;1;;0;;"";;100;;None +""" \ No newline at end of file diff --git a/nimsuggest/tests/timport2.nim b/nimsuggest/tests/timport2.nim new file mode 100644 index 0000000000..e6386e0fe1 --- /dev/null +++ b/nimsuggest/tests/timport2.nim @@ -0,0 +1,9 @@ +import fixtures/mcl#[!]# +import fixtures/[mstrutils, mfak#[!]#] +discard """ +$nimsuggest --tester --v4 --maxresults:1 $file +>sug $1 +sug;;skModule;;mclass_macro;;;;mclass_macro;;1;;0;;"";;100;;None +>sug $2 +sug;;skModule;;mfakeassert;;;;mfakeassert;;2;;0;;"";;100;;None +""" diff --git a/nimsuggest/tests/timport3.nim b/nimsuggest/tests/timport3.nim new file mode 100644 index 0000000000..4a326f5769 --- /dev/null +++ b/nimsuggest/tests/timport3.nim @@ -0,0 +1,9 @@ +import fixtu#[!]# #Suggest folders +import nimpre#[!]# #Can suggest from search path (see cmd arg below) +discard """ +$nimsuggest --tester --v4 --maxresults:1 --path:nimpretty $file +>sug $1 +sug;;skModule;;fixtures;;;;fixtures;;1;;0;;"";;100;;None +>sug $2 +sug;;skModule;;nimpretty;;;;nimpretty;;2;;0;;"";;100;;None +""" \ No newline at end of file From d2fee7dbabd6761ea1eb1ae6f4f95b65882456a2 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 11 May 2025 12:42:27 +0800 Subject: [PATCH 109/119] fixes broken discriminators of float types by disabling it (#24938) ```nim type Case = object case x: float of 1.0: id: int else: ta: float ``` It segfaults with `fatal error: invalid kind for firstOrd(tyFloat)` It was caused by https://github.com/nim-lang/Nim/pull/12591 and has affected discriminators of float types since 1.2.x I think no one is using discriminators of float types anyway so I simply disable it like what was done to discriminators of string types (ref https://github.com/nim-lang/Nim/pull/15080) ref https://github.com/nim-lang/nimony/pull/1069 --- compiler/semtypes.nim | 5 +++-- tests/errmsgs/tobjectvariants.nim | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 tests/errmsgs/tobjectvariants.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index a0ea8baac7..b3839ef633 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -813,7 +813,7 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, case typ.kind of shouldChckCovered: chckCovered = true - of tyFloat..tyFloat128, tyError: + of tyError: discard of tyRange: if skipTypes(typ.elementType, abstractInst).kind in shouldChckCovered: @@ -821,7 +821,8 @@ proc semRecordCase(c: PContext, n: PNode, check: var IntSet, pos: var int, of tyForward: errorUndeclaredIdentifier(c, n[0].info, typ.sym.name.s) elif not isOrdinalType(typ): - localError(c.config, n[0].info, "selector must be of an ordinal type, float") + localError(c.config, n[0].info, "selector must be of an ordinal type") + if firstOrd(c.config, typ) != 0: localError(c.config, n.info, "low(" & $a[0].sym.name.s & ") must be 0 for discriminant") diff --git a/tests/errmsgs/tobjectvariants.nim b/tests/errmsgs/tobjectvariants.nim new file mode 100644 index 0000000000..f7084ec51d --- /dev/null +++ b/tests/errmsgs/tobjectvariants.nim @@ -0,0 +1,13 @@ +discard """ + errormsg: "selector must be of an ordinal type" +""" + +type + Case = object + case x: float + of 1.0: + id: int + else: + ta: float + +var s = Case(x: 4.0, id: 1) \ No newline at end of file From 091fb5057bbe7a33de01ee84b1f032d69f12cdb2 Mon Sep 17 00:00:00 2001 From: c-blake Date: Sun, 11 May 2025 04:44:03 +0000 Subject: [PATCH 110/119] Maybe close https://github.com/nim-lang/Nim/issues/24932 by simply (#24945) explaining why the result may not be so surprising. Clean-up of stray whitespace and insert of missing "in" along for the ride. It's just not always faster or slower than `Table`. The difference depends upon many factors such as (at least!): A) how much (if anything - for `int` keys it is nothing) hash-comparison before `==` comparison saves B) how much resizing happens (which may even vary from run to run if end users are allowed to provide scale guess input), C) how much comparison happens at all (i.e., table density), D) how much space/size matters - like how close to a specific deployment "available" cache size the table is. If we want, we could add a sentence suggesting performance fans also try `Table`, but the kind of low-level nature of the explanation strikes me as already along those lines. --- lib/pure/collections/tables.nim | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 9a71a28d50..92f85b1464 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -107,7 +107,7 @@ runnableExamples: ## container (e.g. string, sequence or array), as it is a mapping where the ## items are the keys, and their number of occurrences are the values. ## For that purpose `toCountTable proc<#toCountTable,openArray[A]>`_ -## comes handy: +## comes in handy: runnableExamples: let myString = "abracadabra" @@ -2329,19 +2329,15 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = yield t.data[h].val assert(len(t) == L, "the length of the table changed while iterating over it") - - - - - - # ------------------------------------------------------------------------- # ------------------------------ CountTable ------------------------------- # ------------------------------------------------------------------------- type CountTable*[A] = object - ## Hash table that counts the number of each key. + ## Hash table that counts the number of each key. Unlike `Table<#Table>`_, + ## this uses a zero count to signal "empty" & so does not cache hash values + ## for comparison reduction or resize acceleration. ## ## For creating an empty CountTable, use `initCountTable proc ## <#initCountTable>`_. @@ -2736,10 +2732,6 @@ iterator mvalues*[A](t: var CountTable[A]): var int = - - - - # --------------------------------------------------------------------------- # ---------------------------- CountTableRef -------------------------------- # --------------------------------------------------------------------------- From c1e6cf812f7f9d2a706d70d99cab8d0a89a7e791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Kr=C3=B6ger?= Date: Wed, 14 May 2025 21:31:09 +0200 Subject: [PATCH 111/119] Fix extra newline from nimpretty when used with `--stdin` (#24951) Using `echo` to print file contents to stdout automatically adds a newline at the end of the file contents. When using nimpretty to auto format files on save in some editors which replace the file contents with the formatted ones this means that with every save/format operation an additional newline is added to the end of the file. Using `stdout.write` does not automatically add a newline at the end preventing this issue. Fixes #24950 --- nimpretty/nimpretty.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimpretty/nimpretty.nim b/nimpretty/nimpretty.nim index c860d2970e..ff193744f2 100644 --- a/nimpretty/nimpretty.nim +++ b/nimpretty/nimpretty.nim @@ -111,7 +111,7 @@ proc handleStdinInput(opt: PrettyOptions) = prettyPrint(path, path, opt) - echo(readAll(cfile)) + stdout.write(readAll(cfile)) close(cfile) removeFile(path) From ade500b2cbba5ba16587e48cea736c10e6798cae Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Thu, 15 May 2025 03:31:53 +0800 Subject: [PATCH 112/119] adds `nimPreviewCStringComparisons` for cstring comparisons (#24946) todo: We can also give a deprecation message for `ltPtr`/`lePtr` matching for cstring in `magicsAfterOverloadResolution` follow up https://github.com/nim-lang/Nim/pull/24942 --- changelog.md | 2 ++ compiler/nim.cfg | 1 + lib/system.nim | 51 ++++++++++++++++++++++++----------------------- tests/config.nims | 2 ++ 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index dc225844b8..e12265d169 100644 --- a/changelog.md +++ b/changelog.md @@ -21,6 +21,8 @@ errors. - The bare `except:` now panics on `Defect`. Use `except Exception:` or `except Defect:` to catch `Defect`. `--legacy:noPanicOnExcept` is provided for a transition period. +- With `-d:nimPreviewCStringComparisons`, comparsions (`<`, `>`, `<=`, `>=`) between cstrings switch from reference semantics to value semantics like `==` and `!=`. + ## Standard library additions and changes [//]: # "Additions:" diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 21faf37836..0cc8c476ec 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -11,6 +11,7 @@ define:nimPreviewRangeDefault define:nimPreviewNonVarDestructor define:nimPreviewCheckedClose define:nimPreviewAsmSemSymbol +define:nimPreviewCStringComparisons threads:off #import:"$projectpath/testability" diff --git a/lib/system.nim b/lib/system.nim index a77b59e74e..128759ecf8 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2738,36 +2738,37 @@ func ltCStringVm(x, y: cstring): bool {.inline.} = func leCStringVm(x, y: cstring): bool {.inline.} = discard "implemented in the vm ops" -func `<`*(x, y: cstring): bool {.inline.} = - if x == y: - result = false - elif x == nil: - result = true - elif y == nil: - result = false - else: - when nimvm: - result = ltCStringVm(x, y) +when defined(nimPreviewCStringComparisons): + func `<`*(x, y: cstring): bool {.inline.} = + if x == y: + result = false + elif x == nil: + result = true + elif y == nil: + result = false else: - when defined(js): - result = pointer(x) < pointer(y) + when nimvm: + result = ltCStringVm(x, y) else: - result = strcmp(x, y) < 0 + when defined(js): + result = pointer(x) < pointer(y) + else: + result = strcmp(x, y) < 0 -func `<=`*(x, y: cstring): bool {.inline.} = - if x == y: result = true - elif x == nil: - result = true - elif y == nil: - result = false - else: - when nimvm: - result = leCStringVm(x, y) + func `<=`*(x, y: cstring): bool {.inline.} = + if x == y: result = true + elif x == nil: + result = true + elif y == nil: + result = false else: - when defined(js): - result = pointer(x) <= pointer(y) + when nimvm: + result = leCStringVm(x, y) else: - result = strcmp(x, y) <= 0 + when defined(js): + result = pointer(x) <= pointer(y) + else: + result = strcmp(x, y) <= 0 template closureScope*(body: untyped): untyped = ## Useful when creating a closure in a loop to capture local loop variables by diff --git a/tests/config.nims b/tests/config.nims index 71825774c8..f19ff92220 100644 --- a/tests/config.nims +++ b/tests/config.nims @@ -46,3 +46,5 @@ when not defined(testsConciseTypeMismatch): switch("experimental", "vtables") switch("experimental", "openSym") switch("experimental", "typeBoundOps") + +switch("define", "nimPreviewCStringComparisons") From 71c5a4f72c2130184dcf6a6b21bccf756a98dc85 Mon Sep 17 00:00:00 2001 From: metagn Date: Thu, 15 May 2025 10:32:10 +0300 Subject: [PATCH 113/119] generate `let _ =` to fully unpack partial tuple unpacking assignment for arc (#24948) fixes #24947 When injectdestructors detects that a variable is a tuple unpacking temp (i.e. it is an `skTemp`, is not a cursor, and has tuple type) it does not generate a destructor for it and only generates sink/bit assignments for its components. However the reason it does not generate a destructor is that it expects it to be fully unpacked, this is true for unpackings in for loops but not for tuple unpacking assignments which supports `_` since #22537. Tuple unpacking definitions for `var`/`let`/`const` do not generate `skTemp` and use the same symbol kind as the definition so they did not have this problem. To keep this compatible, the `_` parts of the tuple unpacking assignments are now not ignored and unpacked into `let _ = ...`, which generates its own destructor. Another option might be to use `skLet` instead of `skTemp` but this might cause changes to behavior like additional copies, I am not sure about this though. --- compiler/injectdestructors.nim | 9 +++++---- compiler/semexprs.nim | 16 ++++++++++++++-- tests/arc/tpartialtupleunpacking1.nim | 19 +++++++++++++++++++ tests/arc/tpartialtupleunpacking2.nim | 18 ++++++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 tests/arc/tpartialtupleunpacking1.nim create mode 100644 tests/arc/tpartialtupleunpacking2.nim diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index cacb3305eb..fcbe89df5c 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -185,10 +185,11 @@ proc isCursor(n: PNode): bool = else: false -template isUnpackedTuple(n: PNode): bool = +template isFullyUnpackedTuple(n: PNode): bool = ## we move out all elements of unpacked tuples, ## hence unpacked tuples themselves don't need to be destroyed ## except it's already a cursor + ## restricted to `skTemp`, tuple temps where not every field is unpacked should not use `skTemp` (n.kind == nkSym and n.sym.kind == skTemp and n.sym.typ.kind == tyTuple and sfCursor notin n.sym.flags) @@ -275,7 +276,7 @@ proc deepAliases(dest, ri: PNode): bool = return aliases(dest, ri) != no proc genSink(c: var Con; s: var Scope; dest, ri: PNode; flags: set[MoveOrCopyFlag] = {}): PNode = - if (c.inLoopCond == 0 and (isUnpackedTuple(dest) or IsDecl in flags or + if (c.inLoopCond == 0 and (isFullyUnpackedTuple(dest) or IsDecl in flags or (isAnalysableFieldAccess(dest, c.owner) and isFirstWrite(dest, c)))) or isNoInit(dest) or IsReturn in flags: # optimize sink call into a bitwise memcopy @@ -559,7 +560,7 @@ proc cycleCheck(n: PNode; c: var Con) = proc pVarTopLevel(v: PNode; c: var Con; s: var Scope; res: PNode) = # move the variable declaration to the top of the frame: s.vars.add v.sym - if isUnpackedTuple(v): + if isFullyUnpackedTuple(v): if c.inLoop > 0: # unpacked tuple needs reset at every loop iteration res.add newTree(nkFastAsgn, v, genDefaultCall(v.typ, c, v.info)) @@ -1148,7 +1149,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con; s: var Scope, flags: set[MoveOrCopy of nkCallKinds: result = c.genSink(s, dest, p(ri, c, s, consumed), flags) of nkBracketExpr: - if isUnpackedTuple(ri[0]): + if isFullyUnpackedTuple(ri[0]): # unpacking of tuple: take over the elements result = c.genSink(s, dest, p(ri, c, s, consumed), flags) elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c, s): diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 2a3a4d13d4..5e2a5d0a72 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1923,8 +1923,20 @@ proc makeTupleAssignments(c: PContext; n: PNode): PNode = for i in 0.. Date: Fri, 16 May 2025 09:44:13 +0200 Subject: [PATCH 114/119] fixes #4851 [backport] (#24954) --- lib/system/cellsets.nim | 4 ++-- lib/system/gc.nim | 8 +++++++- testament/categories.nim | 3 ++- tests/gc/tfinalizers.nim | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 tests/gc/tfinalizers.nim diff --git a/lib/system/cellsets.nim b/lib/system/cellsets.nim index 7815f928bd..1fed45b7b5 100644 --- a/lib/system/cellsets.nim +++ b/lib/system/cellsets.nim @@ -252,12 +252,12 @@ iterator elementsExcept(t, s: CellSet): PCell {.inline.} = var r = t.head while r != nil: let ss = cellSetGet(s, r.key) - var i:uint = 0 + var i = 0'u while int(i) <= high(r.bits): var w = r.bits[i] if ss != nil: w = w and not ss.bits[i] - var j:uint = 0 + var j = 0'u while w != 0: if (w and 1) != 0: yield cast[PCell]((r.key shl PageShift) or diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 9289c7f55c..e1de2aade7 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -597,7 +597,13 @@ proc sweep(gch: var GcHeap) = if isCell(x): # cast to PCell is correct here: var c = cast[PCell](x) - if c notin gch.marked: freeCyclicCell(gch, c) + if c notin gch.marked: + # Don't free objects that have the ZctFlag set (created in finalizers) + if (c.refcount and ZctFlag) == 0: + freeCyclicCell(gch, c) + else: + # Clear the ZctFlag for the next collection cycle + c.refcount = c.refcount and not ZctFlag proc markS(gch: var GcHeap, c: PCell) = gcAssert isAllocatedPtr(gch.region, c), "markS: foreign heap root detected A!" diff --git a/testament/categories.nim b/testament/categories.nim index ee2da5bb8d..eba1e3cb27 100644 --- a/testament/categories.nim +++ b/testament/categories.nim @@ -83,7 +83,7 @@ proc runBasicDLLTest(c, r: var TResults, cat: Category, options: string, isOrc = if "boehm" notin options: # hcr tests - + var basicHcrTest = makeTest("tests/dll/nimhcr_basic.nim", options & " --threads:off --forceBuild --hotCodeReloading:on " & rpath, cat) # test segfaults for now but compiles: if isOrc: basicHcrTest.spec.action = actionCompile @@ -165,6 +165,7 @@ proc gcTests(r: var TResults, cat: Category, options: string) = test "stackrefleak" test "cyclecollector" testWithoutBoehm "trace_globals" + test "tfinalizers" # ------------------------- threading tests ----------------------------------- diff --git a/tests/gc/tfinalizers.nim b/tests/gc/tfinalizers.nim new file mode 100644 index 0000000000..53295b71ab --- /dev/null +++ b/tests/gc/tfinalizers.nim @@ -0,0 +1,19 @@ + +type + PNode = ref TNode + TNode = object + le: PNode + +proc finalizeNode(n: PNode) = + var s = @[0] + +proc returnTree() = + var cycle: PNode + new(cycle, finalizeNode) + cycle.le = cycle + +for i in 1..100: + returnTree() + +GC_fullCollect() +GC_fullCollect() From e855019f84e93e01022ad57aa3624c8e4d237486 Mon Sep 17 00:00:00 2001 From: metagn Date: Sat, 17 May 2025 19:37:02 +0300 Subject: [PATCH 115/119] add STRING_LITERAL macro back to nimbase.h for compatibility (#24957) refs #24956, refs #24302 --- lib/nimbase.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/nimbase.h b/lib/nimbase.h index 3b4438331b..2144c84b0c 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -470,6 +470,13 @@ typedef char* NCSTRING; #define NIM_STRLIT_FLAG ((NU)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */ +/* unused in codegen after 2.2 but keep for compatibility: */ +#define STRING_LITERAL(name, str, length) \ + static const struct { \ + TGenericSeq Sup; \ + NIM_CHAR data[(length) + 1]; \ + } name = {{length, (NI) ((NU)length | NIM_STRLIT_FLAG)}, str} + /* declared size of a sequence/variable length array: */ #if defined(__cplusplus) && defined(__clang__) # define SEQ_DECL_SIZE 1 From c3f64fb12743dd02dd0541f2420e4aa386cd2144 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 20 May 2025 03:40:35 +0800 Subject: [PATCH 116/119] rework `nimOrcLeakDetector` (#24958) ref https://github.com/nim-lang/Nim/issues/22273#issuecomment-2888931920 --- lib/system.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index 128759ecf8..f81c6d5363 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1616,7 +1616,7 @@ when not defined(js) and defined(nimV2): align: int16 depth: int16 display: ptr UncheckedArray[uint32] # classToken - when defined(nimTypeNames) or defined(nimArcIds): + when defined(nimTypeNames) or defined(nimArcIds) or defined(nimOrcLeakDetector): name: cstring traceImpl: pointer typeInfoV1: pointer # for backwards compat, usually nil From 3c0446b0828e0a64eb55ea38459e214d871d8ac1 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 23 May 2025 22:15:55 +0800 Subject: [PATCH 117/119] fixes #24940; fixes #17552; lifts `{.global.}` in `injectDestructorCalls` (#24962) fixes #24940 fixes #17552 Collects `{.global.}` (i.e. if it was changed into a hook call: `=copy`, `=sink`) in `injectDestructorCalls` and generates it in the init sections in cgen --- compiler/cgen.nim | 3 +++ compiler/injectdestructors.nim | 13 ++++++++++++- compiler/modulegraphs.nim | 2 ++ tests/global/tglobal3.nim | 30 ++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 tests/global/tglobal3.nim diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 6f16c4f17d..49f4d68cfa 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -2433,6 +2433,9 @@ proc genTopLevelStmt*(m: BModule; n: PNode) = else: genProcBody(m.initProc, transformedN) + for g in m.g.graph.procGlobals: + genStmts(m.preInitProc, g) + proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = if optForceFullMake notin m.config.globalOptions: if not moduleHasChanged(m.g.graph, m.module): diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index fcbe89df5c..39e8defe6d 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -936,6 +936,9 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing of nkVarSection, nkLetSection: # transform; var x = y to var x; x op y where op is a move or copy result = newNodeI(nkStmtList, n.info) + + let isInProc = c.owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter} + for it in n: var ri = it[^1] if it.kind == nkVarTuple and hasDestructor(c, ri.typ): @@ -951,7 +954,15 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing s.locals.add v.sym pVarTopLevel(v, c, s, result) if ri.kind != nkEmpty: - result.add moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {}) + let isGlobalPragma = v.kind == nkSym and + {sfPure, sfGlobal} <= v.sym.flags and + isInProc + + let value = moveOrCopy(v, ri, c, s, if v.kind == nkSym: {IsDecl} else: {}) + if isGlobalPragma: + c.graph.procGlobals.add value + else: + result.add value elif ri.kind == nkEmpty and c.inLoop > 0: let skipInit = v.kind == nkDotExpr and # Closure var sfNoInit in v[1].sym.flags diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index dd6a590e4f..25ca73ad1f 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -136,6 +136,8 @@ type cachedFiles*: StringTableRef + procGlobals*: seq[PNode] + TPassContext* = object of RootObj # the pass's context idgen*: IdGenerator PPassContext* = ref TPassContext diff --git a/tests/global/tglobal3.nim b/tests/global/tglobal3.nim new file mode 100644 index 0000000000..10a40798f2 --- /dev/null +++ b/tests/global/tglobal3.nim @@ -0,0 +1,30 @@ +discard """ + matrix: "--mm:refc; --mm:orc" + targets: "c cpp" +""" + +block: # bug #17552 + proc main: string = + var tc {.global.} = "hi" + tc &= "hi" + result = tc + + doAssert main() == "hihi" + doAssert main() == "hihihi" + doAssert main() == "hihihihi" + +# bug #24940 +var v: int + +proc ccc(): ref int = + let tmp = new int + v += 1 + tmp[] = v + tmp + +proc f(v: static string): int = + let xxx {.global.} = ccc() + xxx[] + +doAssert f("1") == 1 +doAssert f("1") == 1 \ No newline at end of file From a09da96c6592da3f231744b7032580777069c3a2 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 23 May 2025 22:16:57 +0800 Subject: [PATCH 118/119] fixes #4594; disallow {.global.} uses local vars for basic expressions (#24961) fixes #4594 --- compiler/semstmts.nim | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index fabf3dee60..ce8b59f9cd 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -727,20 +727,31 @@ template isLocalSym(sym: PSym): bool = sym.kind in {skProc, skFunc, skIterator} and sfGlobal notin sym.flags -template isLocalVarSym(n: PNode): bool = - n.kind == nkSym and isLocalSym(n.sym) - proc usesLocalVar(n: PNode): bool = - result = false - for z in 1 ..< n.len: - if n[z].isLocalVarSym: - return true - elif n[z].kind in nkCallKinds: - if usesLocalVar(n[z]): + case n.kind + of nkSym: + result = isLocalSym(n.sym) + of nkCallKinds, nkObjConstr: + result = false + for i in 1 ..< n.len: + if usesLocalVar(n[i]): return true + of nkTupleConstr, nkPar, nkBracket, nkCurly: + result = false + for i in 0 ..< n.len: + if usesLocalVar(n[i]): + return true + of nkDotExpr, nkCheckedFieldExpr, + nkBracketExpr, nkAddr, nkHiddenAddr, + nkObjDownConv, nkObjUpConv: + result = usesLocalVar(n[0]) + of nkHiddenStdConv, nkHiddenSubConv, nkCast, nkExprColonExpr: + result = usesLocalVar(n[1]) + else: + result = false proc globalVarInitCheck(c: PContext, n: PNode) = - if n.isLocalVarSym or n.kind in nkCallKinds and usesLocalVar(n): + if usesLocalVar(n): localError(c.config, n.info, errCannotAssignToGlobal) const From 87523928389227fa39803e363e10d95ea7c2376e Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 23 May 2025 17:19:13 +0300 Subject: [PATCH 119/119] implement setter fallback for subscripts (#24872) follows up #24871 For subscript assignments, if an overload of `[]=`/`{}=` is not found, the LHS checks for overloads of `[]`/`{}` as a fallback, similar to what field setters do since #24871. This is accomplished by just compiling the LHS if the assignment overloads fail. This has the side effect that the error messages are different now, instead of displaying the overloads of `[]=`/`{}=` that did not match, it will display the ones for `[]`/`{}` instead. This could be fixed by checking for `efLValue` when giving the error messages for `[]`/`{}` but this is not done here. The code for `[]` subscripts is a little different because of the `mArrGet`/`mArrPut` overloads that always match. If the `mArrPut` overload matches without a builtin subscript behavior for the LHS then it calls `semAsgn` again with `mode = noOverloadedSubscript`. Before this meant "fail to compile" but now it means "try to compile the LHS as normal", in both cases the overloads of `[]=` are not considered again. --- compiler/semexprs.nim | 23 +++++-- tests/errmsgs/t22753.nim | 65 ++++++++++--------- tests/specialops/tsetterfallbacksubscript.nim | 25 +++++++ 3 files changed, 79 insertions(+), 34 deletions(-) create mode 100644 tests/specialops/tsetterfallbacksubscript.nim diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5e2a5d0a72..6cc29bd86f 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1963,21 +1963,34 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = of nkBracketExpr: # a[i] = x # --> `[]=`(a, i, x) + # try builtin subscript for LHS first: a = semSubscript(c, a, {efLValue}) if a == nil: - result = buildOverloadedSubscripts(n[0], getIdent(c.cache, "[]=")) - result.add(n[1]) if mode == noOverloadedSubscript: - bracketNotFoundError(c, result, {}) - return errorNode(c, n) + # `[]=` overloads failed and builtin subscript failed, try `[]` overloads for LHS + # will error if not found: + a = semExprWithType(c, n[0], {efLValue}) else: + # magic overload of `[]=` will always match so cannot check for mismatch here, + # will go to above `if` branch instead + result = buildOverloadedSubscripts(n[0], getIdent(c.cache, "[]=")) + result.add(n[1]) result = semExprNoType(c, result) return result of nkCurlyExpr: # a{i} = x --> `{}=`(a, i, x) + # no builtin behavior/magic overloads for curly subscript, + # try `{}=` overloads first then try `{}` overloads for LHS: + let nOrig = n.copyTree result = buildOverloadedSubscripts(n[0], getIdent(c.cache, "{}=")) result.add(n[1]) - return semExprNoType(c, result) + result = semOverloadedCallAnalyseEffects(c, result, result.copyTree, {efNoUndeclared}) + if result != nil: + result = afterCallActions(c, result, nOrig, {}) + return + else: + # will error if `{}` overloads not found: + a = semExprWithType(c, a, {efLValue}) of nkPar, nkTupleConstr: if a.len >= 2 or a.kind == nkTupleConstr: # unfortunately we need to rewrite ``(x, y) = foo()`` already here so diff --git a/tests/errmsgs/t22753.nim b/tests/errmsgs/t22753.nim index 8a504109a8..39f018dd9b 100644 --- a/tests/errmsgs/t22753.nim +++ b/tests/errmsgs/t22753.nim @@ -1,50 +1,57 @@ discard """ cmd: "nim check --hints:off $file" -errormsg: "type mismatch" +action: "reject" nimoutFull: true nimout: ''' -t22753.nim(51, 13) Error: array expects two type parameters -t22753.nim(52, 1) Error: expression 'x' has no type (or is ambiguous) -t22753.nim(52, 1) Error: expression 'x' has no type (or is ambiguous) -t22753.nim(52, 2) Error: type mismatch: got <> +t22753.nim(58, 13) Error: array expects two type parameters +t22753.nim(59, 1) Error: expression 'x' has no type (or is ambiguous) +t22753.nim(59, 1) Error: expression 'x' has no type (or is ambiguous) +t22753.nim(59, 1) Error: expression 'x' has no type (or is ambiguous) +t22753.nim(59, 1) Error: expression 'x' has no type (or is ambiguous) +t22753.nim(59, 2) Error: type mismatch: got <> but expected one of: -proc `[]=`(s: var string; i: BackwardsIndex; x: char) +proc `[]`(s: string; i: BackwardsIndex): char first type mismatch at position: 2 required type for i: BackwardsIndex but expression '0' is of type: int literal(0) -proc `[]=`[I: Ordinal; T, S](a: T; i: I; x: sink S) +proc `[]`(s: var string; i: BackwardsIndex): var char + first type mismatch at position: 2 + required type for i: BackwardsIndex + but expression '0' is of type: int literal(0) +proc `[]`[I: Ordinal; T](a: T; i: I): T first type mismatch at position: 0 -proc `[]=`[Idx, T; U, V: Ordinal](a: var array[Idx, T]; x: HSlice[U, V]; - b: openArray[T]) +proc `[]`[Idx, T; U, V: Ordinal](a: array[Idx, T]; x: HSlice[U, V]): seq[T] first type mismatch at position: 2 - required type for x: HSlice[[]=.U, []=.V] + required type for x: HSlice[[].U, [].V] but expression '0' is of type: int literal(0) -proc `[]=`[Idx, T](a: var array[Idx, T]; i: BackwardsIndex; x: T) +proc `[]`[Idx, T](a: array[Idx, T]; i: BackwardsIndex): T first type mismatch at position: 2 required type for i: BackwardsIndex but expression '0' is of type: int literal(0) -proc `[]=`[T, U: Ordinal](s: var string; x: HSlice[T, U]; b: string) - first type mismatch at position: 2 - required type for x: HSlice[[]=.T, []=.U] - but expression '0' is of type: int literal(0) -proc `[]=`[T; U, V: Ordinal](s: var seq[T]; x: HSlice[U, V]; b: openArray[T]) - first type mismatch at position: 2 - required type for x: HSlice[[]=.U, []=.V] - but expression '0' is of type: int literal(0) -proc `[]=`[T](s: var openArray[T]; i: BackwardsIndex; x: T) +proc `[]`[Idx, T](a: var array[Idx, T]; i: BackwardsIndex): var T + first type mismatch at position: 2 + required type for i: BackwardsIndex + but expression '0' is of type: int literal(0) +proc `[]`[T, U: Ordinal](s: string; x: HSlice[T, U]): string + first type mismatch at position: 2 + required type for x: HSlice[[].T, [].U] + but expression '0' is of type: int literal(0) +proc `[]`[T; U, V: Ordinal](s: openArray[T]; x: HSlice[U, V]): seq[T] + first type mismatch at position: 2 + required type for x: HSlice[[].U, [].V] + but expression '0' is of type: int literal(0) +proc `[]`[T](s: openArray[T]; i: BackwardsIndex): T + first type mismatch at position: 2 + required type for i: BackwardsIndex + but expression '0' is of type: int literal(0) +proc `[]`[T](s: var openArray[T]; i: BackwardsIndex): var T first type mismatch at position: 2 required type for i: BackwardsIndex but expression '0' is of type: int literal(0) -template `[]=`(a: WideCStringObj; idx: int; val: Utf16Char) - first type mismatch at position: 3 - required type for val: Utf16Char - but expression '9' is of type: int literal(9) -template `[]=`(s: string; i: int; val: char) - first type mismatch at position: 3 - required type for val: char - but expression '9' is of type: int literal(9) -expression: x[0] = 9 +expression: x[0] +t22753.nim(59, 2) Error: expression '' has no type (or is ambiguous) +t22753.nim(59, 2) Error: '' cannot be assigned to ''' """ diff --git a/tests/specialops/tsetterfallbacksubscript.nim b/tests/specialops/tsetterfallbacksubscript.nim new file mode 100644 index 0000000000..eb04e1b9f6 --- /dev/null +++ b/tests/specialops/tsetterfallbacksubscript.nim @@ -0,0 +1,25 @@ +type Foo = object + x, y: float + +proc `[]`(foo: var Foo, i: int): var float = + if i == 0: + result = foo.x + else: + result = foo.y + +var pt = Foo(x: 0.0, y: 0.0) +pt[0] += 1.0 # <-- fine +`[]`(pt, 0) = 1.0 # <-- fine +pt[0] = 1.0 # <-- does not compile + +# curly: + +proc `{}`(foo: var Foo, i: int): var float = + if i == 0: + result = foo.x + else: + result = foo.y + +pt{0} += 1.0 # <-- fine +`{}`(pt, 0) = 1.0 # <-- fine +pt{0} = 1.0 # <-- does not compile