Compare commits

..

4 Commits

Author SHA1 Message Date
ringabout
ed199bdfcd fix: improve materialization of tuple view types and arguments
Co-authored-by: Copilot <copilot@github.com>
2026-05-05 21:13:52 +08:00
ringabout
af40a2b50d progress 2026-05-04 18:29:27 +08:00
ringabout
c709ee09af test
Co-authored-by: Copilot <copilot@github.com>
2026-05-01 21:21:29 +08:00
ringabout
70c30284b2 fixes #24720; std lib iterators unnecessarily require value copies 2026-04-30 21:30:02 +08:00
70 changed files with 241 additions and 894 deletions

View File

@@ -35,10 +35,6 @@ errors.
- Adds a new warning `--warning:ImplicitRangeConversion` that detects downsizing implicit conversions to range types (e.g., `int -> range[0..255]` or `range[1..256] -> range[0..255]`) that could cause runtime panics. Safe conversions like `range[0..255] -> range[0..65535]` and explicit casts do not trigger warnings. `int` to `Natural` and `Positive` conversions do not trigger warnings, which can be enabled with `--warning:systemRangeConversion`.
- Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
## Standard library additions and changes
[//]: # "Additions:"
@@ -81,9 +77,6 @@ parameter and result types, not just their source-level shape. Use
- `std/re` and `std/nre` are deprecated as PCRE library is obsolete.
Use https://github.com/nitely/nim-regex or `std/nre2`.
See: https://github.com/nim-lang/Nim/issues/23668.
- `std/pegs` now correctly lexes UTF-8 bytes inside bare identifier-style
terminals, so case-insensitive matching of non-ASCII terms (e.g. ``\i café``)
works without single-quoting.
## Language changes

View File

@@ -1647,13 +1647,9 @@ proc canRaise*(fn: PNode): bool =
if fn.typ.n[0].kind == nkSym:
result = false
else:
# A proc-typed value with no explicit raises slot still has
# unspecified effects, which sempass2 treats conservatively.
# Codegen needs to do the same in order to keep goto-exception
# checks after indirect/closure calls.
result = ((fn.typ.n[0].len < effectListLen) or
fn.typ.n[0][exceptionEffects] == nil or
fn.typ.n[0][exceptionEffects].safeLen > 0)
(fn.typ.n[0][exceptionEffects] != nil and
fn.typ.n[0][exceptionEffects].safeLen > 0))
else:
result = false

View File

@@ -2816,9 +2816,9 @@ proc genWasMoved(p: BProc; n: PNode) =
# [addrLoc(p.config, a), getTypeDesc(p.module, a.t)])
proc genMove(p: BProc; n: PNode; d: var TLoc) =
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
if n.len == 4:
# generated by liftdestructors:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
var src: TLoc = initLocExpr(p, n[2])
let destVal = rdLoc(a)
let srcVal = rdLoc(src)
@@ -2838,16 +2838,29 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
else:
if d.k == locNone: d = getTemp(p, n.typ)
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc}:
genAssignment(p, d, a, {})
var op = getAttachedOp(p.module.g.graph, n.typ, attachedWasMoved)
if op == nil or sfOverridden notin op.flags:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
if op == nil:
resetLoc(p, a)
else:
n[1] = makeAddr(n[1], p.module.idgen)
genCall(p, n, d)
var b = initLocExpr(p, newSymNode(op))
case skipTypes(a.t, abstractVar+{tyStatic}).kind
of tyOpenArray, tyVarargs: # todo fixme generated `wasMoved` hooks for
# openarrays, but it probably shouldn't?
let ra = rdLoc(a)
var s: string
if reifiedOpenArray(a.lode):
if a.t.kind in {tyVar, tyLent}:
s = derefField(ra, "Field0") & cArgumentSeparator & derefField(ra, "Field1")
else:
s = dotField(ra, "Field0") & cArgumentSeparator & dotField(ra, "Field1")
else:
s = ra & cArgumentSeparator & ra & "Len_0"
p.s(cpsStmts).addCallStmt(rdLoc(b), s)
else:
let val = if p.module.compileToCpp: rdLoc(a) else: byRefLoc(p, a)
p.s(cpsStmts).addCallStmt(rdLoc(b), val)
else:
var a: TLoc = initLocExpr(p, n[1].skipAddr, {lfEnforceDeref, lfPrepareForMutation})
genAssignment(p, d, a, {})
resetLoc(p, a)

View File

@@ -230,7 +230,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt
# Called by return and break stmts.
# Deals with issues faced when jumping out of try/except/finally stmts.
var stack = newSeq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]](0)
var stack = newSeq[tuple[fin: PNode, inExcept: bool, label: Natural]](0)
inc p.withinBlockLeaveActions
for i in 1..howManyTrys:
@@ -341,9 +341,9 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
call[i][0]
else:
call[i]
if not param.typ.isCompileTimeOnly and (param.kind != nkBracketExpr or param.typ.kind in
if param.kind != nkBracketExpr or param.typ.kind in
{tyRef, tyPtr, tyUncheckedArray, tyArray, tyOpenArray,
tyVarargs, tySequence, tyString, tyCstring, tyTuple}):
tyVarargs, tySequence, tyString, tyCstring, tyTuple}:
let tempLoc = initLocExprSingleUse(p, param)
didGenTemp = didGenTemp or tempLoc.k == locTemp
genOtherArg(p, call, i, typ, res, argBuilder)
@@ -836,26 +836,12 @@ proc raiseExitCleanup(p: BProc, destroy: string) =
p.s(cpsStmts).addGoto("LA" & $p.nestedTryStmts[^1].label & "_")
proc finallyActions(p: BProc) =
if p.config.exc != excGoto:
# Walk past compiler-injected `nkHiddenTryStmt` wrappers (e.g. ARC's
# destructor try/finally that wraps `except T as e:` bodies) to reach
# the user's actual try. We must NOT walk past a real user try whose
# body we are currently in, because a raise from there will be caught
# by that try's own except branches rather than escaping outward.
#
# If after skipping wrappers the next entry is a user try in its
# except branch (inExcept=true), inline its finally body before the
# raise propagates — without this, the C++ sibling-catch rule would
# cause the user's catch(...)/finally pair to be bypassed and the
# finally would be silently dropped.
for i in countdown(p.nestedTryStmts.high, 0):
if p.nestedTryStmts[i].isHidden:
continue
if p.nestedTryStmts[i].inExcept:
let finallyBlock = p.nestedTryStmts[i].fin
if finallyBlock != nil:
genSimpleBlock(p, finallyBlock[0])
return
if p.config.exc != excGoto and p.nestedTryStmts.len > 0 and p.nestedTryStmts[^1].inExcept:
# if the current try stmt have a finally block,
# we must execute it before reraising
let finallyBlock = p.nestedTryStmts[^1].fin
if finallyBlock != nil:
genSimpleBlock(p, finallyBlock[0])
proc raiseInstr(p: BProc; result: var Builder) =
if p.config.exc == excGoto:
@@ -1199,7 +1185,7 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, false, 0.Natural))
if t.kind == nkHiddenTryStmt:
lineCg(p, cpsStmts, "try {$n", [])
@@ -1237,7 +1223,6 @@ proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
else:
scope = initScope(p.s(cpsStmts))
# we handled the error:
linefmt(p, cpsStmts, "T$1_ = nullptr;$n", [etmp])
expr(p, t[i][0], d)
linefmt(p, cpsStmts, "#popCurrentException();$n", [])
endBlockWith(p):
@@ -1386,7 +1371,7 @@ proc genTryCppOld(p: BProc, t: PNode, d: var TLoc) =
genLineDir(p, t)
cgsym(p.module, "popCurrentExceptionEx")
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, false, 0.Natural))
startBlockWith(p):
p.s(cpsStmts).add("try {\n")
expr(p, t[0], d)
@@ -1465,7 +1450,7 @@ proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
let lab = p.labels
let hasExcept = t[1].kind == nkExceptBranch
if hasExcept: inc p.withinTryWithExcept
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, Natural lab))
p.nestedTryStmts.add((fin, false, Natural lab))
p.flags.incl nimErrorFlagAccessed
@@ -1671,7 +1656,7 @@ proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
let fin = if t[^1].kind == nkFinally: t[^1] else: nil
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
p.nestedTryStmts.add((fin, quirkyExceptions, 0.Natural))
expr(p, t[0], d)
var quirkyIf = default(IfBuilder)
var quirkyScope = default(ScopeBuilder)

View File

@@ -75,13 +75,10 @@ type
flags*: set[TCProcFlag]
lastLineInfo*: TLineInfo # to avoid generating excessive 'nimln' statements
currLineInfo*: TLineInfo # AST codegen will make this superfluous
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, isHidden: bool, label: Natural]]
nestedTryStmts*: seq[tuple[fin: PNode, inExcept: bool, label: Natural]]
# in how many nested try statements we are
# (the vars must be volatile then)
# `inExcept` is true when we are in the except part of a try block.
# `isHidden` is true for compiler-injected `nkHiddenTryStmt` wrappers
# (e.g. ARC's destructor try/finally around `except T as e:` bodies);
# finallyActions walks past such wrappers to reach the user's try.
# bool is true when are in the except part of a try block
finallySafePoints*: seq[Rope] # For correctly cleaning up exceptions when
# using return in finally statements
labels*: Natural # for generating unique labels in the C proc

View File

@@ -930,7 +930,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
if m.len == 0:
localError(conf, info, "Cannot resolve filename: " & arg)
else:
conf.implicitImports.add(if arg.startsWith(stdPrefix): arg else: m)
conf.implicitImports.add m
of "include":
expectArg(conf, switch, arg, pass, info)
if pass in {passCmd2, passPP}:

View File

@@ -488,19 +488,10 @@ proc generateBuildFile(c: DepContext): string =
let exeFile = changeFileExt(c.nodes[0].files[0].nimFile, ExeExt)
b.addTree "do"
b.addIdent "nim_nifc"
# Input: .nim file (expanded as argument)
# Input: .nim file (expanded as argument) and .nif file (dependency)
b.addTree "input"
b.addStrLit mainNif
b.endTree()
# Also depend on the semmed .nif files of the main module and all its
# dependencies. nifmake's topological sort orders nodes by depth; without
# these inputs the nim_nifc node sits at depth 1 (no recognized inputs)
# alongside the nifler nodes and runs *before* the nim_m steps that
# produce the .nif files it needs to read.
for node in c.nodes:
b.addTree "input"
b.addStrLit c.semmedFile(node.files[0])
b.endTree()
b.addTree "output"
b.addStrLit exeFile
b.endTree()

View File

@@ -46,7 +46,7 @@ proc isLocation(n: PNode): bool = not n.isValue
proc isLet(n: PNode): bool =
if n.kind == nkSym:
if n.sym.kind in {skLet, skConst, skTemp, skForVar}: # guard immutable variables
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
abstractInst).kind notin {tyVar}:

View File

@@ -13,7 +13,7 @@ import
ast, msgs, options, idents, lookups,
semdata, modulepaths, sigmatch, lineinfos,
modulegraphs, wordrecg
from std/strutils import `%`, startsWith, replace
from std/strutils import `%`, startsWith
from std/sequtils import addUnique
import std/[sets, tables, intsets]
@@ -304,9 +304,9 @@ proc myImportModule(c: PContext, n: var PNode, importStmtResult: PNode): PSym =
var prefix = ""
if realModule.constraint != nil: prefix = realModule.constraint.strVal & "; "
message(c.config, n.info, warnDeprecated, prefix & realModule.name.s & " is deprecated")
let moduleNameNorm = getModuleName(c.config, n).replace("\\", "/")
if belongsToStdlib(c.graph, result) and not startsWith(moduleNameNorm, stdPrefix) and
not startsWith(moduleNameNorm, "system/") and not startsWith(moduleNameNorm, "packages/"):
let moduleName = getModuleName(c.config, n)
if belongsToStdlib(c.graph, result) and not startsWith(moduleName, stdPrefix) and
not startsWith(moduleName, "system/") and not startsWith(moduleName, "packages/"):
message(c.config, n.info, warnStdPrefix, realModule.name.s)
proc suggestMod(n: PNode; s: PSym) =

View File

@@ -1349,7 +1349,7 @@ proc rawGetTok*(L: var Lexer, tok: var Token) =
lexMessage(L, errGenerated, "invalid token: no whitespace between number and identifier")
of '-':
if L.buf[L.bufpos+1] in {'0'..'9'} and
(L.bufpos == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
(L.bufpos-1 == 0 or L.buf[L.bufpos-1] in UnaryMinusWhitelist):
# x)-23 # binary minus
# ,-23 # unary minus
# \n-78 # unary minus? Yes.

View File

@@ -259,9 +259,6 @@ type
## Old transformation for closures in JS backend
noPanicOnExcept
## don't panic on bare except
procParamTypeBackendAliases
## Keep the old proc type compatibility rules that ignore backend
## c type aliases.
SymbolFilesOption* = enum
disabledSf, writeOnlySf, readOnlySf, v2Sf, stressTest

View File

@@ -637,11 +637,6 @@ proc renderNotLValue*(n: PNode): string =
elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2:
result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")"
proc isSsoStringIndex*(conf: ConfigRef; n: PNode): bool =
result = conf.usesSso() and n.kind == nkBracketExpr and n.len >= 1 and
n[0].typ != nil and
n[0].typ.skipTypes(abstractVar + abstractInst - {tyTypeDesc}).kind == tyString
proc isAssignable(c: PContext, n: PNode): TAssignableResult =
result = parampatterns.isAssignable(c.p.owner, n)

View File

@@ -652,9 +652,6 @@ proc overloadedCallOpr(c: PContext, n: PNode): PNode =
result = semExpr(c, result, flags = {efNoUndeclared})
proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
template isViewTarget(t: PType): bool =
t.skipTypes({tyGenericInst, tyAlias, tySink}).kind in {tyVar, tyLent}
case n.kind
of nkCurly:
for i in 0..<n.len:
@@ -683,15 +680,12 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
if f == nil:
globalError(c.config, m.info, "unknown identifier: " & m.sym.name.s)
return
if not isViewTarget(f.typ):
changeType(c, n[i][1], f.typ, check)
changeType(c, n[i][1], f.typ, check)
else:
if not isViewTarget(tup[i]):
changeType(c, n[i][1], tup[i], check)
changeType(c, n[i][1], tup[i], check)
else:
for i in 0..<n.len:
if not isViewTarget(tup[i]):
changeType(c, n[i], tup[i], check)
changeType(c, n[i], tup[i], check)
when false:
var m = n[i]
var a = newNodeIT(nkExprColonExpr, m.info, newType[i])
@@ -714,7 +708,6 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) =
localError(c.config, n.info, "cannot convert '" & n.sym.name.s &
"' to '" & typeNameAndDesc(newType) & "'")
else: discard
n.typ = newType
proc arrayConstrType(c: PContext, n: PNode): PType =

View File

@@ -65,7 +65,7 @@ proc semTypeOf(c: PContext; n: PNode): PNode =
t.incl tfNonConstExpr
else:
t = base
result.typ = makeTypeDesc(c, t)
result.typ = makeTypeDesc(c, decayTypeOfView(c, t))
type
SemAsgnMode = enum asgnNormal, noOverloadedSubscript, noOverloadedAsgn
@@ -248,13 +248,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym)
assert operand.kind == tyTuple, $operand.kind
result = newIntNodeT(toInt128(operand.len), traitCall, c.idgen, c.graph)
of "distinctBase":
var arg = operand.skipTypes(skippedTypes)
var arg = operand.skipTypes({tyGenericInst})
let rec = semConstExpr(c, traitCall[2]).intVal != 0
while true:
let distinctArg = arg.skipTypes(skippedTypes + {tyGenericInst})
if distinctArg.kind != tyDistinct:
break
arg = distinctArg.base.skipTypes(skippedTypes)
while arg.kind == tyDistinct:
arg = arg.base.skipTypes(skippedTypes + {tyGenericInst})
if not rec: break
result = getTypeDescNode(c, arg, operand.owner, traitCall.info)
of "rangeBase":

View File

@@ -486,11 +486,6 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags; expectedType: PType
# we have to watch out, there are also 'owned proc' types that can be used
# multiple times as long as they don't have closures.
result.typ.incl tfHasOwned
if t.kind == tyForward and efDetermineType in flags:
# a forward object type does not error during determine-type analysis;
# it now stays unresolved long enough for the existing delayed field-default pass to resolve it after the type section finishes.
result.typ = t
return result
if t.kind != tyObject:
return localErrorNode(c, result, if t.kind != tyGenericBody:
"object constructor needs an object type".dup(addTypeNodeDeclaredLoc(c.config, t))

View File

@@ -809,10 +809,6 @@ proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; ar
markSideEffect(tracked, a, n.info)
let paramType = if formals != nil and argIndex < formals.signatureLen: formals[argIndex] else: nil
if paramType != nil and paramType.kind in {tyVar}:
let arg = n.skipAddr()
if isSsoStringIndex(tracked.config, arg):
localError(tracked.config, arg.info,
"expression '$1' is immutable, not 'var'" % renderNotLValue(arg))
invalidateFacts(tracked.guards, n)
if n.kind == nkSym and isLocalSym(tracked, n.sym):
makeVolatile(tracked, n.sym)
@@ -1208,7 +1204,6 @@ type
enforcedGcSafety, enforceNoSideEffects: bool
oldExc, oldTags, oldForbids: int
exc, tags, forbids: PNode
excSource, tagsSource, forbidsSource: PNode
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
var oldForbidsLen = 0
@@ -1231,18 +1226,17 @@ proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
setLen(tracked.exc.sons, bc.oldExc)
for e in bc.exc:
addRaiseEffect(tracked, e, if bc.excSource != nil: bc.excSource else: e)
addRaiseEffect(tracked, e, e)
if bc.tags != nil:
setLen(tracked.tags.sons, bc.oldTags)
for t in bc.tags:
addTag(tracked, t, if bc.tagsSource != nil: bc.tagsSource else: t)
addTag(tracked, t, t)
if bc.forbids != nil:
setLen(tracked.forbids.sons, bc.oldForbids)
for t in bc.forbids:
addNotTag(tracked, t, if bc.forbidsSource != nil: bc.forbidsSource else: t)
addNotTag(tracked, t, t)
proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext) =
let pragma = castPragma[1]
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
case whichPragma(pragma)
of wGcSafe:
bc.enforcedGcSafety = true
@@ -1255,7 +1249,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
else:
bc.tags = newNodeI(nkArgList, pragma.info)
bc.tags.add n
bc.tagsSource = castPragma
of wForbids:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
@@ -1263,7 +1256,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
else:
bc.forbids = newNodeI(nkArgList, pragma.info)
bc.forbids.add n
bc.forbidsSource = castPragma
of wRaises:
let n = pragma[1]
if n.kind in {nkCurly, nkBracket}:
@@ -1271,7 +1263,6 @@ proc castBlock(tracked: PEffects, castPragma: PNode, bc: var PragmaBlockContext)
else:
bc.exc = newNodeI(nkArgList, pragma.info)
bc.exc.add n
bc.excSource = castPragma
of wUncheckedAssign:
discard "handled in sempass1"
else:
@@ -1308,8 +1299,6 @@ proc allowCStringConv(n: PNode): bool =
proc track(tracked: PEffects, n: PNode) =
case n.kind
of nkTypeOfExpr:
discard "typeof() never evaluates its operand; not a definite-assignment use"
of nkSym:
useVar(tracked, n)
if n.sym.typ != nil and tfHasAsgn in n.sym.typ.flags:
@@ -1527,7 +1516,7 @@ proc track(tracked: PEffects, n: PNode) =
of wNoSideEffect:
bc.enforceNoSideEffects = true
of wCast:
castBlock(tracked, pragmaList[i], bc)
castBlock(tracked, pragmaList[i][1], bc)
else:
discard
applyBlockContext(tracked, bc)
@@ -1559,10 +1548,9 @@ proc track(tracked: PEffects, n: PNode) =
message(tracked.config, n.info, warnPtrToCstringConv,
$n[1].typ)
# Check for implicit range conversions. Compile-time constants are already
# fully known here, so only non-constant values need the downsizing warning.
# Check for implicit range conversions
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
getConstExpr(tracked.ownerModule, n[1], tracked.c.idgen, tracked.graph) == nil and
n[1].kind notin {nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit} and
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ):
message(tracked.config, n.info, warnImplicitRangeConversion,
typeToString(n[1].typ) & " -> " & typeToString(n.typ))

View File

@@ -815,6 +815,14 @@ proc makeVarTupleSection(c: PContext, n, a, def: PNode, typ: PType, symkind: TSy
lastDef[^1] = val
result.add(lastDef)
proc materializeDirectView(n: PNode): PNode =
let t = n.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned})
if t.kind in {tyVar, tyLent}:
result = newNodeIT(nkHiddenDeref, n.info, t.elementType)
result.add n
else:
result = n
proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
var b: PNode
result = copyNode(n)
@@ -881,6 +889,10 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
#changeType(def.skipConv, typ, check=true)
else:
typ = def.typ.skipTypes({tyStatic, tySink}).skipIntLit(c.idgen)
let directTyp = typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned})
if directTyp.kind in {tyVar, tyLent}:
def = materializeDirectView(def)
typ = def.typ.skipTypes({tyStatic, tySink}).skipIntLit(c.idgen)
if typ.kind in tyUserTypeClasses and typ.isResolvedUserTypeClass:
typ = typ.last
if hasEmpty(typ):
@@ -908,7 +920,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
if c.matchedConcept != nil:
typFlags.incl taConcept
typeAllowedCheck(c, a.info, typ, symkind, typFlags)
if a.kind != nkVarTuple:
typeAllowedCheck(c, a.info, typ, symkind, typFlags)
var tup = skipTypes(typ, {tyGenericInst, tyAlias, tySink})
if a.kind == nkVarTuple:

View File

@@ -370,7 +370,6 @@ proc semFieldDefault(c: PContext; owner, expectedType: PType; field: PNode): PTy
propagateToOwner(owner, result)
proc semDelayedFieldDefault(c: PContext; owner, expectedType: PType; field: PNode) =
resetSemFlag(field[^1])
fitDefaultNode(c, field[^1], expectedType)
propagateToOwner(owner, field[^1].typ.skipIntLit(c.idgen))
@@ -1880,6 +1879,38 @@ proc fixupTypeOf(c: PContext, prev: PType, typ: PType) =
if prev.kind != tyGenericBody:
assignType(prev, result)
proc decayTypeOfView(c: PContext, typ: PType): PType =
if typ == nil: return nil
let t = typ.skipTypes({tyGenericInst, tyAlias, tySink})
case t.kind
of tyVar, tyLent:
result = decayTypeOfView(c, t.elementType)
of tyTuple:
var changed = false
var kids = newSeq[PType](t.len)
for i in 0..<t.len:
kids[i] = decayTypeOfView(c, t[i])
if kids[i] != t[i]: changed = true
if changed:
result = copyType(t, c.idgen, t.owner)
for i in 0..<kids.len:
result[i] = kids[i]
if t.n != nil:
result.n = copyNode(t.n)
for it in t.n:
if it.kind == nkSym and it.sym.kind == skField:
let field = copySym(it.sym, c.idgen)
field.ast = it.sym.ast
if field.position >= 0 and field.position < kids.len:
field.typ = kids[field.position]
result.n.add newSymNode(field, it.info)
else:
result.n.add copyTree(it)
else:
result = typ
else:
result = typ
proc semTypeExpr(c: PContext, n: PNode; prev: PType): PType =
var n = semExprWithType(c, n, {efDetermineType})
if n.typ.kind == tyTypeDesc:
@@ -2079,6 +2110,7 @@ proc semTypeOf(c: PContext; n: PNode; prev: PType): PType =
result.incl tfNonConstExpr
else:
result = base
result = decayTypeOfView(c, result)
fixupTypeOf(c, prev, result)
proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
@@ -2104,6 +2136,7 @@ proc semTypeOf2(c: PContext; n: PNode; prev: PType): PType =
result.incl tfNonConstExpr
else:
result = base
result = decayTypeOfView(c, result)
fixupTypeOf(c, prev, result)
proc semTypeIdent(c: PContext, n: PNode): PSym =

View File

@@ -15,6 +15,8 @@ import
magicsys, idents, lexer, options, parampatterns, trees,
linter, lineinfos, lowerings, modulegraphs, concepts, layeredtable
import typeallowed
import std/[intsets, strutils, tables]
when defined(nimPreviewSlimSystem):
@@ -123,6 +125,58 @@ proc initCandidate*(ctx: PContext, callee: PType): TCandidate =
result.calleeSym = nil
result.bindings = initLayeredTypeMap()
proc materializeTupleViewType(t: PType; idgen: IdGenerator): PType =
case t.kind
of tyVar, tyLent:
result = materializeTupleViewType(t.elementType, idgen)
of tyTuple:
if classifyViewType(t) == noView:
result = t
else:
result = copyType(t, idgen, t.owner)
for i in 0..<t.len:
result[i] = materializeTupleViewType(t[i], idgen)
if t.n != nil:
result.n = copyNode(t.n)
for it in t.n:
if it.kind == nkSym and it.sym.kind == skField:
let field = copySym(it.sym, idgen)
field.ast = it.sym.ast
if field.position >= 0 and field.position < result.len:
field.typ = result[field.position]
result.n.add newSymNode(field, it.info)
else:
result.n.add copyTree(it)
else:
result = t
proc materializeTupleViewArg(c: PContext; targetType: PType; arg: PNode): PNode =
let targetTuple = targetType.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct, tyInferred})
var tupleArg = arg
var prefix: PNode = nil
if targetTuple.len > 1 and arg.kind notin {nkHiddenAddr, nkSym}:
prefix = evalOnce(c.graph, arg, c.idgen, getCurrOwner(c))
tupleArg = prefix[^1]
let tupleConstr = newNodeIT(nkTupleConstr, arg.info, targetType)
for i in 0..<targetTuple.len:
let targetField = targetTuple[i]
var field = newTupleAccess(c.graph, tupleArg, i)
let sourceField = field.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct, tyInferred})
if sourceField.kind in {tyVar, tyLent}:
field = newDeref(field)
elif targetField.kind == tyTuple and classifyViewType(sourceField) != noView:
field = materializeTupleViewArg(c, targetField, field)
tupleConstr.add field
if prefix == nil:
result = tupleConstr
else:
result = newNodeIT(nkStmtListExpr, arg.info, targetType)
for i in 0..<(prefix.len - 1):
result.add prefix[i]
result.add tupleConstr
proc put(c: var TCandidate, key, val: PType) {.inline.} =
## Given: proc foo[T](x: T); foo(4)
## key: 'T'
@@ -135,7 +189,12 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} =
writeStackTrace()
if c.c.module.name.s == "temp3":
echo "binding ", key, " -> ", val
put(c.bindings, key, val.skipIntLit(c.c.idgen))
let normalized = val.skipIntLit(c.c.idgen)
if normalized.kind == tyTuple and classifyViewType(normalized) != noView:
put(c.bindings, key, materializeTupleViewType(normalized, c.c.idgen))
else:
put(c.bindings, key, normalized)
proc typeRel*(c: var TCandidate, f, aOrig: PType,
flags: TTypeRelFlags = {}): TTypeRelation
@@ -784,19 +843,6 @@ proc procParamTypeRel(c: var TCandidate; f, a: PType): TTypeRelation =
# if f is metatype.
result = typeRel(c, f, a)
if result == isEqual and
procParamTypeBackendAliases notin c.c.config.legacyFeatures:
# Ensure types that are semantically equal also match at the backend level.
# E.g. reject assigning proc(csize_t) to proc(uint) since these map to
# different C types (size_t vs unsigned long long).
let fCheck = concreteType(c, f)
let aCheck = concreteType(c, a)
# Note that `result` is equal; now check whether they have the same
# backend type.
if fCheck != nil and aCheck != nil and
not sameBackendTypePickyAliases(fCheck, aCheck, {IgnoreFlags}):
result = isNone
if result <= isSubrange or inconsistentVarTypes(f, a):
result = isNone
@@ -2213,7 +2259,16 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate,
if result.typ == nil: internalError(c.graph.config, arg.info, "implicitConv")
result.add c.graph.emptyNode
if arg.typ != nil and arg.typ.kind == tyLent:
let targetTuple = result.typ.skipTypes({tyVar, tyGenericInst, tyAlias, tySink, tyDistinct, tyInferred})
let sourceTuple =
if arg.typ != nil:
arg.typ.skipTypes({tyGenericInst, tyAlias, tySink, tyDistinct, tyInferred})
else:
nil
if sourceTuple != nil and sourceTuple.kind == tyTuple and targetTuple.kind == tyTuple and
classifyViewType(arg.typ) != noView and classifyViewType(result.typ) == noView:
result.add materializeTupleViewArg(c, targetTuple, arg)
elif arg.typ != nil and arg.typ.kind == tyLent:
let a = newNodeIT(nkHiddenDeref, arg.info, arg.typ.elementType)
a.add arg
result.add a
@@ -2473,10 +2528,6 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
return arg
elif f.kind == tyStatic and arg.typ.n != nil:
return arg.typ.n
elif f.kind == tyUntyped:
# bug #25693: a different overload candidate may have sem-checked the
# operand and left symbols behind; templates expect the pristine AST.
return argOrig
else:
return argSemantized # argOrig

View File

@@ -10,7 +10,7 @@
## This module implements threadpool's ``spawn``.
import ast, types, idents, magicsys, msgs, options, modulegraphs,
lowerings, liftdestructors, renderer, trees
lowerings, liftdestructors, renderer
from trees import getMagic, getRoot
proc callProc(a: PNode): PNode =
@@ -53,24 +53,6 @@ proc typeNeedsNoDeepCopy(t: PType): bool =
if t.kind in {tyVar, tyLent, tySequence}: t = t.elementType
result = not containsGarbageCollectedRef(t)
proc newSpawnMoveStmt(g: ModuleGraph; idgen: IdGenerator; le, ri: PNode): PNode =
let op = getAttachedOp(g, ri.typ.skipTypes({tyGenericInst, tyAlias, tyVar, tySink}), attachedWasMoved)
if op != nil and sfOverridden in op.flags:
result = newNodeI(nkStmtList, le.info)
result.add newFastAsgnStmt(le, ri)
let wasMovedCall = newNodeI(nkCall, ri.info)
wasMovedCall.add newSymNode(op)
if op.typ != nil and op.typ.signatureLen > 1 and op.typ.firstParamType.kind != tyVar:
wasMovedCall.add ri.skipAddr
else:
wasMovedCall.add makeAddr(ri.skipAddr, idgen)
result.add wasMovedCall
else:
result = newFastMoveStmt(g, le, ri)
proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator; owner: PSym; typ: PType;
v: PNode; useShallowCopy=false): PSym =
result = newSym(skTemp, getIdent(g.cache, genPrefix), idgen, owner, varSection.info,
@@ -86,10 +68,10 @@ proc addLocalVar(g: ModuleGraph; varSection, varInit: PNode; idgen: IdGenerator;
if varInit != nil:
if g.config.selectedGC in {gcArc, gcOrc, gcAtomicArc, gcYrc}:
# inject destructors pass will do its own analysis
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
if useShallowCopy and typeNeedsNoDeepCopy(typ) or optTinyRtti in g.config.globalOptions:
varInit.add newSpawnMoveStmt(g, idgen, newSymNode(result), v)
varInit.add newFastMoveStmt(g, newSymNode(result), v)
else:
let deepCopyCall = newNodeI(nkCall, varInit.info, 3)
deepCopyCall[0] = newSymNode(getSysMagic(g, varSection.info, "deepCopy", mDeepCopy))

View File

@@ -897,7 +897,7 @@ proc sameTypeAux(x, y: PType, c: var TSameTypeClosure): bool =
c.flags = oldFlags
if x == y: return true
let aliasSkipSet = maybeSkipRange({tyAlias, tyInferred})
let aliasSkipSet = maybeSkipRange({tyAlias})
var a = skipTypes(x, aliasSkipSet)
while a.kind == tyUserTypeClass and tfResolved in a.flags:
a = skipTypes(a.last, aliasSkipSet)
@@ -1069,10 +1069,9 @@ proc sameBackendTypeIgnoreRange*(x, y: PType): bool =
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)
proc sameBackendTypePickyAliases*(x, y: PType, flags: TTypeCmpFlags = {}): bool =
proc sameBackendTypePickyAliases*(x, y: PType): bool =
var c = initSameTypeClosure()
c.flags.incl {IgnoreTupleFields, IgnoreRangeShallow, PickyCAliases, PickyBackendAliases}
c.flags.incl flags
c.cmp = dcEqIgnoreDistinct
result = sameTypeAux(x, y, c)

View File

@@ -1842,8 +1842,6 @@ proc genArrAccessOpcode(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode;
if dest < 0: dest = c.getTemp(n.typ)
if opc in {opcLdArrAddr, opcLdStrIdxAddr} and gfNodeAddr in flags:
c.gABC(n, opc, dest, a, b)
if c.prc.regInfo[a].kind >= slotTempUnknown:
c.prc.regInfo[a].kind = slotTempPerm
elif needsRegLoad():
var cc = c.getTemp(n.typ)
c.gABC(n, opc, cc, a, b)
@@ -1860,8 +1858,6 @@ proc genObjAccessAux(c: PCtx; n: PNode; a, b: int, dest: var TDest; flags: TGenF
if dest < 0: dest = c.getTemp(n.typ)
if {gfNodeAddr} * flags != {}:
c.gABC(n, opcLdObjAddr, dest, a, b)
if a < c.prc.regInfo.len and c.prc.regInfo[a].kind >= slotTempUnknown:
c.prc.regInfo[a].kind = slotTempPerm
elif needsRegLoad():
var cc = c.getTemp(n.typ)
c.gABC(n, opcLdObj, cc, a, b)

View File

@@ -33,7 +33,7 @@ The text representation is particularly valuable for debugging and introspection
Each ``.nim`` module produces its own ``.nif`` file during compilation.
The NIF format contains:
- **Header** - Version information (e.g., `(.nif27)`)
- **Header** - Version information (e.g., `(.nif26)`)
- **Dependencies** - List of source files and dependencies
- **Interface** - Exported symbols and their indices
- **Body** - The intermediate representation of the module's code in Lisp-like syntax

View File

@@ -34,10 +34,10 @@ To learn how to compile Nim programs and generate documentation see
the [Compiler User Guide](nimc.html) and the [DocGen Tools Guide](docgen.html).
The language constructs are explained using an extended BNF, in which `(a)*`
means 0 or more *a*'s, `a+` means 1 or more *a*'s, and `(a)?` means an
means 0 or more `a`'s, `a+` means 1 or more `a`'s, and `(a)?` means an
optional *a*. Parentheses may be used to group elements.
`&` is the lookahead operator; `&a` means that an *a* is expected but
`&` is the lookahead operator; `&a` means that an `a` is expected but
not consumed. It will be consumed in the following rule.
The `|`, `/` symbols are used to mark alternatives and have the lowest
@@ -1024,9 +1024,6 @@ These are the major type classes:
* procedural type
* generic type
The compiler's internal type zoo is richer than this summary suggests:
some types that are structurally equal still differ in backend representation.
Ordinal types
-------------
@@ -2177,10 +2174,6 @@ Procedural type
A procedural type is internally a pointer to a procedure. `nil` is
an allowed value for a variable of a procedural type.
Procedure compatibility also checks the backend representation of the
parameter and result types, not just their source-level shape. Use
`--legacy:procParamTypeBackendAliases` to restore the older behavior.
Examples:
```nim
@@ -8874,7 +8867,7 @@ Byref pragma
The `byref` pragma can be applied to an object or tuple type or a proc param.
When applied to a type it instructs the compiler to pass the type by reference
(hidden pointer) to procs. When applied to a param it will take precedence, even
if the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
if the the type was marked as `bycopy`. When an `importc` type has a `byref` pragma or
parameters are marked as `byref` in an `importc` proc, these params translate to pointers.
When an `importcpp` type has a `byref` pragma, these params translate to
C++ references `&`.

View File

@@ -1144,7 +1144,7 @@ there is a difference between the `$` and `repr` outputs:
echo myCharacter, ":", repr(myCharacter)
# --> n:'n'
echo myString, ":", repr(myString)
# --> nim:"nim"
# --> nim:0x10fa8c050"nim"
echo myInteger, ":", repr(myInteger)
# --> 42:42
echo myFloat, ":", repr(myFloat)

View File

@@ -16,11 +16,11 @@ const
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
NimonyStableCommit = "c189ef438598878b2f02f6a2ff91d08febafc04b" # unversioned \
# Note that Nimony uses Nim as a git submodule but we don't want to install
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
# is **required** here.
# Commit from 2026-05-05
# Commit from 2026-04-27
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"

View File

@@ -153,7 +153,7 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
protocol)
result.orig = protocol
i.inc protocol.parseSaturatedNatural(result.major, i)
if i < protocol.len: inc i # Skip .
i.inc # Skip .
i.inc protocol.parseSaturatedNatural(result.minor, i)
proc sendStatus(client: AsyncSocket, status: string): Future[void] =

View File

@@ -495,16 +495,13 @@ func `$`*[T](c: CritBitTree[T]): string =
const avgItemLen = 16
result = newStringOfCap(c.count * avgItemLen)
result.add("{")
var first = true
when T is void:
for key in keys(c):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(key)
else:
for key, val in pairs(c):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(key)
result.add(": ")
result.addQuoted(val)

View File

@@ -454,10 +454,8 @@ proc `$`*[T](deq: Deque[T]): string =
assert $a == "[10, 20, 30]"
result = "["
var first = true
for x in deq:
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -260,9 +260,7 @@ proc `$`*[T](heap: HeapQueue[T]): string =
assert $heap == "[1, 2]"
result = "["
var first = true
for x in heap.data:
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x)
result.add("]")

View File

@@ -304,10 +304,8 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string =
assert $a == "[1, 2, 3, 4]"
result = "["
var first = true
for x in nodes(L):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.addQuoted(x.value)
result.add("]")

View File

@@ -739,7 +739,7 @@ template withValue*[A, B](t: Table[A, B], key: A,
discard
iterator pairs*[A, B](t: Table[A, B]): (A, B) =
iterator pairs*[A, B](t: Table[A, B]): (lent A, lent B) =
## Iterates over any `(key, value)` pair in the table `t`.
##
## See also:
@@ -1201,7 +1201,7 @@ proc `==`*[A, B](s, t: TableRef[A, B]): bool =
iterator pairs*[A, B](t: TableRef[A, B]): (A, B) =
iterator pairs*[A, B](t: TableRef[A, B]): (lent A, lent B) =
## Iterates over any `(key, value)` pair in the table `t`.
##
## See also:
@@ -1789,7 +1789,7 @@ proc `==`*[A, B](s, t: OrderedTable[A, B]): bool =
iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) =
iterator pairs*[A, B](t: OrderedTable[A, B]): (lent A, lent B) =
## Iterates over any `(key, value)` pair in the table `t` in insertion
## order.
##
@@ -2212,7 +2212,7 @@ proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool =
iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) =
iterator pairs*[A, B](t: OrderedTableRef[A, B]): (lent A, lent B) =
## Iterates over any `(key, value)` pair in the table `t` in insertion
## order.
##
@@ -2622,7 +2622,7 @@ proc `==`*[A](s, t: CountTable[A]): bool =
equalsImpl(s, t)
iterator pairs*[A](t: CountTable[A]): (A, int) =
iterator pairs*[A](t: CountTable[A]): (lent A, int) =
## Iterates over any `(key, value)` pair in the table `t`.
##
## See also:
@@ -2899,7 +2899,7 @@ proc `==`*[A](s, t: CountTableRef[A]): bool =
else: result = s[] == t[]
iterator pairs*[A](t: CountTableRef[A]): (A, int) =
iterator pairs*[A](t: CountTableRef[A]): (lent A, int) =
## Iterates over any `(key, value)` pair in the table `t`.
##
## See also:

View File

@@ -175,48 +175,23 @@ proc parseEscapedUTF16*(buf: cstring, pos: var int): int =
else:
return -1
proc addSpan(dst: var string; src: string; startPos, endPos: int) {.inline.} =
let n = endPos - startPos
if n <= 0:
return
let old = dst.len
dst.setLen old + n
template impl =
for i in 0..<n:
dst[old + i] = src[startPos + i]
when nimvm:
impl
else:
when defined(js) or defined(nimscript):
impl
else:
{.noSideEffect.}:
copyMem dst[old].addr, src[startPos].unsafeAddr, n
proc parseString(my: var JsonParser): TokKind =
result = tkString
var pos = my.bufpos + 1
var spanStart = pos
if my.rawStringLiterals:
add(my.a, '"')
while true:
case my.buf[pos]
of '\0':
my.err = errInvalidToken
addSpan(my.a, my.buf, spanStart, pos)
my.err = errQuoteExpected
result = tkError
break
of '"':
addSpan(my.a, my.buf, spanStart, pos)
if my.rawStringLiterals:
add(my.a, '"')
inc(pos)
break
of '\\':
addSpan(my.a, my.buf, spanStart, pos)
if my.rawStringLiterals:
add(my.a, '\\')
case my.buf[pos+1]
@@ -276,18 +251,14 @@ proc parseString(my: var JsonParser): TokKind =
# don't bother with the error
add(my.a, my.buf[pos])
inc(pos)
spanStart = pos
of '\c':
addSpan(my.a, my.buf, spanStart, pos)
pos = lexbase.handleCR(my, pos)
add(my.a, '\c')
spanStart = pos
of '\L':
addSpan(my.a, my.buf, spanStart, pos)
pos = lexbase.handleLF(my, pos)
add(my.a, '\L')
spanStart = pos
else:
add(my.a, my.buf[pos])
inc(pos)
my.bufpos = pos # store back

View File

@@ -1668,10 +1668,7 @@ func getSymbol(c: var PegLexer, tok: var Token) =
while pos < c.buf.len:
add(tok.literal, c.buf[pos])
inc(pos)
if pos < c.buf.len:
let ch = c.buf[pos]
# Keep non-ASCII bytes so UTF-8 terminals reach the rune-aware matchers.
if ch notin strutils.IdentChars and ord(ch) < 0x80: break
if pos < c.buf.len and c.buf[pos] notin strutils.IdentChars: break
c.bufpos = pos
tok.kind = tkIdentifier

View File

@@ -380,10 +380,8 @@ proc `$`*(t: StringTableRef): string {.rtlFunc, extern: "nstDollar".} =
result = "{:}"
else:
result = "{"
var first = true
for key, val in pairs(t):
if first: first = false
else: result.add(", ")
if result.len > 1: result.add(", ")
result.add(key)
result.add(": ")
result.add(val)

View File

@@ -166,7 +166,7 @@ proc wasMoved*[T](obj: var T) {.magic: "WasMoved", noSideEffect.}
## it was "moved" and to signify its destructor should do nothing and
## ideally be optimized away.
proc move*[T](x: var T): T {.magic: "Move", noSideEffect, nodestroy.} =
proc move*[T](x: var T): T {.magic: "Move", noSideEffect.} =
result = x
{.cast(raises: []), cast(tags: []).}:
`=wasMoved`(x)
@@ -2694,9 +2694,7 @@ when hasAlloc or defined(nimscript):
setLen(x, xl+item.len)
var j = xl-1
while j >= i:
when defined(nimsso):
x[j+item.len] = x[j]
elif defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
when defined(gcArc) or defined(gcOrc) or defined(gcYrc) or defined(gcAtomicArc):
x[j+item.len] = move x[j]
else:
shallowCopy(x[j+item.len], x[j])

View File

@@ -691,7 +691,7 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
removeChunkFromMatrix2(a, result, fl, sl)
if result.size >= size + PageSize:
splitChunk(a, result, size)
# set 'used' to true:
# set 'used' to to true:
result.prevSize = 1
track("setUsedToFalse", addr result.size, sizeof(int))
sysAssert result.owner == addr a, "getBigChunk: No owner set!"
@@ -708,7 +708,7 @@ proc getHugeChunk(a: var MemRegion; size: int): PBigChunk =
result.next = nil
result.prev = nil
result.size = size
# set 'used' to true:
# set 'used' to to true:
result.prevSize = 1
result.owner = addr a
incl(a, a.chunkStarts, pageIndex(result))

View File

@@ -143,7 +143,7 @@ when nimCoroutines:
proc find(first: var GcStack, bottom: pointer): ptr GcStack =
## Find stack struct based on bottom pointer. If `bottom` is nil then main
## thread stack is returned.
## thread stack is is returned.
if bottom == nil:
return addr(gch.stack)

View File

@@ -59,35 +59,16 @@ template `[]=`*(s: string; i: int; val: char) = arrPut(s, i, val)
template `^^`(s, i: untyped): untyped =
(when i is BackwardsIndex: s.len - int(i) else: int(i))
template spliceStringImpl(s, a, L, b: typed): untyped =
template spliceImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len):
s[i] = s[i-shift]
for i in countdown(newLen-1, a+b.len): movingCopy(s[i], s[i-shift])
else:
for i in countup(a+b.len, newLen-1):
s[i] = s[i-shift]
# cut down:
setLen(s, newLen)
# fill the hole:
for i in 0 ..< b.len: s[a+i] = b[i]
template spliceSeqImpl(s, a, L, b: typed): untyped =
# make room for additional elements or cut:
var shift = b.len - max(0,L) # ignore negative slice size
var newLen = s.len + shift
if shift > 0:
# enlarge:
setLen(s, newLen)
for i in countdown(newLen-1, a+b.len):
movingCopy(s[i], s[i-shift])
else:
for i in countup(a+b.len, newLen-1):
movingCopy(s[i], s[i-shift])
for i in countup(a+b.len, newLen-1): movingCopy(s[i], s[i-shift])
# cut down:
setLen(s, newLen)
# fill the hole:
@@ -121,7 +102,7 @@ proc `[]=`*[T, U: Ordinal](s: var string, x: HSlice[T, U], b: string) {.systemRa
if L == b.len:
for i in 0..<L: s[i+a] = b[i]
else:
spliceStringImpl(s, a, L, b)
spliceImpl(s, a, L, b)
proc `[]`*[Idx, T; U, V: Ordinal](a: array[Idx, T], x: HSlice[U, V]): seq[T] {.systemRaisesDefect.} =
## Slice operation for arrays.
@@ -181,4 +162,4 @@ proc `[]=`*[T; U, V: Ordinal](s: var seq[T], x: HSlice[U, V], b: openArray[T]) {
if L == b.len:
for i in 0 ..< L: s[i+a] = b[i]
else:
spliceSeqImpl(s, a, L, b)
spliceImpl(s, a, L, b)

View File

@@ -224,14 +224,13 @@ proc cmpStringPtrs(a, b: ptr SmallString): int {.inline.} =
minLen - AlwaysAvail)
if result == 0: result = aslen - bslen
return
# At least one is long. Hot prefix mirrors heap data, but only up to fullLen:
# shrinking can leave stale bytes in the inline cache past the logical length.
# At least one is long. Hot prefix: inlinePtr[0..AlwaysAvail-1] mirrors heap data.
let pfxLen = min(min(aslen, bslen), AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
let la = if aslen > PayloadSize: a.more.fullLen else: aslen
let lb = if bslen > PayloadSize: b.more.fullLen else: bslen
let minLen = min(la, lb)
let pfxLen = min(minLen, AlwaysAvail)
result = cmpInlineBytes(inlinePtrOf(a), inlinePtrOf(b), pfxLen)
if result != 0: return
if minLen <= AlwaysAvail:
result = la - lb
return

20
tests/arc/t24720.nim Normal file
View File

@@ -0,0 +1,20 @@
discard """
matrix: "--mm:orc"
output: '''
found entry
'''
"""
import std/tables
type NoCopies = object
proc `=copy`(a: var NoCopies, b: NoCopies) {.error.}
# bug #24720
proc foo() =
var t: Table[int, NoCopies]
t[3] = NoCopies() # only moves
for k, v in t.pairs(): # lent values, no need to copy!
echo "found entry"
foo()

View File

@@ -1,36 +0,0 @@
discard """
matrix: "; --panics:on"
"""
# issue #25851: --panics:on must not drop the nimErr_ check after a closure
# call whose result is consumed directly (e.g. `result.add elem(src)`).
# Regression from #25295.
type
Overrun = object of CatchableError
Source = object
data: seq[bool]
cursor: int
ElemFn = proc(src: var Source): bool {.closure.}
proc drawBool(src: var Source): bool =
if src.cursor >= src.data.len: raise newException(Overrun, "exhausted")
result = src.data[src.cursor]; inc src.cursor
proc listRun(elem: ElemFn, src: var Source): seq[bool] =
result = @[]
while true:
if not src.drawBool(): break
result.add elem(src) # closure call the result flows straight
# into `add`, which previously caused the
# compiler to skip the nimErr_ check.
let elem: ElemFn = proc(src: var Source): bool = src.drawBool()
# Both --panics:on and --panics:off must propagate the Overrun.
var caught = false
try:
var src = Source(data: @[true])
discard listRun(elem, src)
except Overrun:
caught = true
doAssert caught, "Overrun exception was swallowed"

View File

@@ -1,13 +0,0 @@
template a(T: type): int =
when T is uint64: 1 else: 2
type
M*[T] = object
data*: seq[T]
b: seq[int]
indices*: array[a(T), int64]
U = distinct uint64
D* = object
c: M[U]
v: array[180000, int64]
g*: M[uint64]

View File

@@ -1,5 +0,0 @@
import ./c
proc p*(): D =
let c = M[uint64](data: @[0], indices: [1])
result = D(g: c)

View File

@@ -1,7 +0,0 @@
/*TYPESECTION*/
struct CppRef {
int* data;
CppRef() : data(new int(42)) {}
~CppRef() { delete data; data = nullptr; }
void reset() { delete data; data = nullptr; }
};

View File

@@ -1,18 +0,0 @@
discard """
matrix: "--mm:refc; --mm:orc"
"""
import ./m25294/[c, t]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]

View File

@@ -1,23 +0,0 @@
discard """
cmd: "nim cpp $file"
action: "compile"
"""
# Bug Report 1: {.importcpp.} on =wasMoved generates invalid preprocessor directive #.
type CppRef* {.importcpp, bycopy, noInit, header: "m25800.h".} = object
proc `=destroy`(x: var CppRef) {.importcpp: "#.~CppRef()".}
proc `=wasMoved`(x: var CppRef) {.importcpp: "#.reset()".}
proc `=copy`(dest: var CppRef; src: CppRef) {.importcpp: "dest = src".}
proc `=sink`(dest: var CppRef; src: CppRef) {.importcpp: "dest = std::move(src)".}
# This triggers =wasMoved when passing to sink parameter
proc consume(x: sink CppRef) = discard
proc test() =
var x: CppRef
consume(move(x)) # =wasMoved MUST be called here after the move
test()

View File

@@ -1,10 +0,0 @@
discard """
matrix: "-d:nimPreviewSlimSystem --warning:StdPrefix:on --warningAsError:StdPrefix:on --import:std/objectdollar"
output: "(a: 23, b: 45)"
"""
type Foo = object
a, b: int
let x = Foo(a: 23, b: 45)
echo x

View File

@@ -176,42 +176,6 @@ block t6462:
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
doAssert s.test() == nil
block concept_with_cint:
# Generic proc matching through concepts with cint should still work
type
FilterMixin[T] = ref object
test: (T) -> bool
trans: (T) -> T
SeqGen[T] = ref object
fil: FilterMixin[T]
WithFilter[T] = concept a
a.fil is FilterMixin[T]
proc test[T](a: WithFilter[T]): (T) -> bool =
a.fil.test
var s = SeqGen[cint](fil: FilterMixin[cint](test: nil, trans: nil))
doAssert s.test() == nil
block concept_with_int:
type
FilterMixin[T] = ref object
test: (T) -> bool
trans: (T) -> T
SeqGen[T] = ref object
fil: FilterMixin[T]
WithFilter[T] = concept a
a.fil is FilterMixin[T]
proc test[T](a: WithFilter[T]): (T) -> bool =
a.fil.test
var s = SeqGen[int](fil: FilterMixin[int](test: nil, trans: nil))
doAssert s.test() == nil
block t6770:

View File

@@ -12,51 +12,3 @@ proc foo =
doAssert m.id == 999
foo()
block:
type Foo = object
a,b,c: int
var dest: Foo
# proc `=wasMoved`(x: var Foo) =
# debugEcho "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
block:
type Foo = object
a,b,c: int
var dest: Foo
proc `=wasMoved`(x: var Foo) =
discard "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
import std/threadpool
block:
type Foo = object
data: string
proc `=wasMoved`(x: var Foo) =
discard
proc work(x: Foo) =
discard
var x = Foo(data: "hello")
spawn work(x)
sync()

View File

@@ -1,8 +0,0 @@
discard """
errormsg: "cast(raises: ValueError) can raise an unlisted exception: ValueError"
line: 7
"""
proc fff() {.raises: [].} =
{.cast(raises: ValueError).}:
discard

View File

@@ -1,13 +0,0 @@
discard """
cmd: "nim check --strings:sso --mm:orc --hints:off $file"
action: "reject"
nimout: '''
tsso_string_index_var.nim(13, 12) Error: expression 's[0]' is immutable, not 'var'
'''
"""
proc passByVar(c: var char) =
c = 'x'
var s = "abc"
passByVar(s[0])

View File

@@ -1,61 +0,0 @@
discard """
targets: "cpp"
matrix: "--mm:arc; --mm:orc; --mm:refc"
output: '''
inner: orig
finally
outer: re:orig
inner-typeless: orig
finally-typeless
outer-typeless: re-tl:orig
no-catch-finally
caught-propagated: prop
'''
"""
# When an `except` handler raises a new exception, the enclosing `finally`
# block must still run before the new exception propagates to the outer
# try.
#
# The C++ backend previously emitted the finally's `catch (...)` as a
# sibling of the user-written catches. C++ does not allow sibling catches
# to catch each other's throws, so a handler-raised exception bypassed the
# finally entirely. The fix wraps the inner try/catch sequence in an
# outer try, so any escaping exception (whether from the body or from a
# handler) is captured before the finally runs.
block typed_except:
try:
try:
raise newException(CatchableError, "orig")
except CatchableError as e:
echo "inner: ", e.msg
raise newException(CatchableError, "re:" & e.msg)
finally:
echo "finally"
except CatchableError as outer:
echo "outer: ", outer.msg
block typeless_except:
try:
try:
raise newException(CatchableError, "orig")
except:
let e = getCurrentException()
echo "inner-typeless: ", e.msg
raise newException(CatchableError, "re-tl:" & e.msg)
finally:
echo "finally-typeless"
except CatchableError as outer:
echo "outer-typeless: ", outer.msg
# try/finally without an except: the body's exception must still propagate
# after the finally runs.
block no_catch_finally:
try:
try:
raise newException(CatchableError, "prop")
finally:
echo "no-catch-finally"
except CatchableError as e:
echo "caught-propagated: ", e.msg

View File

@@ -1,5 +1,5 @@
discard """
matrix: "--mm:refc; --mm:orc"
matrix: "--mm:refc"
targets: "cpp"
output: '''
caught as std::exception

View File

@@ -1,30 +0,0 @@
discard """
targets: "cpp"
matrix: "--mm:arc; --mm:orc; --mm:refc"
output: '''
finally
after
'''
"""
# Regression test: typeless `except:` followed by `finally:` must not
# trigger ReraiseDefect at the end of the proc.
#
# Previously, `genTryCpp` only emitted `T_ = nullptr;` in the *typed*
# except branches, leaving the typeless `except:` path with a still-set
# `T_`. After the handler body and `popCurrentException`, the trailing
# `if (T_) std::rethrow_exception(T_);` in the finally block would still
# fire — but with the Nim exception stack already popped, the rethrow
# bubbled up as a `ReraiseDefect: no exception to reraise`.
proc test() =
try:
raise newException(CatchableError, "x")
except:
let e = getCurrentException()
discard e
finally:
echo "finally"
test()
echo "after"

View File

@@ -1,19 +0,0 @@
discard """
output: "1"
"""
# Regression for #25857: `typeof(result)` inside `result`'s initializer must not be
# treated as a use-before-initialization of `result`. `typeof` is a type query and
# never evaluates its operand, so this compiles and runs.
# (Before the fix this errored: "'result' requires explicit initialization" on
# {.requiresInit.} return types, breaking the `ok(typeof(result), v)` idiom.)
type Box[T] {.requiresInit.} = object
v: T
func make[T](_: typedesc[Box[T]], v: T): Box[T] = Box[T](v: v)
proc f(): Box[int] =
make(typeof(result), 1)
echo f().v

View File

@@ -1,12 +0,0 @@
discard """
errormsg: "expression has no address"
"""
iterator foo(x: int): (lent int, lent int) =
yield (x, x + 1)
var x = 12
for i in foo(x):
echo i[0]
echo i[1]

View File

@@ -434,32 +434,3 @@ block: # bug #24378
type Win222[T] = typeof("foobar")
doAssert not supportsCopyMem((int, Win222[int]))
doAssert not supportsCopyMem(tuple[a: int, b: Win222[int]])
block: # bug #25789
type
L[T; N: static int] = distinct seq[T]
EPF = distinct L[int, 100]
var e: EPF = EPF(L[int, 100](@[1, 2, 3]))
template classifyGeneric[T](x: T): bool =
when typeof(x) is L:
true
else:
false
template classifyConcrete[T](x: T): bool =
when typeof(x) is L[int, 100]:
true
else:
false
let viaConv = L[int, 100](e)
doAssert $type(viaConv) == "L[system.int, 100]"
doAssert classifyGeneric(viaConv)
doAssert classifyConcrete(viaConv)
let viaDB = distinctBase(e, recursive = false)
doAssert $type(viaDB) == "L[system.int, 100]"
doAssert classifyGeneric(viaDB)
doAssert classifyConcrete(viaDB)

View File

@@ -833,37 +833,4 @@ proc overloaded[T: object](x: T) =
var v: typeof(val)
overloaded(v)
overloaded(Thing())
block:
type
Foo = object
x = Bar()
Bar = object
x: int
var f = Foo()
doassert f.x.x == 0
block:
type
Foo = object
x = Bar(x: 55)
Bar = object
x: int
var f = Foo()
doassert f.x.x == 55
block:
type
Bar = object
x: int
Foo = object
x = Bar()
var f = Foo()
doassert f.x.x == 0
overloaded(Thing())

View File

@@ -20,25 +20,3 @@ block: # issue #24021
discard
else:
discard foo.z
# bug #22791
type Foo = object
case a: bool
of false:
discard
of true:
case b: bool
of false:
discard
of true:
c: bool
const f = Foo(a: true, b: true, c: true)
case f.a
of true:
case f.b
of true:
echo f.c
else: discard
else: discard

View File

@@ -1,44 +0,0 @@
# bug #25617
# Ensure that proc types with backend type alias mismatches
# (e.g. uint vs csize_t) are rejected at the Nim level rather
# than producing invalid C code.
discard """
cmd: "nim check --hints:off --warnings:off --errorMax:0 $file"
action: "reject"
nimout: '''
tbackendtypealias.nim(21, 7) Error: type mismatch: got <proc (len: csize_t){.closure.}> but expected 'proc (len: uint){.closure.}'
tbackendtypealias.nim(28, 7) Error: type mismatch: got <proc (len: uint){.closure.}> but expected 'proc (len: csize_t){.closure.}'
'''
"""
block direct_assignment:
# Direct proc variable assignment with backend type alias mismatch
var
a: proc (len: uint)
b: proc (len: csize_t)
c = a
c = b
block direct_assignment_reverse:
var
a: proc (len: csize_t)
b: proc (len: uint)
c = a
c = b
block same_backend_type:
# Same backend type should still work
var
a: proc (len: uint)
b: proc (len: uint)
c = a
c = b
block cint_same_type:
# cint to cint should work
var
a: proc (len: cint)
b: proc (len: cint)
c = a
c = b

View File

@@ -29,30 +29,3 @@ block tnestprc:
result = x + y
result = add(x, 3)
doAssert Add3(7) == 10
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
const u = A(c: 0)
proc e(T: typedesc): lent A = u
proc y(T: typedesc): H =
proc(): lent A {.nimcall.} = T.e
discard y(int)
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
let u = A(c: 0)
proc y(_: int | int): H =
proc(): lent A {.nimcall.} = u
discard y(0)
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
let u = A()
let _: H = proc(): lent A {.nimcall.} = u

View File

@@ -1,30 +0,0 @@
discard """
cmd: "nim check $options --hints:off --warning:ImplicitRangeConversion --warningaserror:ImplicitRangeConversion $file"
action: "compile"
"""
type
E = enum
ea, eb
R = range[eb..eb]
I = range[0..3]
proc accept(r: R) = discard
proc accept(i: I) = discard
var r: R
var i: I
const enumOk = eb
const enumAlias = enumOk
const intOk = 1 + 2
r = eb
r = enumOk
r = enumAlias
accept(eb)
accept(enumOk)
accept(enumAlias)
i = intOk
accept(intOk)

View File

@@ -241,12 +241,3 @@ proc main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
# $ separator must be emitted even when the item's string repr is empty
type EmptyStr18583 = object
proc `$`(x: EmptyStr18583): string = ""
block:
var d = [EmptyStr18583(), EmptyStr18583()].toDeque
doAssert $d == "[, ]", "got: " & $d

View File

@@ -104,15 +104,3 @@ template main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583HeapQ = object
proc `$`(x: EmptyStr18583HeapQ): string = ""
proc `<`(a, b: EmptyStr18583HeapQ): bool = false
block:
var h = initHeapQueue[EmptyStr18583HeapQ]()
push(h, EmptyStr18583HeapQ())
push(h, EmptyStr18583HeapQ())
let s = $h
doAssert s == "[, ]", "got: " & s

View File

@@ -287,14 +287,3 @@ template main =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583List = object
proc `$`(x: EmptyStr18583List): string = ""
block:
var L: SinglyLinkedList[EmptyStr18583List]
L.prepend(EmptyStr18583List())
L.prepend(EmptyStr18583List())
let s = $L
doAssert s == "[, ]", "got: " & s

View File

@@ -259,11 +259,6 @@ block:
doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+")
doAssert(not match("456678", peg"(\letter)+"))
block:
doAssert match("CAFÉ", peg"\i café")
doAssert match("Café", peg"\i café")
doAssert "two cafés: Café and CAFÉ".findAll(peg"\i café").len == 3
doAssert("var1 = key; var2 = key2".replacef(
peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") ==
"var1<-keykey;var2<-key2key2")

View File

@@ -544,12 +544,6 @@ proc main() =
var x = 5
doAssert fmt"{(x=7;123.456)=:13e}" == "(x=7;123.456)= 1.234560e+02"
doAssert x==7
block: # binary operators in interpolated expressions
let n = 1
doAssert &"{n-1}" == "0"
doAssert fmt"{n-1}" == "0"
block: #curly bracket expressions and tuples
proc formatValue(result: var string; value:Table|bool|JsonNode; specifier:string) = result.add $value

View File

@@ -1,32 +0,0 @@
discard """
output: "ok"
"""
# bug #25693
template g(b: untyped) {.dirty.} =
template t: untyped = b
proc d() = discard @[0]
proc g(_: int) = discard
proc f(a: var seq[int], _: string) =
let p = @[0]
d()
a = p
let q = "a"
g:
var a: seq[int]
try:
f(a, q & "1")
except CatchableError:
discard
try:
f(a, q & "1")
except CatchableError:
discard
block: t()
block: t()
echo "ok"

View File

@@ -1,16 +0,0 @@
discard """
targets: "c cpp js"
"""
import std/os
from std/sequtils import toSeq
iterator items(a: array[3, string]): lent string {.inline.} =
for i in 0..2:
yield a[i]
static:
const key = "NIM_TESTS_TOSENV_KEY"
for val in items(["a", "b", "c"]):
putEnv(key, val)
doAssert (key, val) in toSeq(envPairs())