Fix sizeof(T) in typedesc templates called from generic type when clauses (#25374)

The `hasValuelessStatics` function in `semtypinst.nim` only checked for
`tyStatic`, missing `tyTypeDesc(tyGenericParam)`. This caused
`sizeof(T)` inside a typedesc template called from a generic type's
`when` clause to error with "'sizeof' requires '.importc' types to be
'.completeStruct'".

The fix adds a check for `tyTypeDesc` wrapping `tyGenericParam`,
recognizing it as an unresolved generic parameter that needs resolution
before evaluation.

Also documents the `completeStruct` pragma in the manual.
This commit is contained in:
elijahr
2025-12-21 00:37:26 -06:00
committed by GitHub
parent 7b12deecf4
commit b819472e74
4 changed files with 84 additions and 2 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

@@ -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

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

@@ -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