From 6112c51e7850e77963856e4424c74abf596c84ac Mon Sep 17 00:00:00 2001 From: Judd Date: Mon, 25 Nov 2024 17:51:03 +0800 Subject: [PATCH 01/13] Fix highlite.nim (#24457) When parsing `a = 1` with `langPython`, Eof is reported unexpectedly. Fix: allow other languages to fallback to "Identifier" when it is not a keyword. This patch is useful as this is a highlighter. `Eof` as annoying. --- lib/packages/docutils/highlite.nim | 2 ++ tests/stdlib/trstgen.nim | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index f8376f46c371a..7542b88018b00 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -301,6 +301,8 @@ proc nimNextToken(g: var GeneralTokenizer, keywords: openArray[string] = @[]) = g.kind = nimGetKeyword(id) elif isKeyword(keywords, id) >= 0: g.kind = gtKeyword + else: + g.kind = gtIdentifier of '0': inc(pos) case g.buf[pos] diff --git a/tests/stdlib/trstgen.nim b/tests/stdlib/trstgen.nim index f23291a188634..97dbaacd72867 100644 --- a/tests/stdlib/trstgen.nim +++ b/tests/stdlib/trstgen.nim @@ -1631,8 +1631,8 @@ suite "RST/Code highlight": """ - let expected = """

def f_name(arg=42): - print(f"{arg}")

""" + let expected = """

def f_name(arg=42): + print(f"{arg}")

""" check strip(rstToHtml(pythonCode, {}, newStringTable(modeCaseSensitive))) == strip(expected) From 4aaabbb5b8d2a1b5f843319bdfb7cfe4287e663b Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 25 Nov 2024 22:55:25 +0800 Subject: [PATCH 02/13] move "Strict definitions and `out` parameters" to the manual (#24474) --- doc/manual.md | 116 +++++++++++++++++++++++++++++++++ doc/manual_experimental.md | 130 ------------------------------------- 2 files changed, 116 insertions(+), 130 deletions(-) diff --git a/doc/manual.md b/doc/manual.md index d3c81aa78ff30..a35a67851b36a 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -9043,3 +9043,119 @@ This means the following compiles (for now) even though it really should not: inc i access a[i].v ``` + +Strict definitions and `out` parameters +======================================= + +*every* local variable must be initialized explicitly before it can be used: + + ```nim + proc test = + var s: seq[string] + s.add "abc" # invalid! + ``` + +Needs to be written as: + + ```nim + proc test = + var s: seq[string] = @[] + s.add "abc" # valid! + ``` + +A control flow analysis is performed in order to prove that a variable has been written to +before it is used. Thus the following is valid: + + ```nim + proc test(cond: bool) = + var s: seq[string] + if cond: + s = @["y"] + else: + s = @[] + s.add "abc" # valid! + ``` + +In this example every path does set `s` to a value before it is used. + + ```nim + proc test(cond: bool) = + let s: seq[string] + if cond: + s = @["y"] + else: + s = @[] + ``` + +`let` statements are allowed to not have an initial value, but every path should set `s` to a value before it is used. + + +`out` parameters +---------------- + +An `out` parameter is like a `var` parameter but it must be written to before it can be used: + + ```nim + proc myopen(f: out File; name: string): bool = + f = default(File) + result = open(f, name) + ``` + +While it is usually the better style to use the return type in order to return results API and ABI +considerations might make this infeasible. Like for `var T` Nim maps `out T` to a hidden pointer. +For example POSIX's `stat` routine can be wrapped as: + + ```nim + proc stat*(a1: cstring, a2: out Stat): cint {.importc, header: "".} + ``` + +When the implementation of a routine with output parameters is analysed, the compiler +checks that every path before the (implicit or explicit) return does set every output +parameter: + + ```nim + proc p(x: out int; y: out string; cond: bool) = + x = 4 + if cond: + y = "abc" + # error: not every path initializes 'y' + ``` + + +Out parameters and exception handling +------------------------------------- + +The analysis should take exceptions into account (but currently does not): + + ```nim + proc p(x: out int; y: out string; cond: bool) = + x = canRaise(45) + y = "abc" # <-- error: not every path initializes 'y' + ``` + +Once the implementation takes exceptions into account it is easy enough to +use `outParam = default(typeof(outParam))` in the beginning of the proc body. + +Out parameters and inheritance +------------------------------ + +It is not valid to pass an lvalue of a supertype to an `out T` parameter: + + ```nim + type + Superclass = object of RootObj + a: int + Subclass = object of Superclass + s: string + + proc init(x: out Superclass) = + x = Superclass(a: 8) + + var v: Subclass + init v + use v.s # the 's' field was never initialized! + ``` + +However, in the future this could be allowed and provide a better way to write object +constructors that take inheritance into account. + diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index 9bf64790f5a17..81defd70b5304 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -1919,136 +1919,6 @@ restrictions / changes: yet performed for ordinary slices outside of a `parallel` section. -Strict definitions and `out` parameters -======================================= - -With `experimental: "strictDefs"` *every* local variable must be initialized explicitly before it can be used: - - ```nim - {.experimental: "strictDefs".} - - proc test = - var s: seq[string] - s.add "abc" # invalid! - - ``` - -Needs to be written as: - - ```nim - {.experimental: "strictDefs".} - - proc test = - var s: seq[string] = @[] - s.add "abc" # valid! - - ``` - -A control flow analysis is performed in order to prove that a variable has been written to -before it is used. Thus the following is valid: - - ```nim - {.experimental: "strictDefs".} - - proc test(cond: bool) = - var s: seq[string] - if cond: - s = @["y"] - else: - s = @[] - s.add "abc" # valid! - ``` - -In this example every path does set `s` to a value before it is used. - - ```nim - {.experimental: "strictDefs".} - - proc test(cond: bool) = - let s: seq[string] - if cond: - s = @["y"] - else: - s = @[] - ``` - -With `experimental: "strictDefs"`, `let` statements are allowed to not have an initial value, but every path should set `s` to a value before it is used. - - -`out` parameters ----------------- - -An `out` parameter is like a `var` parameter but it must be written to before it can be used: - - ```nim - proc myopen(f: out File; name: string): bool = - f = default(File) - result = open(f, name) - ``` - -While it is usually the better style to use the return type in order to return results API and ABI -considerations might make this infeasible. Like for `var T` Nim maps `out T` to a hidden pointer. -For example POSIX's `stat` routine can be wrapped as: - - ```nim - proc stat*(a1: cstring, a2: out Stat): cint {.importc, header: "".} - ``` - -When the implementation of a routine with output parameters is analysed, the compiler -checks that every path before the (implicit or explicit) return does set every output -parameter: - - ```nim - proc p(x: out int; y: out string; cond: bool) = - x = 4 - if cond: - y = "abc" - # error: not every path initializes 'y' - ``` - - -Out parameters and exception handling -------------------------------------- - -The analysis should take exceptions into account (but currently does not): - - ```nim - proc p(x: out int; y: out string; cond: bool) = - x = canRaise(45) - y = "abc" # <-- error: not every path initializes 'y' - ``` - -Once the implementation takes exceptions into account it is easy enough to -use `outParam = default(typeof(outParam))` in the beginning of the proc body. - -Out parameters and inheritance ------------------------------- - -It is not valid to pass an lvalue of a supertype to an `out T` parameter: - - ```nim - type - Superclass = object of RootObj - a: int - Subclass = object of Superclass - s: string - - proc init(x: out Superclass) = - x = Superclass(a: 8) - - var v: Subclass - init v - use v.s # the 's' field was never initialized! - ``` - -However, in the future this could be allowed and provide a better way to write object -constructors that take inheritance into account. - - -**Note**: The implementation of "strict definitions" and "out parameters" is experimental but the concept -is solid and it is expected that eventually this mode becomes the default in later versions. - - Strict case objects =================== From 1a901bd94ecc3414abbf087bfe413b404bf704bd Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:15:20 +0800 Subject: [PATCH 03/13] minor fix for the command line helper (#24475) --- doc/advopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/advopt.txt b/doc/advopt.txt index e4d11081ae482..7c739ca7232bf 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -51,7 +51,7 @@ Advanced options: --spellSuggest:num show at most `num >= 0` spelling suggestions on typos. if `num` is not specified (or `auto`), return an implementation defined set of suggestions. - --hints:on|off|list. `on|off` enables or disables hints. + --hints:on|off|list `on|off` enables or disables hints. `list` reports which hints are selected. --hint:X:on|off turn specific hint X on|off. `hint:X` means `hint:X:on`, as with similar flags. `all` is the set of all hints From e7f48cdd5c17cbd0c1c74c2c1f360ae2ea122b64 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:35:48 +0800 Subject: [PATCH 04/13] fixes #24472; let symbol created by template is reused in nimvm branch (#24473) fixes #24472 Excluding variables which are initialized in the nimvm branch so that they won't interfere the other branch --- compiler/sempass2.nim | 3 +++ lib/pure/volatile.nim | 1 + testament/important_packages.nim | 2 +- tests/cpp/tasync_cpp.nim | 2 +- tests/init/tlet.nim | 18 ++++++++++++++++++ 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 29ee67c3cbdfa..f1e31b7f7e42c 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -212,6 +212,7 @@ proc varDecl(a: PEffects; n: PNode) {.inline.} = proc skipHiddenDeref(n: PNode): PNode {.inline.} = result = if n.kind == nkHiddenDeref: n[0] else: n + proc initVar(a: PEffects, n: PNode; volatileCheck: bool) = let n = skipHiddenDeref(n) if n.kind != nkSym: return @@ -1302,7 +1303,9 @@ proc track(tracked: PEffects, n: PNode) = track(tracked, last) of nkCaseStmt: trackCase(tracked, n) of nkWhen: # This should be a "when nimvm" node. + let oldState = tracked.init.len track(tracked, n[0][1]) + tracked.init.setLen(oldState) track(tracked, n[1][0]) of nkIfStmt, nkIfExpr: trackIf(tracked, n) of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n[1]) diff --git a/lib/pure/volatile.nim b/lib/pure/volatile.nim index a38247c7db1db..0b79b61015543 100644 --- a/lib/pure/volatile.nim +++ b/lib/pure/volatile.nim @@ -19,6 +19,7 @@ proc volatileLoad*[T](src: ptr T): T {.inline, noinit.} = when defined(js): result = src[] else: + result = default(T) {.emit: [result, " = (*(", typeof(src[]), " volatile*)", src, ");"].} proc volatileStore*[T](dest: ptr T, val: T) {.inline.} = diff --git a/testament/important_packages.nim b/testament/important_packages.nim index 833d285bdf98b..3f2d4a7e3cabe 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -138,7 +138,7 @@ pkg "plotly", "nim c examples/all.nim" pkg "pnm" pkg "polypbren" pkg "presto" -# pkg "prologue", "nimble tcompile" +pkg "prologue", "nimble tcompile" pkg "protobuf", "nim c -o:protobuff -r src/protobuf.nim" pkg "rbtree" pkg "react", "nimble example" diff --git a/tests/cpp/tasync_cpp.nim b/tests/cpp/tasync_cpp.nim index 953f6a2b487a7..3f4ec620840f4 100644 --- a/tests/cpp/tasync_cpp.nim +++ b/tests/cpp/tasync_cpp.nim @@ -1,7 +1,7 @@ discard """ targets: "cpp" output: "hello" - cmd: "nim cpp --legacy:nostrictdefs --clearNimblePath --nimblePath:build/deps/pkgs2 $file" + cmd: "nim cpp --clearNimblePath --nimblePath:build/deps/pkgs2 $file" """ # bug #3299 diff --git a/tests/init/tlet.nim b/tests/init/tlet.nim index a3041baf876d2..b566de9f62ee1 100644 --- a/tests/init/tlet.nim +++ b/tests/init/tlet.nim @@ -83,3 +83,21 @@ proc foo2 = doAssert z == 3 foo2() + +# bug #24472 +template bar1314(): bool = + let hello = true + hello + +template foo1314*(val: bool): bool = + when nimvm: + val + else: + val + +proc test() = # Doesn't fail when top level + # Original code is calling `unlikely` which has a `nimvm` branch + let s = foo1314(bar1314()) + doAssert s + +test() From e4791514739ea8442e269ee2f7c2f38251150a48 Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Wed, 27 Nov 2024 04:15:22 -0500 Subject: [PATCH 05/13] Fixes 3 small issues with concepts (#24481) issue 1 - statics in the type: This probably only handles simple cases. It's probably too accepting only comparing the base, but that should only affect candidate selection I think. issue 2 - `tyArray` of length 3: This is just a work around since I couldn't get the fix right in previous PR issue 3 - shadowing: The part in `concepts.nim` that iterates candidates does not consider imported idents if at least once module level ident matches. It does not have to match in any other way then name. EDIT: 2 more issue 4 - composite typeclasses: when declared in both the concept and the `proc` can cause problems issue 5 - recursion: simple recursion and scenarios where more than one concepts recurse together (only tested two) --- compiler/concepts.nim | 56 ++++++++++++----- compiler/lookups.nim | 27 +++++---- compiler/semcall.nim | 2 +- compiler/semgnrc.nim | 2 +- tests/concepts/conceptsv2_helper.nim | 8 +++ tests/concepts/tconceptsv2.nim | 89 ++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 29 deletions(-) create mode 100644 tests/concepts/conceptsv2_helper.nim diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 3f0d30538ed82..23f8c5d87ce7b 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -78,6 +78,7 @@ type magic: TMagic ## mArrGet and mArrPut is wrong in system.nim and ## cannot be fixed that easily. ## Thus we special case it here. + concpt: PType proc existingBinding(m: MatchCon; key: PType): PType = ## checks if we bound the type variable 'key' already to some @@ -174,27 +175,43 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool = result = ak.kind == f.kind or ak.kind == tyOrdinal or (ak.kind == tyGenericParam and ak.hasElementType and ak.elementType.kind == tyOrdinal) of tyConcept: - let oldLen = m.inferred.len - let oldPotentialImplementation = m.potentialImplementation - m.potentialImplementation = a - result = conceptMatchNode(c, f.n.lastSon, m) - m.potentialImplementation = oldPotentialImplementation - if not result: - m.inferred.setLen oldLen + 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: - result = matchType(c, last(f), a, m) + 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. - 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) + if f.kind == tyArray and f.kidsLen == 3: + # 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: @@ -230,6 +247,16 @@ proc matchType(c: PContext; f, a: PType; m: var MatchCon): bool = 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: + scomp = scomp.base + if a.kind == tyStatic: + result = matchType(c, scomp, a.base, m) + else: + result = matchType(c, scomp, a, m) else: result = false @@ -280,7 +307,8 @@ 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. - let candidates = searchInScopesAllCandidatesFilterBy(c, n[namePos].sym.name, kinds) + 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 @@ -327,7 +355,7 @@ proc conceptMatch*(c: PContext; concpt, arg: PType; bindings: var LayeredIdTable ## `C[S, T]` parent type that we look for. We need this because we need to store bindings ## for 'S' and 'T' inside 'bindings' on a successful match. It is very important that ## we do not add any bindings at all on an unsuccessful match! - var m = MatchCon(inferred: @[], potentialImplementation: arg) + var m = MatchCon(inferred: @[], potentialImplementation: arg, concpt: concpt) result = conceptMatchNode(c, concpt.n.lastSon, m) if result: for (a, b) in m.inferred: diff --git a/compiler/lookups.nim b/compiler/lookups.nim index d8fcf73e0f73b..745915cdf9cd2 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -221,7 +221,14 @@ proc debugScopes*(c: PContext; limit=0, max = int.high) {.deprecated.} = if i == limit: return inc i -proc searchInScopesAllCandidatesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] = +proc searchImportsAll*(c: PContext, s: PIdent, filter: TSymKinds, holding: var seq[PSym]) = + var marked = initIntSet() + for im in c.imports.mitems: + for s in symbols(im, marked, s, c.graph): + if s.kind in filter: + holding.add s + +proc searchScopes*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] = result = @[] for scope in allScopes(c.currentScope): var ti: TIdentIter = default(TIdentIter) @@ -231,14 +238,12 @@ proc searchInScopesAllCandidatesFilterBy*(c: PContext, s: PIdent, filter: TSymKi result.add candidate candidate = nextIdentIter(ti, scope.symbols) +proc searchScopesAll*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] = + result = searchScopes(c,s,filter) if result.len == 0: - var marked = initIntSet() - for im in c.imports.mitems: - for s in symbols(im, marked, s, c.graph): - if s.kind in filter: - result.add s + searchImportsAll(c, s, filter, result) -proc searchInScopesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] = +proc selectFromScopesElseAll*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSym] = result = @[] block outer: for scope in allScopes(c.currentScope): @@ -252,11 +257,7 @@ proc searchInScopesFilterBy*(c: PContext, s: PIdent, filter: TSymKinds): seq[PSy candidate = nextIdentIter(ti, scope.symbols) if result.len == 0: - var marked = initIntSet() - for im in c.imports.mitems: - for s in symbols(im, marked, s, c.graph): - if s.kind in filter: - result.add s + searchImportsAll(c, s, filter, result) proc cmpScopes*(ctx: PContext, s: PSym): int = # Do not return a negative number @@ -644,7 +645,7 @@ const allExceptModule = {low(TSymKind)..high(TSymKind)} - {skModule, skPackage} proc lookUpCandidates*(c: PContext, ident: PIdent, filter: set[TSymKind], includePureEnum = false): seq[PSym] = - result = searchInScopesFilterBy(c, ident, filter) + result = selectFromScopesElseAll(c, ident, filter) if skEnumField in filter and (result.len == 0 or includePureEnum): result.add allPureEnumFields(c, ident) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index d0ccb17b1fa17..92276b84878b5 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -56,7 +56,7 @@ proc initCandidateSymbols(c: PContext, headSymbol: PNode, proc name[T: static proc()]() = T() name[proc() = echo"hello"]() ]# - for paramSym in searchInScopesAllCandidatesFilterBy(c, symx.name, {skConst}): + for paramSym in searchScopesAll(c, symx.name, {skConst}): let paramTyp = paramSym.typ if paramTyp.n.kind == nkSym and paramTyp.n.sym.kind in filter: result.add((paramTyp.n.sym, o.lastOverloadScope)) diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index cb51973857461..135d26ba56bf0 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -218,7 +218,7 @@ proc fuzzyLookup(c: PContext, n: PNode, flags: TSemGenericFlags, let ident = considerQuotedIdent(c, n) # could be type conversion if like a.T and not a.T() let symKinds = if inCall: routineKinds else: routineKinds+{skType} - var candidates = searchInScopesFilterBy(c, ident, symKinds) + var candidates = selectFromScopesElseAll(c, ident, symKinds) if candidates.len > 0: let s = candidates[0] # XXX take into account the other candidates! isMacro = s.kind in {skTemplate, skMacro} diff --git a/tests/concepts/conceptsv2_helper.nim b/tests/concepts/conceptsv2_helper.nim new file mode 100644 index 0000000000000..daab556cbeb3d --- /dev/null +++ b/tests/concepts/conceptsv2_helper.nim @@ -0,0 +1,8 @@ +type + ShadowConcept* = concept + proc iGetShadowed(s: Self) + DummyFitsObj* = object + +proc iGetShadowed*(s: DummyFitsObj)= + discard + diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 3d90204becd7e..f72f522ee6100 100644 --- a/tests/concepts/tconceptsv2.nim +++ b/tests/concepts/tconceptsv2.nim @@ -3,8 +3,11 @@ action: "run" output: ''' B[system.int] A[system.string] +A[array[0..0, int]] +A[seq[int]] ''' """ +import conceptsv2_helper block: # issue #24451 type @@ -39,3 +42,89 @@ block: # typeclass var a = A[string]() a.accept() + +block: + type + SomethingLike[T] = concept + proc len(s: Self): int + proc `[]`(s: Self; index: int): T + + A[T] = object + x: T + + proc initA(x: SomethingLike): auto = + A[type x](x: x) + + var a: array[1, int] + var s: seq[int] + echo typeof(initA(a)) + echo typeof(initA(s)) + +block: + proc iGetShadowed(s: int)= + discard + proc spring(x: ShadowConcept)= + discard + let a = DummyFitsObj() + spring(a) + +block: + type + Buffer = concept + proc put(s: Self) + ArrayBuffer[T: static int] = object + proc put(x: ArrayBuffer)=discard + proc p(a: Buffer)=discard + var buffer = ArrayBuffer[5]() + p(buffer) + +block: # composite typeclass matching + type + A[T] = object + Buffer = concept + proc put(s: Self, i: A) + BufferImpl = object + WritableImpl = object + + proc launch(a: var Buffer)=discard + proc put(x: BufferImpl, i: A)=discard + + var a = BufferImpl() + launch(a) + +block: # simple recursion + type + Buffer = concept + proc put(s: var Self, i: auto) + proc second(s: Self) + Writable = concept + proc put(w: var Buffer, s: Self) + BufferImpl[T: static int] = object + WritableImpl = object + + proc launch(a: var Buffer, b: Writable)= discard + proc put(x: var BufferImpl, i: object)= discard + proc second(x: BufferImpl)= discard + proc put(x: var Buffer, y: WritableImpl)= discard + + var a = BufferImpl[5]() + launch(a, WritableImpl()) + +block: # more complex recursion + type + Buffer = concept + proc put(s: var Self, i: auto) + proc second(s: Self) + Writable = concept + proc put(w: var Buffer, s: Self) + BufferImpl[T: static int] = object + WritableImpl = object + + proc launch(a: var Buffer, b: Writable)= discard + proc put(x: var Buffer, i: object)= discard + proc put(x: var BufferImpl, i: object)= discard + proc second(x: BufferImpl)= discard + proc put(x: var Buffer, y: WritableImpl)= discard + + var a = BufferImpl[5]() + launch(a, WritableImpl()) From dcd0793f2b6255f561f904f1eaa9f5d11ee25de0 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Wed, 27 Nov 2024 19:23:54 +0800 Subject: [PATCH 06/13] Add support for parsing parameterised sql types (#24483) Co-authored-by: Cletus Igwe --- lib/pure/parsesql.nim | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 4ea01c89d8deb..299e4599dcce9 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -549,6 +549,7 @@ type SqlParser* = object of SqlLexer ## SQL parser object tok: Token + considerTypeParams: bool ## Determines whether type parameters (e.g., sizes in types like VARCHAR(255)) are included in the SQL AST. proc newNode*(k: SqlNodeKind): SqlNode = when defined(js): # bug #14117 @@ -641,16 +642,21 @@ proc parseDataType(p: var SqlParser): SqlNode = expectIdent(p) result = newNode(nkIdent, p.tok.literal) getTok(p) - # ignore (12, 13) part: if p.tok.kind == tkParLe: + var complexType = newNode(nkCall) + complexType.add(result) getTok(p) + complexType.add(newNode(nkIntegerLit, p.tok.literal)) expect(p, tkInteger) getTok(p) while p.tok.kind == tkComma: getTok(p) + complexType.add(newNode(nkIntegerLit, p.tok.literal)) expect(p, tkInteger) getTok(p) eat(p, tkParRi) + if p.considerTypeParams: + result = complexType proc getPrecedence(p: SqlParser): int = if isOpr(p, "*") or isOpr(p, "/") or isOpr(p, "%"): @@ -1565,19 +1571,20 @@ proc open(p: var SqlParser, input: Stream, filename: string) = p.tok.literal = "" getTok(p) -proc parseSql*(input: Stream, filename: string): SqlNode = +proc parseSql*(input: Stream, filename: string, considerTypeParams = false): SqlNode = ## parses the SQL from `input` into an AST and returns the AST. ## `filename` is only used for error messages. ## Syntax errors raise an `SqlParseError` exception. var p: SqlParser + p.considerTypeParams = considerTypeParams open(p, input, filename) try: result = parse(p) finally: close(p) -proc parseSql*(input: string, filename = ""): SqlNode = +proc parseSql*(input: string, filename = "", considerTypeParams = false): SqlNode = ## parses the SQL from `input` into an AST and returns the AST. ## `filename` is only used for error messages. ## Syntax errors raise an `SqlParseError` exception. - parseSql(newStringStream(input), "") + parseSql(newStringStream(input), "", considerTypeParams) From 8881017c8096f6a6bf3d30c3fb4ef348253eef24 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 27 Nov 2024 12:31:06 +0100 Subject: [PATCH 07/13] stdlib: minor refactorings and updates (#24482) --- lib/pure/memfiles.nim | 62 +++++++++++++++++++++---------------------- lib/pure/streams.nim | 6 ++--- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 8eec551c4e501..8d5599489024c 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -32,16 +32,16 @@ when defined(nimPreviewSlimSystem): proc newEIO(msg: string): ref IOError = - new(result) - result.msg = msg + result = (ref IOError)(msg: msg) proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode = ## Set the size of open file pointed to by `fh` to `newFileSize` if != -1, ## allocating | freeing space from the file system. This routine returns the ## last OSErrorCode found rather than raising to support old rollback/clean-up ## code style. [ Should maybe move to std/osfiles. ] + result = OSErrorCode(0) if newFileSize < 0 or newFileSize == oldSize: - return + return result when defined(windows): var sizeHigh = int32(newFileSize shr 32) let sizeLow = int32(newFileSize and 0xffffffff) @@ -56,7 +56,7 @@ proc setFileSize(fh: FileHandle, newFileSize = -1, oldSize = -1): OSErrorCode = when declared(posix_fallocate): while (e = posix_fallocate(fh, 0, newFileSize); e == EINTR): discard - if e in [EINVAL, EOPNOTSUPP] and ftruncate(fh, newFileSize) == -1: + if (e == EINVAL or e == EOPNOTSUPP) and ftruncate(fh, newFileSize) == -1: result = osLastError() # fallback arguable; Most portable BUT allows SEGV elif e != 0: result = osLastError() @@ -268,10 +268,10 @@ proc open*(filename: string, mode: FileMode = fmRead, if result.handle == -1: fail(osLastError(), "error opening file") - if mappedSize != -1: #XXX Logic here differs from `when windows` branch .. - result.size = mappedSize #.. which always fstats&Uses min(mappedSize, st). + if mappedSize != -1: # XXX Logic here differs from `when windows` branch .. + result.size = mappedSize # .. which always fstats&Uses min(mappedSize, st). else: # if newFileSize!=-1: result.size=newFileSize # if trust setFileSize - var stat: Stat #^^.. BUT some FSes (eg. Linux HugeTLBfs) round to 2MiB. + var stat: Stat # ^^.. BUT some FSes (eg. Linux HugeTLBfs) round to 2MiB. if fstat(result.handle, stat) != -1: result.size = stat.st_size.int # int may be 32-bit-unsafe for 2..<4 GiB else: @@ -336,8 +336,9 @@ proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} = f.mapHandle = createFileMappingW(f.fHandle, nil, PAGE_READWRITE, 0,0,nil) if f.mapHandle == 0: # Re-do map raiseOSError(osLastError()) - if (let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE, - 0, 0, WinSizeT(newFileSize), nil); m != nil): + let m = mapViewOfFileEx(f.mapHandle, FILE_MAP_READ or FILE_MAP_WRITE, + 0, 0, WinSizeT(newFileSize), nil) + if m != nil: f.mem = m f.size = newFileSize else: @@ -347,8 +348,8 @@ proc resize*(f: var MemFile, newFileSize: int) {.raises: [IOError, OSError].} = raise newException(IOError, "Cannot resize MemFile opened with allowRemap=false") if newFileSize != f.size: - if (let e = setFileSize(f.handle.FileHandle, newFileSize, f.size); - e != 0.OSErrorCode): raiseOSError(e) + let e = setFileSize(f.handle.FileHandle, newFileSize, f.size) + if e != 0.OSErrorCode: raiseOSError(e) when defined(linux): #Maybe NetBSD, too? # On Linux this can be over 100 times faster than a munmap,mmap cycle. proc mremap(old: pointer; oldSize, newSize: csize_t; flags: cint): @@ -401,9 +402,10 @@ proc close*(f: var MemFile) = if error: raiseOSError(lastErr) -type MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records - data*: pointer - size*: int +type + MemSlice* = object ## represent slice of a MemFile for iteration over delimited lines/records + data*: pointer + size*: int proc `==`*(x, y: MemSlice): bool = ## Compare a pair of MemSlice for strict equality. @@ -411,7 +413,7 @@ proc `==`*(x, y: MemSlice): bool = proc `$`*(ms: MemSlice): string {.inline.} = ## Return a Nim string built from a MemSlice. - result.setLen(ms.size) + result = newString(ms.size) copyMem(result.cstring, ms.data, ms.size) iterator memSlices*(mfile: MemFile, delim = '\l', eat = '\r'): MemSlice {.inline.} = @@ -449,9 +451,8 @@ iterator memSlices*(mfile: MemFile, delim = '\l', eat = '\r'): MemSlice {.inline proc c_memchr(cstr: pointer, c: char, n: csize_t): pointer {. importc: "memchr", header: "".} proc `-!`(p, q: pointer): int {.inline.} = return cast[int](p) -% cast[int](q) - var ms: MemSlice var ending: pointer - ms.data = mfile.mem + var ms = MemSlice(data: mfile.mem, size: 0) var remaining = mfile.size while remaining > 0: ending = c_memchr(ms.data, delim, csize_t(remaining)) @@ -479,7 +480,6 @@ iterator lines*(mfile: MemFile, buf: var string, delim = '\l', ## for line in lines(memfiles.open("foo"), buffer): ## echo line ## ``` - for ms in memSlices(mfile, delim, eat): setLen(buf, ms.size) if ms.size > 0: @@ -497,7 +497,6 @@ iterator lines*(mfile: MemFile, delim = '\l', eat = '\r'): string {.inline.} = ## for line in lines(memfiles.open("foo")): ## echo line ## ``` - var buf = newStringOfCap(80) for line in lines(mfile, buf, delim, eat): yield buf @@ -507,7 +506,7 @@ type MemMapFileStreamObj* = object of Stream mf: MemFile mode: FileMode - pos: ByteAddress + pos: int proc mmsClose(s: Stream) = MemMapFileStream(s).pos = -1 @@ -555,14 +554,15 @@ proc newMemMapFileStream*(filename: string, mode: FileMode = fmRead, ## `fileSize` can only be set if the file does not exist and is opened ## with write access (e.g., with fmReadWrite). var mf: MemFile = open(filename, mode, newFileSize = fileSize) - new(result) - result.mode = mode - result.mf = mf - result.closeImpl = mmsClose - result.atEndImpl = mmsAtEnd - result.setPositionImpl = mmsSetPosition - result.getPositionImpl = mmsGetPosition - result.readDataImpl = mmsReadData - result.peekDataImpl = mmsPeekData - result.writeDataImpl = mmsWriteData - result.flushImpl = mmsFlush + result = MemMapFileStream( + mode: mode, + mf: mf, + closeImpl: mmsClose, + atEndImpl: mmsAtEnd, + setPositionImpl: mmsSetPosition, + getPositionImpl: mmsGetPosition, + readDataImpl: mmsReadData, + peekDataImpl: mmsPeekData, + writeDataImpl: mmsWriteData, + flushImpl: mmsFlush + ) diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 56f49d7b1bf64..895281c04ca76 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -183,7 +183,7 @@ proc close*(s: Stream) = let strm = newStringStream("The first line\nthe second line\nthe third line") ## do something... strm.close() - + block: let strm = newFileStream("amissingfile.txt") # deferring works even if newFileStream fails @@ -286,9 +286,9 @@ when (NimMajor, NimMinor) >= (1, 3) or not defined(js): strm.close() const bufferSize = 1024 + result = "" jsOrVmBlock: - var buffer2: string - buffer2.setLen(bufferSize) + var buffer2 = newString(bufferSize) while true: let readBytes = readDataStr(s, buffer2, 0.. Date: Thu, 28 Nov 2024 00:36:57 +0800 Subject: [PATCH 08/13] fixes #24476; remove proc type cast if they are same types for backends (#24480) fixes #24476 closes https://github.com/nim-lang/Nim/pull/24479 --- compiler/ccgexprs.nim | 2 ++ compiler/vtables.nim | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 5600f6842dd5d..0cd2e74148faf 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -2520,6 +2520,8 @@ proc genSomeCast(p: BProc, e: PNode, d: var TLoc) = putIntoDest(p, d, e, cDeref(cCast(ptrType(destTyp), wrapPar(cAddr(val)))), a.storage) elif etyp.kind == tyBool and srcTyp.kind in IntegralTypes: putIntoDest(p, d, e, cOp(NotEqual, rdCharLoc(a), cIntValue(0)), a.storage) + elif etyp.kind == tyProc and srcTyp.kind == tyProc and sameBackendType(etyp, srcTyp): + expr(p, e[1], d) else: if etyp.kind == tyPtr: # generates the definition of structs for casts like cast[ptr object](addr x)[] diff --git a/compiler/vtables.nim b/compiler/vtables.nim index 6d481fbb3c998..b9c64ef687c75 100644 --- a/compiler/vtables.nim +++ b/compiler/vtables.nim @@ -15,7 +15,6 @@ proc dispatch(x: Base, params: ...) = var disp = newNodeI(nkIfStmt, base.info) - var vTableAccess = newNodeIT(nkBracketExpr, base.info, base.typ) let nimGetVTableSym = getCompilerProc(g, "nimGetVTable") let ptrPNimType = nimGetVTableSym.typ.n[1].sym.typ @@ -33,7 +32,7 @@ proc dispatch(x: Base, params: ...) = dispatchObject, newIntNode(nkIntLit, index) ) - getVTableCall.typ() = base.typ + getVTableCall.typ() = getSysType(g, unknownLineInfo, tyPointer) var vTableCall = newNodeIT(nkCall, base.info, base.typ.returnType) var castNode = newTree(nkCast, newNodeIT(nkType, base.info, base.typ), From 05bba156233a8f269ccfa970c1b277684d038d74 Mon Sep 17 00:00:00 2001 From: metagn Date: Mon, 2 Dec 2024 07:21:12 +0300 Subject: [PATCH 09/13] fix crash with undeclared proc type pragma macro in generics (#24490) fixes #24489 --- compiler/semtypes.nim | 2 +- tests/pragmas/tundeclaredgenericmacro.nim | 32 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/pragmas/tundeclaredgenericmacro.nim diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 54e7298d42145..4855faf736c3a 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -1867,7 +1867,7 @@ proc applyTypeSectionPragmas(c: PContext; pragmas, operand: PNode): PNode = x.add(operand.copyTreeWithoutNode(p)) # recursion assures that this works for multiple macro annotations too: var r = semOverloadedCall(c, x, x, {skMacro, skTemplate}, {efNoUndeclared}) - if r != nil: + if r != nil and (r.typ == nil or r.typ.kind != tyFromExpr): doAssert r[0].kind == nkSym let m = r[0].sym case m.kind diff --git a/tests/pragmas/tundeclaredgenericmacro.nim b/tests/pragmas/tundeclaredgenericmacro.nim new file mode 100644 index 0000000000000..826f1a58e23ca --- /dev/null +++ b/tests/pragmas/tundeclaredgenericmacro.nim @@ -0,0 +1,32 @@ +import std/[asyncdispatch, httpclient, strutils, json, times] + +type + Tree[T] = object + x: T + rr: seq[Tree[T]] + I = Tree[int] + F = Tree[Future[string]] + +proc title(t: I): Future[string] {.async,gcsafe.} = + let cli = newAsyncHttpClient() + let c = await cli.getContent("https://jsonplaceholder.typicode.com/todos/" & $t.x) + c.parseJson()["title"].getStr() + +proc map[T,U](t: T, f: proc (x: T): U{.async.gcsafe.}): U = #[tt.Error + ^ invalid pragma: async.gcsafe]# + result.x = f(t) + for r in t.rr: + result.rr.add map(r, f) + +proc f(t: F, l: int) {.async.} = + echo repeat(' ', l), (await t.x) + for r in t.rr: + await f(r, l+1) + +proc asyncMain() {.async.} = + let t = I(x: 1, rr: @[I(x: 2), I(x: 3, rr: @[I(x: 4), I(x: 5)])]) + await f(map[I,F](t, title), 0) + +let a = now() +waitFor asyncMain() +echo now() - a From ddf5a9f6c5cf2d16958edf774d545343bc07cadf Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 2 Dec 2024 13:39:55 +0800 Subject: [PATCH 10/13] adds a test case (#24486) closes #23680 --- tests/template/template_issues.nim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/template/template_issues.nim b/tests/template/template_issues.nim index 58c40941db698..f44de789eb9f2 100644 --- a/tests/template/template_issues.nim +++ b/tests/template/template_issues.nim @@ -13,6 +13,7 @@ foo foo false true +0 ''' """ @@ -302,3 +303,10 @@ block: # bug #21920 discard t[void]() # Error: expression has no type: discard + +template m(a: uint, p: int) = discard + +block: # bug #23680 + template m(x: untyped, i: int) = echo x + proc j(n: int | int) = m([0][0], 0) + j(0) From 33dc2367e786b278cfc5245f8e1a736374882d5b Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 3 Dec 2024 06:48:21 +0300 Subject: [PATCH 11/13] install older version of nimcuda for arraymancer (#24496) Attempt to fix CI failure, refs https://github.com/nim-lang/Nim/pull/24495#issuecomment-2511299112, alternative is to use a commit version like https://github.com/SciNim/nimcuda/commit/bc65375ff52980ca2faec058bea40c2306ab6081 --- 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 3f2d4a7e3cabe..ed3be4cbc7356 100644 --- a/testament/important_packages.nim +++ b/testament/important_packages.nim @@ -35,7 +35,7 @@ proc pkg(name: string; cmd = "nimble test"; url = "", useHead = true, allowFailu pkg "alea" pkg "argparse" -pkg "arraymancer", "nim c tests/tests_cpu.nim" +pkg "arraymancer", "nimble install -y; nimble uninstall -i -y nimcuda; nimble install nimcuda@0.2.1; nim c tests/tests_cpu.nim" pkg "ast_pattern_matching", "nim c -r tests/test1.nim" pkg "asyncftpclient", "nimble compileExample" pkg "asyncthreadpool", "nimble test --mm:refc" From 3bee04d9f3f7360eb5e3e268395433551c1e4d62 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:05:14 +0800 Subject: [PATCH 12/13] prefix `NimDestroyGlobals` with `nimMainPrefix` (#24493) ref https://github.com/nim-lang/Nim/issues/24471 --------- Co-authored-by: metagn --- compiler/cgen.nim | 5 +++-- doc/backends.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 2d4e70420b9e2..7b71239e96eda 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -2497,12 +2497,13 @@ proc updateCachedModule(m: BModule) = addFileToCompile(m.config, cf) proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode; isDynlib: bool): PSym = - let procname = getIdent(graph.cache, "NimDestroyGlobals") + let prefixedName = m.config.nimMainPrefix & "NimDestroyGlobals" + let procname = getIdent(graph.cache, prefixedName) result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info) result.typ = newProcType(m.module.info, m.idgen, m.module.owner) result.typ.callConv = ccCDecl incl result.flags, sfExportc - result.loc.snippet = "NimDestroyGlobals" + result.loc.snippet = prefixedName if isDynlib: incl(result.loc.flags, lfExportLib) diff --git a/doc/backends.md b/doc/backends.md index 6751703430ec2..a8db83fb59db4 100644 --- a/doc/backends.md +++ b/doc/backends.md @@ -249,7 +249,7 @@ which will likely make your program crash at runtime. The name `NimMain` can be influenced via the `--nimMainPrefix:prefix` switch. Use `--nimMainPrefix:MyLib` and the function to call is named `MyLibNimMain`. -When compiling to static or dynamic libraries, they don't call destructors of global variables as normal Nim programs would do. A C API `NimDestroyGlobals` is provided to call these global destructors. +When compiling to static or dynamic libraries, they don't call destructors of global variables as normal Nim programs would do. A C API `NimDestroyGlobals` is provided to call these global destructors. It is influenced by the `--nimMainPrefix:prefix` switch, too. ### Nim invocation example from C From 464dc993762a7eadb36462e6d5e265aefc1da6e3 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:41:04 +0800 Subject: [PATCH 13/13] fixes devel version number (#24494) As said in the documentation, `NimMinor` is supposed to be odd and should be greater than the stable channel --- lib/system/compilation.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/system/compilation.nim b/lib/system/compilation.nim index cdb976ed53f72..a5ea44a1ff8dc 100644 --- a/lib/system/compilation.nim +++ b/lib/system/compilation.nim @@ -6,7 +6,7 @@ const ## ``` # see also std/private/since - NimMinor* {.intdefine.}: int = 2 + NimMinor* {.intdefine.}: int = 3 ## is the minor number of Nim's version. ## Odd for devel, even for releases.