From f4fd26d4031e8ea0814cd4d4b1a53308192714c1 Mon Sep 17 00:00:00 2001 From: elijahr Date: Sun, 21 Dec 2025 00:37:26 -0600 Subject: [PATCH] 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. (cherry picked from commit b819472e744463aa8fa4959d6610f371240699d7) --- changelog.md | 8 +++++ compiler/semtypinst.nim | 15 ++++++++-- doc/manual.md | 29 ++++++++++++++++++ tests/generic/tgeneric_typedesc_sizeof.nim | 34 ++++++++++++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 tests/generic/tgeneric_typedesc_sizeof.nim diff --git a/changelog.md b/changelog.md index b2d5840dfb..38f87d1328 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/semtypinst.nim b/compiler/semtypinst.nim index f923b736ed..303d801c2b 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 diff --git a/doc/manual.md b/doc/manual.md index aeec51e005..85ce2dbe26 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/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