Merge branch 'devel' into araq-ic7

This commit is contained in:
araq
2025-12-28 14:07:23 +01:00
20 changed files with 234 additions and 80 deletions

View File

@@ -103,7 +103,15 @@ errors.
## Compiler changes
- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
`when` clause would error with "'sizeof' requires '.importc' types to be '.completeStruct'".
The issue was that `hasValuelessStatics` in `semtypinst.nim` didn't recognize
`tyTypeDesc(tyGenericParam)` as an unresolved generic parameter.
## Tool changes
- Added `--stdinfile` flag to name of the file used when running program from stdin (defaults to `stdinfile.nim`)
## Documentation changes
- Added documentation for the `completeStruct` pragma in the manual.

View File

@@ -549,18 +549,28 @@ proc addAllowNil*(father, son: PNode) {.inline.} =
father.sons.add(son)
proc add*(father, son: PType) =
assert father.kind != tyProc or father.sonsImpl.len == 0
assert son != nil
father.sonsImpl.add son
proc addAllowNil*(father, son: PType) {.inline.} =
assert father.kind != tyProc or father.sonsImpl.len == 0
father.sonsImpl.add son
template `[]`*(n: PType, i: int): PType =
if n.state == Partial: loadType(n)
n.sonsImpl[i]
if n.kind == tyProc and i > 0:
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
n.nImpl[i].sym.typ
else:
n.sonsImpl[i]
template `[]=`*(n: PType, i: int; x: PType) =
if n.state == Partial: loadType(n)
n.sonsImpl[i] = x
if n.kind == tyProc and i > 0:
assert n.nImpl[i] != nil and n.nImpl[i].sym != nil
n.nImpl[i].sym.typ = x
else:
n.sonsImpl[i] = x
template `[]`*(n: PType, i: BackwardsIndex): PType =
if n.state == Partial: loadType(n)
@@ -806,7 +816,10 @@ proc replaceSon*(n: PNode; i: int; newson: PNode) {.inline.} =
proc last*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
if n.kind == tyProc and n.nImpl.len > 1:
n.nImpl[^1].sym.typ
else:
n.sonsImpl[^1]
proc elementType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
@@ -842,7 +855,10 @@ proc setIndexType*(n, idx: PType) {.inline.} =
proc firstParamType*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[1]
if n.kind == tyProc:
n.nImpl[1].sym.typ
else:
n.sonsImpl[1]
proc firstGenericParam*(n: PType): PType {.inline.} =
if n.state == Partial: loadType(n)
@@ -914,10 +930,13 @@ proc `$`*(s: PSym): string =
result = "<nil>"
proc len*(n: PType): int {.inline.} =
result = n.sonsImpl.len
if n.kind == tyProc:
result = if n.nImpl == nil: 0 else: n.nImpl.len
else:
result = n.sonsImpl.len
proc sameTupleLengths*(a, b: PType): bool {.inline.} =
result = a.sonsImpl.len == b.sonsImpl.len
result = a.len == b.len
iterator tupleTypePairs*(a, b: PType): (int, PType, PType) =
for i in 0 ..< a.len:
@@ -1012,15 +1031,20 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType
alignImpl: defaultAlignment, itemId: id,
uniqueId: id, sonsImpl: @[])
if son != nil:
assert kind != tyProc
result.sonsImpl.add son
when false:
if result.itemId.module == 55 and result.itemId.item == 2:
echo "KNID ", kind
writeStackTrace()
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} = dest.sonsImpl = sons
proc setSon*(dest: PType; son: sink PType) {.inline.} = dest.sonsImpl = @[son]
proc setSons*(dest: PType; sons: sink seq[PType]) {.inline.} =
assert dest.kind != tyProc or sons.len <= 1
dest.sonsImpl = sons
proc setSon*(dest: PType; son: sink PType) {.inline.} =
dest.sonsImpl = @[son]
proc setSonsLen*(dest: PType; len: int) {.inline.} =
assert dest.kind != tyProc or len <= 1
setLen(dest.sonsImpl, len)
proc mergeLoc(a: var TLoc, b: TLoc) =
@@ -1034,6 +1058,7 @@ proc newSons*(father: PNode, length: int) =
setLen(father.sons, length)
proc newSons*(father: PType, length: int) =
assert father.kind != tyProc or length <= 1
setLen(father.sonsImpl, length)
proc truncateInferredTypeCandidates*(t: PType) {.inline.} =
@@ -1058,8 +1083,16 @@ proc assignType*(dest, src: PType) =
mergeLoc(dest.sym.locImpl, src.sym.loc)
else:
dest.symImpl = src.sym
newSons(dest, src.len)
for i in 0..<src.len: dest[i] = src[i]
if src.kind == tyProc:
# `tyProc` uses only `sonsImpl[0]` to store return type.
# parameter symbols and types are stored in `nImpl`.
assert src.sonsImpl.len <= 1
if src.len > 0:
setLen(dest.sonsImpl, 1)
dest.sonsImpl[0] = src.sonsImpl[0]
else:
newSons(dest, src.len)
for i in 0..<src.len: dest[i] = src[i]
proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType =
result = newType(t.kind, idgen, owner)
@@ -1169,7 +1202,8 @@ proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
proc rawAddSon*(father, son: PType; propagateHasAsgn = true) =
ensureMutable father
father.sonsImpl.add(son)
if father.kind != tyProc or father.sonsImpl.len == 0:
father.sonsImpl.add(son)
if not son.isNil: propagateToOwner(father, son, propagateHasAsgn)
proc addSonNilAllowed*(father, son: PNode) =
@@ -1575,7 +1609,7 @@ proc newProcType*(info: TLineInfo; idgen: IdGenerator; owner: PSym): PType =
result.n.add newNodeI(nkEffectList, info)
proc addParam*(procType: PType; param: PSym) =
param.position = procType.sons.len-1
param.position = procType.n.len - 1
procType.n.add newSymNode(param)
rawAddSon(procType, param.typ)

View File

@@ -337,7 +337,10 @@ proc genLineDir(p: BProc, t: PNode) =
let line = t.info.safeLineNm
if optEmbedOrigSrc in p.config.globalOptions:
p.s(cpsStmts).add("//" & sourceLine(p.config, t.info) & "\L")
var code = sourceLine(p.config, t.info)
if code.endsWith('\\'):
code.add "#"
p.s(cpsStmts).add("// " & code & "\L")
let lastFileIndex = p.lastLineInfo.fileIndex
let freshLine = freshLineInfo(p, t.info)
if freshLine:

View File

@@ -370,8 +370,14 @@ proc storeType(t: PType; c: var PackedEncoder; m: var PackedModule): PackedItemI
paddingAtEnd: t.paddingAtEnd)
storeNode(p, t, n)
p.typeInst = t.typeInst.storeType(c, m)
for kid in kids t:
p.types.add kid.storeType(c, m)
if t.kind == tyProc and t.len > 0:
# if kind == tyProc, parameter types are stored in t.n
# and you can access them with `kits` iterator.
# return type is stored in t.sons[0].
p.types.add t[0].storeType(c, m)
else:
for kid in kids t:
p.types.add kid.storeType(c, m)
c.addMissing t.sym
p.sym = t.sym.safeItemId(c, m)
c.addMissing t.owner

View File

@@ -244,7 +244,8 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
var result = instCopyType(cl, prc.typ)
let originalParams = result.n
result.n = originalParams.shallowCopy
for i, resulti in paramTypes(result):
for i in 1 ..< originalParams.len:
let resulti = originalParams[i].sym.typ
# twrong_field_caching requires these 'resetIdTable' calls:
if i > FirstParamAt:
resetIdTable(cl.symMap)
@@ -258,23 +259,23 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
let needsTypeDescSkipping = resulti.kind == tyTypeDesc and tfUnresolved in resulti.flags
if resulti.kind == tyFromExpr:
resulti.incl tfNonConstExpr
result[i] = replaceTypeVarsT(cl, resulti)
var paramType = replaceTypeVarsT(cl, resulti)
if needsStaticSkipping:
result[i] = result[i].skipTypes({tyStatic})
paramType = paramType.skipTypes({tyStatic})
if needsTypeDescSkipping:
result[i] = result[i].skipTypes({tyTypeDesc})
typeToFit = result[i]
paramType = paramType.skipTypes({tyTypeDesc})
typeToFit = paramType
# ...otherwise, we use the instantiated type in `fitNode`
if (typeToFit.kind != tyTypeDesc or typeToFit.base.kind != tyNone) and
(typeToFit.kind != tyStatic):
typeToFit = result[i]
typeToFit = paramType
internalAssert c.config, originalParams[i].kind == nkSym
let oldParam = originalParams[i].sym
let param = copySym(oldParam, c.idgen)
setOwner(param, prc)
param.typ = result[i]
param.typ = paramType
# The default value is instantiated and fitted against the final
# concrete param type. We avoid calling `replaceTypeVarsN` on the
@@ -305,12 +306,12 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable,
param.ast.typ = def.typ
else:
param.ast = fitNodePostMatch(c, typeToFit, converted)
param.typ = result[i]
param.typ = paramType
result.n[i] = newSymNode(param)
if isRecursiveStructuralType(result[i]):
if isRecursiveStructuralType(paramType):
localError(c.config, originalParams[i].sym.info, "illegal recursion in type '" & typeToString(result[i]) & "'")
propagateToOwner(result, result[i])
propagateToOwner(result, paramType)
addDecl(c, param)
resetIdTable(cl.symMap)

View File

@@ -249,13 +249,24 @@ proc hasValuelessStatics(n: PNode): bool =
a
proc doThing(_: MyThing)
]#
result = false
if n.safeLen == 0 and n.kind != nkEmpty: # Some empty nodes can get in here
n.typ == nil or n.typ.kind == tyStatic
if n.typ == nil:
result = true
elif n.typ.kind == tyStatic:
result = true
elif n.typ.kind == tyTypeDesc:
# Check if the base type is an unresolved generic parameter.
# This handles cases where a template containing sizeof(T) is called
# inside a generic object's when clause - the T needs to be resolved
# before we can evaluate the condition.
let base = n.typ.skipTypes({tyTypeDesc})
if base.kind == tyGenericParam:
result = true
else:
for x in n:
if hasValuelessStatics(x):
return true
false
proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PType = nil): PNode =
if n == nil: return
@@ -542,14 +553,12 @@ proc eraseVoidParams*(t: PType) =
for i in FirstParamAt..<t.signatureLen:
# don't touch any memory unless necessary
if t[i].kind == tyVoid:
if t.n[i].kind == nkRecList or t[i].kind == tyVoid:
var pos = i
for j in i+1..<t.signatureLen:
if t[j].kind != tyVoid:
t[pos] = t[j]
t.n[pos] = t.n[j]
inc pos
newSons t, pos
setLen t.n.sons, pos
break
@@ -743,7 +752,8 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
let r2 = r.skipTypes({tyAlias, tySink, tyOwned})
if r2.kind in {tyPtr, tyRef}:
r = skipTypes(r2, {tyPtr, tyRef})
result[i] = r
if result.kind != tyProc or i == 0:
result[i] = r
if result.kind != tyArray or i != 0:
propagateToOwner(result, r)
# bug #4677: Do not instantiate effect lists

View File

@@ -233,13 +233,11 @@ proc copyingEraseVoidParams(m: TCandidate, t: var PType) =
if not copied:
# keep first i children
t = copyType(original, m.c.idgen, t.owner)
t.setSonsLen(i)
t.n = copyNode(original.n)
t.n.sons = original.n.sons
t.n.sons.setLen(i)
copied = true
elif copied:
t.add(f)
t.n.add(original.n[i])
proc initCandidate*(ctx: PContext, callee: PSym,

View File

@@ -38,7 +38,7 @@ proc checkForSink*(config: ConfigRef; idgen: IdGenerator; owner: PSym; arg: PNod
sinkType.add argType
arg.sym.typ = sinkType
owner.typ[arg.sym.position+1] = sinkType
assert owner.typ.n[arg.sym.position+1].sym == arg.sym
#message(config, arg.info, warnUser,
# ("turned '$1' to a sink parameter") % [$arg])

View File

@@ -1723,6 +1723,12 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg =
let max = (1.BiggestInt shl (rb-1))-1
if regs[ra].intVal < min or regs[ra].intVal > max:
stackTrace(c, tos, pc, "unhandled exception: value out of range")
of opcNarrowR:
decodeBC(rkInt)
let min = regs[rb].intVal
let max = regs[rc].intVal
if regs[ra].intVal < min or regs[ra].intVal > max:
stackTrace(c, tos, pc, "unhandled exception: value out of range")
of opcNarrowU:
decodeB(rkInt)
regs[ra].intVal = regs[ra].intVal and ((1'i64 shl rb)-1)

View File

@@ -105,7 +105,7 @@ type
opcIsNil, opcOf, opcIs,
opcParseFloat, opcConv, opcCast,
opcQuit, opcInvalidField,
opcNarrowS, opcNarrowU,
opcNarrowS, opcNarrowU, opcNarrowR
opcSignExtend,
opcAddStrCh,

View File

@@ -798,6 +798,11 @@ proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
c.gABC(n, opcNarrowU, dest, TRegister(size*8))
elif t.kind in {tyInt8..tyInt32} or (t.kind == tyInt and size < 8):
c.gABC(n, opcNarrowS, dest, TRegister(size*8))
elif t.kind in {tyEnum, tyRange}:
let intType = getSysType(c.graph, n.info, tyInt)
let first = c.genx(newIntTypeNode(firstOrd(c.config, t), intType))
let last = c.genx(newIntTypeNode(lastOrd(c.config, t), intType))
c.gABC(n, opcNarrowR, dest, first, last)
proc genNarrowU(c: PCtx; n: PNode; dest: TDest) =
let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})

View File

@@ -7981,6 +7981,35 @@ underlying C `struct`:c: in a `sizeof` expression:
```
CompleteStruct pragma
---------------------
The `completeStruct` pragma is a contract indicating that an `importc` type
declaration contains all fields of the corresponding C type, allowing
`sizeof`, `alignof`, and `offsetof` to be computed at compile-time.
By default, `importc` types are assumed to be incomplete (their size is
unknown at compile-time). Use `completeStruct` when you need compile-time
size information and can guarantee the Nim definition matches the C layout:
```Nim
type
InotifyEvent {.importc: "struct inotify_event", header: "<sys/inotify.h>",
completeStruct.} = object
wd: cint
mask: uint32
cookie: uint32
len: uint32
# All fields must match the C struct exactly
```
If the Nim fields don't match the C struct, a static assertion will fail
during C code generation.
Without `completeStruct`, attempting to use `sizeof` on an `importc` type
at compile-time will error with "'sizeof' requires '.importc' types to be
'.completeStruct'".
Compile pragma
--------------
The `compile` pragma can be used to compile and link a C/C++ source file

View File

@@ -181,6 +181,7 @@ body {
.nine.columns {
width: 75.0%;
margin-left: 0;
padding-left: 1.5em; }
.twelve.columns {
@@ -192,7 +193,9 @@ body {
display: none;
}
.nine.columns {
width: 98.0%;
width: 100%;
margin-left: 0;
padding-left: 0;
}
body {
font-size: 1em;

View File

@@ -304,6 +304,35 @@ else:
proc rotl32(x: uint32, r: int): uint32 {.inline.} =
(x shl r) or (x shr (32 - r))
proc load4e(s: openArray[byte], o=0): uint32 {.inline.} =
uint32(s[o + 3]) shl 24 or uint32(s[o + 2]) shl 16 or
uint32(s[o + 1]) shl 8 or uint32(s[o + 0])
proc load8e(s: openArray[byte], o=0): uint64 {.inline.} =
uint64(s[o + 7]) shl 56 or uint64(s[o + 6]) shl 48 or
uint64(s[o + 5]) shl 40 or uint64(s[o + 4]) shl 32 or
uint64(s[o + 3]) shl 24 or uint64(s[o + 2]) shl 16 or
uint64(s[o + 1]) shl 8 or uint64(s[o + 0])
when declared(copyMem):
from std/endians import littleEndian64, littleEndian32
proc load4(s: openArray[byte], o=0): uint32 {.inline.} =
when nimvm: result = load4e(s, o)
else:
when declared copyMem:
result = uint32(0)
littleEndian32(addr result, addr s[o])
else: result = load4e(s, o)
proc load8(s: openArray[byte], o=0): uint64 {.inline.} =
when nimvm: result = load8e(s, o)
else:
when declared copyMem:
result = uint64(0)
littleEndian64(addr result, addr s[o])
else: result = load8e(s, o)
proc murmurHash(x: openArray[byte]): Hash =
# https://github.com/PeterScott/murmur3/blob/master/murmur3.c
const
@@ -320,24 +349,10 @@ proc murmurHash(x: openArray[byte]): Hash =
h1: uint32 = uint32(0)
i = 0
template impl =
var j = stepSize
while j > 0:
dec j
k1 = (k1 shl 8) or (ord(x[i+j])).uint32
# body
while i < n * stepSize:
var k1: uint32 = uint32(0)
var k1 = load4(x, i)
when nimvm:
impl()
else:
when declared(copyMem):
copyMem(addr k1, addr x[i], 4)
else:
impl()
inc i, stepSize
k1 = imul(k1, c1)
@@ -384,32 +399,6 @@ const k0 = 0xc3a5c85c97cb3127u64 # Primes on (2^63, 2^64) for various uses
const k1 = 0xb492b66fbe98f273u64
const k2 = 0x9ae16a3b2f90404fu64
proc load4e(s: openArray[byte], o=0): uint32 {.inline.} =
uint32(s[o + 3]) shl 24 or uint32(s[o + 2]) shl 16 or
uint32(s[o + 1]) shl 8 or uint32(s[o + 0])
proc load8e(s: openArray[byte], o=0): uint64 {.inline.} =
uint64(s[o + 7]) shl 56 or uint64(s[o + 6]) shl 48 or
uint64(s[o + 5]) shl 40 or uint64(s[o + 4]) shl 32 or
uint64(s[o + 3]) shl 24 or uint64(s[o + 2]) shl 16 or
uint64(s[o + 1]) shl 8 or uint64(s[o + 0])
proc load4(s: openArray[byte], o=0): uint32 {.inline.} =
when nimvm: result = load4e(s, o)
else:
when declared copyMem:
result = uint32(0)
copyMem result.addr, s[o].addr, result.sizeof
else: result = load4e(s, o)
proc load8(s: openArray[byte], o=0): uint64 {.inline.} =
when nimvm: result = load8e(s, o)
else:
when declared copyMem:
result = uint64(0)
copyMem result.addr, s[o].addr, result.sizeof
else: result = load8e(s, o)
proc lenU(s: openArray[byte]): uint64 {.inline.} = s.len.uint64
proc shiftMix(v: uint64): uint64 {.inline.} = v xor (v shr 47)

View File

@@ -573,7 +573,7 @@ proc generateHeaders(requestUrl: Uri, httpMethod: HttpMethod, headers: HttpHeade
result = $httpMethod
result.add ' '
if proxy.isNil or (requestUrl.scheme == "https" and proxy.url.scheme == "socks5h"):
if proxy.isNil or requestUrl.scheme == "https":
# /path?query
if not requestUrl.path.startsWith("/"): result.add '/'
result.add(requestUrl.path)

View File

@@ -181,6 +181,7 @@ body {
.nine.columns {
width: 75.0%;
margin-left: 0;
padding-left: 1.5em; }
.twelve.columns {
@@ -192,7 +193,9 @@ body {
display: none;
}
.nine.columns {
width: 98.0%;
width: 100%;
margin-left: 0;
padding-left: 0;
}
body {
font-size: 1em;

8
tests/ccgbugs/t25387.nim Normal file
View File

@@ -0,0 +1,8 @@
discard """
matrix: "--embedsrc=on"
"""
proc trim() =
let s = 10
let x = s + 5 # user entered literal \
trim()

View File

@@ -0,0 +1,17 @@
discard """
action: reject
nimout: '''
stack trace: (most recent call last)
tvmranges.nim(14, 10)
tvmranges.nim(14, 10) Error: unhandled exception: value out of range
'''
"""
type X = enum
a
b
when pred(a) == b:
echo "a"
else:
echo "b"

View File

@@ -0,0 +1,34 @@
discard """
output: '''
42
'''
"""
# Regression test for semtypinst.nim hasValuelessStatics bug.
#
# Bug: hasValuelessStatics only checked for tyStatic, missing tyTypeDesc(tyGenericParam)
# Fix: Added check for tyTypeDesc wrapping tyGenericParam in compiler/semtypinst.nim
#
# The bug triggers when:
# 1. A generic type has a when clause calling a typedesc template with sizeof(T)
# 2. A generic proc on that type is called, triggering instantiation
# 3. The T in sizeof(T) becomes tyTypeDesc(tyGenericParam), which wasn't recognized as unresolved
#
# Error without fix: 'sizeof' requires '.importc' types to be '.completeStruct'
template isSmall(T: typedesc): bool =
sizeof(T) <= 8
type Foo[T] = object
when isSmall(T):
a: T
else:
b: ptr T
proc bar[T](x: var Foo[T]) =
discard
var x: Foo[int]
x.a = 42
x.bar()
echo x.a

View File

@@ -3,7 +3,7 @@ discard """
matrix: "--hint:processing"
nimout: '''
compile start
...
....
warn_module.nim(6, 6) Hint: 'test' is declared but not used [XDeclaredButNotUsed]
compile end
'''