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 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" diff --git a/changelog.md b/changelog.md index b9671147f0..e12265d169 100644 --- a/changelog.md +++ b/changelog.md @@ -19,15 +19,26 @@ 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. + +- With `-d:nimPreviewCStringComparisons`, comparsions (`<`, `>`, `<=`, `>=`) between cstrings switch from reference semantics to value semantics like `==` and `!=`. + ## 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. +- `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. +- `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`. ## Language changes @@ -71,4 +82,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/ast.nim b/compiler/ast.nim index a187687f4e..13f7890bcd 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 @@ -795,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 @@ -933,16 +943,17 @@ 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 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) @@ -2089,14 +2115,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 @@ -2133,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/ccgcalls.nim b/compiler/ccgcalls.nim index aaad53b64b..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 @@ -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.. 1: #we dont care about the return param for i in 1.. conf.target.floatSize * 3): result = true # requested anyway elif (tfFinal in pt.flags) and (pt[0] == nil): @@ -120,14 +117,14 @@ proc makeUnique(m: BModule; s: PSym, name: string = ""): string = result.add "_u" result.add $s.itemId.item -proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false): string = +proc encodeSym*(m: BModule; s: PSym; makeUnique: bool = false; extra: string = ""): string = #Module::Type - var name = s.name.s + var name = s.name.s & extra if makeUnique: name = makeUnique(m, s, name) "N" & encodeName(s.skipGenericOwner.name.s) & encodeName(name) & "E" -proc encodeType*(m: BModule; t: PType): string = +proc encodeType*(m: BModule; t: PType; staticLists: var string): string = result = "" var kindName = ($t.kind)[2..^1] kindName[0] = toLower($kindName[0])[0] @@ -138,10 +135,10 @@ proc encodeType*(m: BModule; t: PType): string = result = encodeName(t[0].sym.name.s) result.add "I" for i in 1.. 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.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: + 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,46 @@ 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: + 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: 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: + result = true + for i in 1 ..< k2: + if not matchType(c, f[i], ea[i], m): + result = false 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 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 +363,75 @@ 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() + 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: + 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 +441,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 +480,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 +488,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 +528,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/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/compiler/docgen.nim b/compiler/docgen.nim index 2b25ded7df..1ea8eafd5d 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]) @@ -1889,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: @@ -1909,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/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/compiler/extccomp.nim b/compiler/extccomp.nim index 82cf5afb91..4bae400dc0 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", @@ -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) diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 013ab1c506..39e8defe6d 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -24,7 +24,7 @@ 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 @@ -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 @@ -400,7 +401,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 & ")") @@ -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)) @@ -935,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): @@ -950,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 @@ -1148,7 +1160,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): @@ -1197,8 +1209,11 @@ 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) - of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt: + 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 handleNestedTempl(ri, process, willProduceStmt = true) @@ -1244,6 +1259,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 +1321,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/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/compiler/layeredtable.nim b/compiler/layeredtable.nim index 565fb95464..248ec4bcf2 100644 --- a/compiler/layeredtable.nim +++ b/compiler/layeredtable.nim @@ -1,5 +1,5 @@ -import std/tables -import ast +import std/[tables] +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: @@ -53,6 +54,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 idTablePairs(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/liftdestructors.nim b/compiler/liftdestructors.nim index 9020a6f7ff..a9eb0263e9 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) @@ -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 @@ -1002,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: @@ -1021,7 +1026,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: @@ -1063,9 +1068,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 +1107,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 +1118,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 +1160,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) @@ -1197,7 +1200,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/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/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) diff --git a/compiler/lookups.nim b/compiler/lookups.nim index e452da959d..34f65973cf 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) @@ -77,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/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/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/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/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/compiler/options.nim b/compiler/options.nim index ea75a68487..f9c9f9a8be 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 @@ -402,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 @@ -578,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/parser.nim b/compiler/parser.nim index 7475050974..4af56f2103 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) @@ -699,10 +700,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) @@ -2107,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/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: @@ -1405,11 +1409,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/compiler/renderer.nim b/compiler/renderer.nim index a598a0ae5e..1887e5a1a9 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -410,7 +410,7 @@ proc atom(g: TSrcGen; n: PNode): string = of nkEmpty: result = "" of nkIdent: result = n.ident.s of nkSym: result = n.sym.name.s - of nkClosedSymChoice, nkOpenSymChoice: result = n[0].sym.name.s + of nkClosedSymChoice, nkOpenSymChoice, nkOpenSym: result = n[0].sym.name.s of nkStrLit: result = ""; result.addQuoted(n.strVal) of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"") & '\"' of nkTripleStrLit: result = "\"\"\"" & n.strVal & "\"\"\"" @@ -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: @@ -1002,7 +1008,7 @@ type proc bracketKind*(g: TSrcGen, n: PNode): BracketKind = if renderIds notin g.flags: case n.kind - of nkClosedSymChoice, nkOpenSymChoice: + of nkClosedSymChoice, nkOpenSymChoice, nkOpenSym: if n.len > 0: result = bracketKind(g, n[0]) else: result = bkNone of nkSym: @@ -1415,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) @@ -1469,15 +1472,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 +1757,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/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.. 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 @@ -695,7 +686,14 @@ 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) + 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) a[0].sym = finalCallee a[0].typ() = finalCallee.typ #a.typ = finalCallee.typ.returnType @@ -911,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 6e256b3d32..b31395ed55 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, astalgo import ic / ic @@ -41,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] @@ -171,8 +172,13 @@ 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.} + TBorrowState* = enum bsNone, bsReturnNotMatch, bsNoDistinct, bsGeneric, bsNotSupported, bsMatch @@ -254,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 @@ -286,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) = @@ -312,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: @@ -391,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): @@ -635,3 +643,166 @@ 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.. `[]=`(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 @@ -2267,10 +2208,8 @@ proc lookUpForDeclared(c: PContext, n: PNode, onlyCurrentScope: bool): PSym = result = someSym(c.graph, m, ident) of nkSym: result = n.sym - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: result = n[0].sym - of nkOpenSym: - result = lookUpForDeclared(c, n[0], onlyCurrentScope) else: localError(c.config, n.info, "identifier expected, but got: " & renderTree(n)) result = nil @@ -2808,16 +2747,21 @@ proc semSetConstr(c: PContext, n: PNode, expectedType: PType = nil): PNode = else: # only semantic checking for all elements, later type checking: var typ: PType = nil + var isGeneric = false for i in 0.. 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..= 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/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/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/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/compiler/semmagic.nim b/compiler/semmagic.nim index f3dff366eb..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 @@ -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) + 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 @@ -604,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) @@ -645,42 +654,13 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode, of mNewFinalize: result = semNewFinalize(c, n) of mDestroy: - result = n - let t = n[1].typ.skipTypes(abstractVar) - let op = getAttachedOp(c.graph, t, attachedDestructor) - if op != nil: - result[0] = newSymNode(op) - if op.typ != nil 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: - result[1] = genDeref(n[1]) - else: - result[1] = skipAddr(n[1]) + result = replaceHookMagic(c, n, attachedDestructor) of mTrace: - result = n - let t = n[1].typ.skipTypes(abstractVar) - let op = getAttachedOp(c.graph, t, attachedTrace) - if op != nil: - result[0] = newSymNode(op) + result = replaceHookMagic(c, n, attachedTrace) of mDup: - result = n - let t = n[1].typ.skipTypes(abstractVar) - let op = getAttachedOp(c.graph, t, attachedDup) - if op != nil: - result[0] = newSymNode(op) - if op.typ.len == 3: - let boolLit = newIntLit(c.graph, n.info, 1) - boolLit.typ() = getSysType(c.graph, n.info, tyBool) - result.add boolLit + result = replaceHookMagic(c, n, attachedDup) of mWasMoved: - result = n - let t = n[1].typ.skipTypes(abstractVar) - let op = getAttachedOp(c.graph, t, attachedWasMoved) - if op != nil: - result[0] = newSymNode(op) - let addrExp = newNodeIT(nkHiddenAddr, result[1].info, makePtrType(c, t)) - addrExp.add result[1] - result[1] = addrExp + result = replaceHookMagic(c, n, attachedWasMoved) of mUnown: result = semUnown(c, n) of mExists, mForall: diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 4122ec2fd6..b5e600ad8a 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] @@ -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 @@ -137,8 +138,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 = @@ -638,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) @@ -977,6 +981,25 @@ proc markCaughtExceptions(tracked: PEffects; g: ModuleGraph; info: TLineInfo; s: if optIdeExceptionInlayHints in tracked.config.globalOptions: internalMarkCaughtExceptions(tracked, g.suggestSymbols.mgetOrPut(info.fileIndex, newSuggestFileSymbolDatabase(info.fileIndex, true)), info) +proc findHookKind(name: string): (bool, TTypeAttachedOp) = + case name.normalize + of "=wasmoved": + result = (true, attachedWasMoved) + of "=destroy": + result = (true, attachedDestructor) + of "=copy", "=": + result = (true, attachedAsgn) + of "=dup": + result = (true, attachedDup) + of "=sink": + result = (true, attachedSink) + of "=trace": + result = (true, attachedTrace) + of "=deepcopy": + result = (true, attachedDeepCopy) + else: + result = (false, attachedWasMoved) + proc trackCall(tracked: PEffects; n: PNode) = template gcsafeAndSideeffectCheck() = if notGcSafe(op) and not importedFromC(a): @@ -1065,18 +1088,17 @@ proc trackCall(tracked: PEffects; n: PNode) = checkBounds(tracked, n[1], n[2]) + var n = n if a.kind == nkSym and a.sym.name.s.len > 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 + # replace builtin hooks with lifted ones + n = replaceHookMagic(tracked.c, n, opKind) if op != nil and op.kind == tyProc: for i in 1.. 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) @@ -1679,7 +1703,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/compiler/semstmts.nim b/compiler/semstmts.nim index 9295b873c8..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 @@ -1460,8 +1471,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 +1615,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 +1724,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 +1765,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 +1800,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) @@ -2137,13 +2164,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 @@ -2158,9 +2189,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: @@ -2179,7 +2207,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: @@ -2804,9 +2832,24 @@ proc recursiveSetFlag(n: PNode, flag: TNodeFlag) = for i in 0.. MaxSetElements: @@ -307,6 +333,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] @@ -553,7 +582,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, @@ -784,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: @@ -792,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") @@ -994,11 +1024,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 +1054,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 +1064,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 +1094,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) @@ -1289,12 +1330,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 +1351,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) @@ -1465,6 +1507,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 @@ -1528,7 +1572,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") @@ -1659,6 +1705,7 @@ proc semGeneric(c: PContext, n: PNode, s: PSym, prev: PType): PType = for i in 1.. 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) @@ -1949,12 +2003,19 @@ 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) - fixupTypeOf(c, prev, t) - result = t.typ + 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/compiler/semtypinst.nim b/compiler/semtypinst.nim index 6c81f8ac7b..a615aeee94 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 = @@ -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 @@ -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 b175549dcb..e486f3a47f 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}) @@ -1212,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 @@ -1424,7 +1447,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: @@ -1517,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}) @@ -1642,8 +1668,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 @@ -1714,7 +1742,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 @@ -1747,9 +1775,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 @@ -1757,7 +1784,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: # @@ -1821,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: @@ -1885,11 +1913,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 @@ -2161,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}) @@ -2270,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`` @@ -2282,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 @@ -2410,7 +2437,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/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/compiler/transf.nim b/compiler/transf.nim index 433a534912..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 @@ -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/trees.nim b/compiler/trees.nim index da58878f82..a42b616d97 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -131,8 +131,8 @@ proc isRange*(n: PNode): bool {.inline.} = let callee = n[0] if (callee.kind == nkIdent and callee.ident.id == ord(wDotDot)) or (callee.kind == nkSym and callee.sym.name.id == ord(wDotDot)) or - (callee.kind in {nkClosedSymChoice, nkOpenSymChoice} and - callee[1].sym.name.id == ord(wDotDot)): + (callee.kind in {nkClosedSymChoice, nkOpenSymChoice, nkOpenSym} and + callee[0].sym.name.id == ord(wDotDot)): result = true else: result = false @@ -145,7 +145,7 @@ proc whichPragma*(n: PNode): TSpecialWord = of nkIdent: result = whichKeyword(key.ident) of nkSym: result = whichKeyword(key.sym.name) of nkCast: return wCast - of nkClosedSymChoice, nkOpenSymChoice: + of nkClosedSymChoice, nkOpenSymChoice, nkOpenSym: return whichPragma(key[0]) of nkBracketExpr: if n.kind notin nkPragmaCallKinds: return wInvalid @@ -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.. ptr object @@ -2068,7 +2116,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/compiler/vm.nim b/compiler/vm.nim index fc9da48f37..73255395fe 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -2051,7 +2051,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = aStrVal = aNode.ident.s.cstring of nkSym: aStrVal = aNode.sym.name.s.cstring - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: aStrVal = aNode[0].sym.name.s.cstring else: discard @@ -2063,7 +2063,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = bStrVal = bNode.ident.s.cstring of nkSym: bStrVal = bNode.sym.name.s.cstring - of nkOpenSymChoice, nkClosedSymChoice: + of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: bStrVal = bNode[0].sym.name.s.cstring else: discard diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 6e47f6fe44..e8612000a3 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1011,7 +1011,7 @@ proc genBindSym(c: PCtx; n: PNode; dest: var TDest) = # if dynamicBindSym notin c.config.features: if n.len == 2: # hmm, reliable? # bindSym with static input - if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkSym}: + if n[1].kind in {nkClosedSymChoice, nkOpenSymChoice, nkOpenSym, nkSym}: let idx = c.genLiteral(n[1]) if dest < 0: dest = c.getTemp(n.typ) c.gABx(n, opcNBindSym, dest, idx) @@ -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: @@ -2313,9 +2317,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/compiler/vmops.nim b/compiler/vmops.nim index 8b0b8b5c7c..f3d349e803 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.} = @@ -331,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/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) 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/doc/manual.md b/doc/manual.md index 40b7b9f180..9abd0e762c 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 ========================== @@ -7724,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. @@ -7736,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/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/koch.nim b/koch.nim index 7c0b006d3b..0703ea61b0 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 = "26cecf4d0cc038d5422fc1aa737eec9c8803a82b" # 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 @@ -171,12 +171,17 @@ 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) +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) diff --git a/lib/nimbase.h b/lib/nimbase.h index 4b338548af..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 @@ -485,13 +492,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 diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 15ce82eb32..9239ca1482 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: "".} @@ -1099,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): 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/asyncnet.nim b/lib/pure/asyncnet.nim index b56289c0c5..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 @@ -207,7 +205,10 @@ proc newAsyncSocket*(domain, sockType, protocol: cint, Protocol(protocol), buffered, inheritable) when defineSsl: - proc getSslError(socket: AsyncSocket, err: cint): cint = + proc raiseSslHandleError = + raiseSSLError("The SSL Handle is closed/unset") + + 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) @@ -220,65 +221,65 @@ 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.} = - 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) - 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) = var opResult {.inject.} = -1.cint while opResult < 0: + if socket.sslHandle == nil: + raiseSslHandleError() 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 @@ -306,14 +307,15 @@ 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+. 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 = @@ -450,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) @@ -464,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`. @@ -727,6 +685,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: @@ -763,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 @@ -782,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 @@ -805,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/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 = 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 dc123d25d5..2a327f8171 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" @@ -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. ## @@ -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]): (lent A, lent B) = ## Iterates over any `(key, value)` pair in the table `t`. @@ -888,7 +950,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 +1107,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 +1359,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 +1411,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 +1609,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 +1969,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 +2110,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. ## @@ -2267,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>`_. @@ -2674,10 +2732,6 @@ iterator mvalues*[A](t: var CountTable[A]): var int = - - - - # --------------------------------------------------------------------------- # ---------------------------- CountTableRef -------------------------------- # --------------------------------------------------------------------------- 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/nativesockets.nim b/lib/pure/nativesockets.nim index 2bae53d6c8..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. @@ -723,7 +730,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()) 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/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 = "" diff --git a/lib/pure/streamwrapper.nim b/lib/pure/streamwrapper.nim index 99752a9ab0..10fdd0861e 100644 --- a/lib/pure/streamwrapper.nim +++ b/lib/pure/streamwrapper.nim @@ -105,7 +105,8 @@ proc newPipeOutStream*[T](s: sink (ref T)): owned PipeOutStream[T] = new(result) for dest, src in fields((ref T)(result)[], s[]): dest = src - wasMoved(s[]) + {.cast(raises: []), cast(tags: []).}: + wasMoved(s[]) if result.readLineImpl != nil: result.baseReadLineImpl = result.readLineImpl result.readLineImpl = posReadLine[T] 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/lib/pure/strutils.nim b/lib/pure/strutils.nim index 687dedd514..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) @@ -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,38 @@ 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" + result = newStringUninit(s.len) + for i in 0..".} - 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/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 {'+', '-'}: 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/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/system.nim b/lib/system.nim index e8d8a8c513..f81c6d5363 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -161,12 +161,10 @@ else: proc `=wasMoved`*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} = ## Generic `wasMoved`:idx: implementation that can be overridden. -proc wasMoved*[T](obj: var T) {.inline, noSideEffect.} = +proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} ## Resets an object `obj` to its initial (binary zero) value to signify ## it was "moved" and to signify its destructor should do nothing and ## ideally be optimized away. - {.cast(raises: []), cast(tags: []).}: - `=wasMoved`(obj) proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} = result = x @@ -1618,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 @@ -1685,7 +1683,7 @@ when not defined(js): else: {.error: "The type T cannot contain managed memory or have destructors".} - proc newStringUninit*(len: Natural): string = + proc newStringUninit*(len: Natural): string {.noSideEffect.} = ## Returns a new string of length `len` but with uninitialized ## content. One needs to fill the string character after character ## with the index operator `s[i]`. @@ -1696,15 +1694,16 @@ when not defined(js): result = newString(len) else: result = newStringOfCap(len) - when defined(nimSeqsV2): - let s = cast[ptr NimStringV2](addr result) - if len > 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.} @@ -2311,8 +2310,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.} @@ -2715,16 +2722,54 @@ 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" + +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 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. @@ -2769,41 +2814,87 @@ 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() + 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) + 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 +2909,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 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/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/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/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/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.} 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/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 = "" 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/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/lib/windows/winlean.nim b/lib/windows/winlean.nim index 9b6b9a28eb..39ee582ee4 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. @@ -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, 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; 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) 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 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/nimsuggest/tests/ttype_highlight.nim b/nimsuggest/tests/ttype_highlight.nim index a324215fe5..d4bf6e4e8d 100644 --- a/nimsuggest/tests/ttype_highlight.nim +++ b/nimsuggest/tests/ttype_highlight.nim @@ -20,8 +20,4 @@ highlight;;skType;;4;;33;;3 highlight;;skType;;5;;13;;1 highlight;;skType;;6;;25;;5 highlight;;skType;;6;;34;;3 -highlight;;skType;;2;;10;;3 -highlight;;skType;;3;;11;;3 -highlight;;skType;;4;;33;;3 -highlight;;skType;;6;;34;;3 """ 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/testament/important_packages.nim b/testament/important_packages.nim index 2471a2d113..b0ef47f9bf 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" @@ -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" 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/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 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 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() diff --git a/tests/arc/tarcmisc.nim b/tests/arc/tarcmisc.nim index 6b8fc3b06f..f8a50c0d21 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,32 @@ 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() + +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 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 == "" 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/arc/tpartialtupleunpacking1.nim b/tests/arc/tpartialtupleunpacking1.nim new file mode 100644 index 0000000000..6eb9d13522 --- /dev/null +++ b/tests/arc/tpartialtupleunpacking1.nim @@ -0,0 +1,19 @@ +discard """ + output: ''' +destroyed +''' +""" + +# issue #24947 + +type Foo = object + +proc `=destroy`(x: Foo) = + echo "destroyed" + +proc go(): void = + let a = (1,2,3, Foo()) + var b,c,d: int + (b,c,d,_) = a # assignment unpacking + +go() diff --git a/tests/arc/tpartialtupleunpacking2.nim b/tests/arc/tpartialtupleunpacking2.nim new file mode 100644 index 0000000000..45f6d48f04 --- /dev/null +++ b/tests/arc/tpartialtupleunpacking2.nim @@ -0,0 +1,18 @@ +discard """ + output: ''' +destroyed +''' +""" + +# issue #24947 + +type Foo = object + +proc `=destroy`(x: Foo) = + echo "destroyed" + +proc go(): void = + let a = (1,2,3, Foo()) + let (b,c,d,_) = a # let unpacking + +go() 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() 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/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 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/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() diff --git a/tests/ccgbugs/targ_lefttoright.nim b/tests/ccgbugs/targ_lefttoright.nim index a0adce1572..74babe9dc3 100644 --- a/tests/ccgbugs/targ_lefttoright.nim +++ b/tests/ccgbugs/targ_lefttoright.nim @@ -69,3 +69,12 @@ test static: test + +block: + proc say(a: int, b: int) = + doAssert a == 1 + doAssert b == 0 + + var a = 1 + var b = a + say a, (b = move a; a) 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() diff --git a/tests/codegen/titaniummangle.nim b/tests/codegen/titaniummangle.nim index 4b45e59aea..d566900b19 100644 --- a/tests/codegen/titaniummangle.nim +++ b/tests/codegen/titaniummangle.nim @@ -29,6 +29,8 @@ discard """ ccodecheck: "'_ZN14titaniummangle8testFuncE9ContainerI3intE'" ccodecheck: "'_ZN14titaniummangle8testFuncE10Container2I5int325int32E'" ccodecheck: "'_ZN14titaniummangle8testFuncE9ContainerI10Container2I5int325int32EE'" + ccodecheck: "'_ZN14titaniummangle7xxx_s10E'" + ccodecheck: "'_ZN14titaniummangle7xxx_s20E'" """ #When debugging this notice that if one check fails, it can be due to any of the above. @@ -151,6 +153,9 @@ proc testFunc(a: int, xs: varargs[string]) = for x in xs: echo x +proc xxx(v: static int) = + echo v + proc testFunc() = var a = 2 var aPtr = a.addr @@ -188,6 +193,8 @@ proc testFunc() = let c2 = Container2[int32, int32](data: 10, data2: 20) testFunc(c2) testFunc(Container[Container2[int32, int32]](data: c2)) - + xxx(10) + xxx(20) + testFunc() \ No newline at end of file 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/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/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) diff --git a/tests/concepts/tconceptsv2.nim b/tests/concepts/tconceptsv2.nim index 0db79c0269..369fd3e854 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,389 @@ 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 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: 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 + +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 - proc spring[T](w: ArrayLike[T])= echo T - 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) + +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 + +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 + 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) 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") 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 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, +) 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 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() diff --git a/tests/destructor/tdistinctseq.nim b/tests/destructor/tdistinctseq.nim index 5a2ac5eadd..f28c435766 100644 --- a/tests/destructor/tdistinctseq.nim +++ b/tests/destructor/tdistinctseq.nim @@ -6,3 +6,26 @@ type DistinctSeq* = distinct seq[int] # `=destroy`(cast[ptr DistinctSeq](0)[]) var x = @[].DistinctSeq `=destroy`(x) + + +import std/options + +# bug #24801 +type + B[T] = object + case r: bool + of false: + v: ref int + of true: + x: T + E = distinct seq[int] + U = ref object of RootObj + G = ref object of U + +proc a(): E = default(E) +method c(_: U): seq[E] {.base.} = discard +proc p(): seq[E] = c(default(U)) +method c(_: G): seq[E] = discard E(newSeq[seq[int]](1)[0]) +method y(_: U) {.base.} = + let s = default(B[tuple[f: B[int], w: B[int]]]) + discard some(s.x) 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 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/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 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) 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 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..44a41156d6 --- /dev/null +++ b/tests/errmsgs/trecursiveproctype2.nim @@ -0,0 +1,18 @@ +discard """ + errormsg: "illegal recursion in type 'A'" + 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..6991a1aef9 --- /dev/null +++ b/tests/errmsgs/trecursiveproctype3.nim @@ -0,0 +1,9 @@ +discard """ + errormsg: "illegal recursion in type '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..4839b77afb --- /dev/null +++ b/tests/errmsgs/trecursiveproctype4.nim @@ -0,0 +1,10 @@ +discard """ + errormsg: "illegal recursion in type 'EventHandler'" + 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 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/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() 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() diff --git a/tests/generics/ttypeofstatic.nim b/tests/generics/ttypeofstatic.nim new file mode 100644 index 0000000000..d5aa9dadef --- /dev/null +++ b/tests/generics/ttypeofstatic.nim @@ -0,0 +1,21 @@ +block: # issue #24715 + type H[c: static[float64]] = object + value: typeof(c) + + proc u[T: H](_: typedesc[T]) = + discard default(T) + + 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 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 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/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/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() 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 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 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]]() 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[""] = "" 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) 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] 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 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) 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 diff --git a/tests/pragmas/tpragmablock.nim b/tests/pragmas/tpragmablock.nim new file mode 100644 index 0000000000..be53816d55 --- /dev/null +++ b/tests/pragmas/tpragmablock.nim @@ -0,0 +1,11 @@ +discard """ + matrix: "--warningaserror:BareExcept" +""" + +{.warning[BareExcept]:on.}: + discard + +try: + echo "Y" +except: # warning disabled here + discard \ No newline at end of file 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]]# diff --git a/tests/proc/tgenericdefaultparam.nim b/tests/proc/tgenericdefaultparam.nim index 7bce591ce5..038110f5d5 100644 --- a/tests/proc/tgenericdefaultparam.nim +++ b/tests/proc/tgenericdefaultparam.nim @@ -96,3 +96,40 @@ block: # issue #24121 proc baz[T: FooBar](x: T, y = foo(x)): string = y doAssert baz(Foo(123)) == "b" doAssert baz(Bar(123)) == "c" + +block: # issue #24484 + type E = enum A + proc foo[T](t: set[T] = {T.A}) = + discard + foo[E]() + + proc bar[T](t: set[T] = {T(0), 5}) = + doAssert t == {0, 5} + bar[uint8]() + doAssert not compiles(bar[string]()) + +block: # issue #24484, array version + type E = enum A + proc foo[T](t: openArray[T] = [T.A]) = + discard + foo[E]() + + proc bar[T](t: openArray[T] = [T(0), 5]) = + doAssert t == [T(0), 5] + bar[uint8]() + +block: # issue #24484, tuple version + type E = enum A + proc foo[T](t = (T.A,)) = + discard + foo[E]() + + proc bar[T](t: (T, int) = (T(0), 5)) = + doAssert t == (T(0), 5) + bar[uint8]() + +block: # issue #24672 + func initArray[T](arg: array[1, T] = [T.high]): array[1, T] = + return arg + + discard initArray[float]() # this would compile and print [inf] in previous versions. diff --git a/tests/proc/trecursivereturntype.nim b/tests/proc/trecursivereturntype.nim new file mode 100644 index 0000000000..8205fd4b87 --- /dev/null +++ b/tests/proc/trecursivereturntype.nim @@ -0,0 +1,15 @@ +# issue #7706 + +type + Op = enum + Halt + Inc + Dec + +type + InstrNext = proc (val: var int, code: seq[Op], pc: var int, stop: var bool): OpH {.inline, nimcall.} + + OpH = object + handler: InstrNext + +var a: OpH 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/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 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 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/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] 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] 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() 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") diff --git a/tests/stdlib/tstrutils.nim b/tests/stdlib/tstrutils.nim index 0cabff8335..db9fb80c44 100644 --- a/tests/stdlib/tstrutils.nim +++ b/tests/stdlib/tstrutils.nim @@ -575,12 +575,29 @@ template main() = doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc" - block: # multiReplace + block: # multiReplace substrings doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab" doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!" doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa" + block: # multiReplace characters + # https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions + const SanitationRules = [ + ({'\0'..'\31'}, ' '), + ({'"'}, '\''), + ({'/', '\\', ':', '|'}, '-'), + ({'*', '?', '<', '>'}, '_'), + ] + # 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 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() diff --git a/tests/stmt/tmiscunderscore.nim b/tests/stmt/tmiscunderscore.nim index c4bae1c3d8..01db9b573d 100644 --- a/tests/stmt/tmiscunderscore.nim +++ b/tests/stmt/tmiscunderscore.nim @@ -13,3 +13,8 @@ block: type _ = float doAssert not (compiles do: let x: _ = 3) + +block: # bug #24339 + const r = (0, 0) + for _ in r.fields: + let _ = 0 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)) 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" 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" 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 diff --git a/tests/tuples/tconstfield.nim b/tests/tuples/tconstfield.nim new file mode 100644 index 0000000000..ff36258a63 --- /dev/null +++ b/tests/tuples/tconstfield.nim @@ -0,0 +1,13 @@ +# issue #24698 + +type Point = tuple[x, y: int] + +const Origin: Point = (0, 0) + +import macros + +template next(point: Point): Point = + (point.x + 1, point.y + 1) + +discard Origin.x # OK: the field is visible. +discard next(Origin) # Compilation error: the field is not visible. 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) diff --git a/tests/tuples/ttuples_various.nim b/tests/tuples/ttuples_various.nim index e392731d2f..2f20e1b78b 100644 --- a/tests/tuples/ttuples_various.nim +++ b/tests/tuples/ttuples_various.nim @@ -1,11 +1,13 @@ discard """ +targets: "c cpp" +matrix: "--mm:refc; --mm:arc" output: ''' it's nil @[1, 2, 3] ''' """ -import macros +import std/[options, macros] block anontuples: @@ -209,3 +211,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) diff --git a/tests/types/tresemtypesection.nim b/tests/types/tresemtypesection.nim new file mode 100644 index 0000000000..255e6c8637 --- /dev/null +++ b/tests/types/tresemtypesection.nim @@ -0,0 +1,71 @@ +discard """ + output: ''' +NONE +a +NONE +a +''' +""" + +# issue #24887 + +macro foo(x: typed) = + result = x + +foo: + type + Flags64 = distinct uint64 + + const NONE = Flags64(0'u64) + const MAX: Flags64 = Flags64(uint64.high) + + proc `$`(x: Flags64): string = + case x: + of NONE: + return "NONE" + of MAX: + return "MAX" + else: + return "UNKNOWN" + + let okay = Flags64(128'u64) + + echo $NONE + type Foo = ref object + x: int + discard Foo(x: 123) + type Enum = enum a, b, c + echo a + type Bar[T] = object + x: T + 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 + int + + 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)) 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()) 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/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) 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): 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: 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