diff --git a/changelog.md b/changelog.md index 4b320399c2..217c8c9653 100644 --- a/changelog.md +++ b/changelog.md @@ -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. diff --git a/compiler/ast.nim b/compiler/ast.nim index 556df74080..bc28cff845 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 = "" 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.. 0: + setLen(dest.sonsImpl, 1) + dest.sonsImpl[0] = src.sonsImpl[0] + else: + newSons(dest, src.len) + for i in 0.. 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 diff --git a/compiler/seminst.nim b/compiler/seminst.nim index b34c7ef58e..a34467636a 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -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) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index 031683d04a..ed9200f7f0 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -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.. 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) diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index e8336aaba4..a3ac120f99 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -105,7 +105,7 @@ type opcIsNil, opcOf, opcIs, opcParseFloat, opcConv, opcCast, opcQuit, opcInvalidField, - opcNarrowS, opcNarrowU, + opcNarrowS, opcNarrowU, opcNarrowR opcSignExtend, opcAddStrCh, diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 8c5460b330..11b7b27fe7 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -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}) diff --git a/doc/manual.md b/doc/manual.md index 53d867c1ad..f52e0ba38c 100644 --- a/doc/manual.md +++ b/doc/manual.md @@ -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: "", + 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 diff --git a/doc/nimdoc.css b/doc/nimdoc.css index 3fc453dc0b..1ca55a2bd0 100644 --- a/doc/nimdoc.css +++ b/doc/nimdoc.css @@ -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; diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index c0171237d0..f53a88db8c 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -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) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index ff6fcb3a66..ecd3b3e8e0 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -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) diff --git a/nimdoc/testproject/expected/nimdoc.out.css b/nimdoc/testproject/expected/nimdoc.out.css index 3fc453dc0b..1ca55a2bd0 100644 --- a/nimdoc/testproject/expected/nimdoc.out.css +++ b/nimdoc/testproject/expected/nimdoc.out.css @@ -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; diff --git a/tests/ccgbugs/t25387.nim b/tests/ccgbugs/t25387.nim new file mode 100644 index 0000000000..d694e3351d --- /dev/null +++ b/tests/ccgbugs/t25387.nim @@ -0,0 +1,8 @@ +discard """ + matrix: "--embedsrc=on" +""" + +proc trim() = + let s = 10 + let x = s + 5 # user entered literal \ +trim() \ No newline at end of file diff --git a/tests/errmsgs/tvmranges.nim b/tests/errmsgs/tvmranges.nim new file mode 100644 index 0000000000..236da34148 --- /dev/null +++ b/tests/errmsgs/tvmranges.nim @@ -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" \ No newline at end of file diff --git a/tests/generic/tgeneric_typedesc_sizeof.nim b/tests/generic/tgeneric_typedesc_sizeof.nim new file mode 100644 index 0000000000..b8284495ee --- /dev/null +++ b/tests/generic/tgeneric_typedesc_sizeof.nim @@ -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 diff --git a/tests/pragmas/thintprocessing.nim b/tests/pragmas/thintprocessing.nim index 943d921669..93b8fa4a61 100644 --- a/tests/pragmas/thintprocessing.nim +++ b/tests/pragmas/thintprocessing.nim @@ -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 '''