Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,29 @@ errors.
See the [experimental manual](https://nim-lang.github.io/Nim/manual_experimental.html#typeminusbound-overloads)
for more information.

- The `size` pragma now supports expressions involving type parameters for generic
imported types. The expression is evaluated when the generic type is instantiated.

```nim
type
CppAtomic[T] {.importcpp: "std::atomic", header: "<atomic>",
size: sizeof(T), completeStruct.} = object

static:
doAssert sizeof(CppAtomic[int32]) == 4
```

- The `align` pragma is now supported on types.
- The `align` pragma now supports expressions involving type parameters for generic
types and fields. The expression is evaluated when the generic type is instantiated.

```nim
type
Container[T] = object
header: int32
data {.align: alignof(T).}: T
```

## Compiler changes

- Fixed a bug where `sizeof(T)` inside a `typedesc` template called from a generic type's
Expand Down
58 changes: 58 additions & 0 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,36 @@ proc `alignment=`*(s: PSym, val: int) {.inline.} =
if s.state == Partial: loadSym(s)
s.alignmentImpl = val

proc setDeferredExpr*(s: PSym, word: TSpecialWord, expr: PNode) {.inline.} =
## Sets a deferred expression for a pragma word on a field symbol.
assert s.state != Sealed, "cannot modify sealed symbol"
assert s.kind in {skLet, skVar, skField, skForVar},
"setDeferredExpr only valid for field-like symbols"
if s.state == Partial: loadSym(s)
for i in 0..<s.deferredExprsImpl.len:
if s.deferredExprsImpl[i].word == word:
s.deferredExprsImpl[i].expr = expr
if expr != nil:
s.flagsImpl.incl sfHasDeferredPragmas
return
s.deferredExprsImpl.add DeferredPragmaExpr(word: word, expr: expr)
if expr != nil:
s.flagsImpl.incl sfHasDeferredPragmas

proc clearDeferredExpr*(s: PSym, word: TSpecialWord) {.inline.} =
## Clears a deferred expression for a pragma word.
for i in 0..<s.deferredExprsImpl.len:
if s.deferredExprsImpl[i].word == word:
s.deferredExprsImpl.delete(i)
break
if s.deferredExprsImpl.len == 0:
s.flagsImpl.excl sfHasDeferredPragmas

iterator deferredPragmas*(s: PSym): DeferredPragmaExpr {.inline.} =
## Iterates over all deferred pragma expressions on a symbol
for dp in s.deferredExprsImpl:
yield dp

proc magic*(s: PSym): TMagic {.inline.} =
if s.state == Partial: loadSym(s)
result = s.magicImpl
Expand Down Expand Up @@ -378,6 +408,34 @@ proc `align=`*(t: PType, val: int16) {.inline.} =
backendEnsureMutable t
t.alignImpl = val

proc setDeferredExpr*(t: PType, word: TSpecialWord, expr: PNode) {.inline.} =
## Sets a deferred expression for a pragma word on a type.
assert t.state != Sealed
if t.state == Partial: loadType(t)
for i in 0..<t.deferredExprsImpl.len:
if t.deferredExprsImpl[i].word == word:
t.deferredExprsImpl[i].expr = expr
if expr != nil:
t.flagsImpl.incl tfHasDeferredPragmas
return
t.deferredExprsImpl.add DeferredPragmaExpr(word: word, expr: expr)
if expr != nil:
t.flagsImpl.incl tfHasDeferredPragmas

proc clearDeferredExpr*(t: PType, word: TSpecialWord) {.inline.} =
## Clears a deferred expression for a pragma word.
for i in 0..<t.deferredExprsImpl.len:
if t.deferredExprsImpl[i].word == word:
t.deferredExprsImpl.delete(i)
break
if t.deferredExprsImpl.len == 0:
t.flagsImpl.excl tfHasDeferredPragmas

iterator deferredPragmas*(t: PType): DeferredPragmaExpr {.inline.} =
## Iterates over all deferred pragma expressions on a type
for dp in t.deferredExprsImpl:
yield dp

proc paddingAtEnd*(t: PType): int16 {.inline.} =
if t.state == Partial: loadType(t)
result = t.paddingAtEndImpl
Expand Down
14 changes: 13 additions & 1 deletion compiler/astdef.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#

import
lineinfos, options, ropes, idents, int128
lineinfos, options, ropes, idents, int128, wordrecg

import std/[tables, hashes]

Expand Down Expand Up @@ -126,6 +126,8 @@ type
sfWasGenSym # symbol was 'gensym'ed
sfForceLift # variable has to be lifted into closure environment

sfHasDeferredPragmas # field has one or more deferred pragma expressions

sfDirty # template is not hygienic (old styled template) module,
# compiled from a dirty-buffer
sfCustomPragma # symbol is custom pragma template
Expand Down Expand Up @@ -397,6 +399,7 @@ type
tfIsOutParam
tfSendable
tfImplicitStatic
tfHasDeferredPragmas # type has one or more deferred pragma expressions

TTypeFlags* = set[TTypeFlag]

Expand Down Expand Up @@ -589,6 +592,13 @@ type
TNodeSeq* = seq[PNode]
PType* = ref TType
PSym* = ref TSym

DeferredPragmaExpr* = object
## A pragma expression that needs evaluation during generic instantiation.
## Used for pragmas like size and align that can reference generic params.
word*: TSpecialWord ## which pragma (wSize, wAlign, etc.)
expr*: PNode ## the deferred expression containing generic params

TNode*{.final, acyclic.} = object # on a 32bit machine, this takes 32 bytes
when defined(useNodeIds):
id*: int
Expand Down Expand Up @@ -709,6 +719,7 @@ type
guardImpl*: PSym
bitsizeImpl*: int
alignmentImpl*: int # for alignment
deferredExprsImpl*: seq[DeferredPragmaExpr] # deferred pragma expressions (nil = none)
else: nil
magicImpl*: TMagic
typImpl*: PType
Expand Down Expand Up @@ -796,6 +807,7 @@ type
# -1 means that the size is unknown
alignImpl*: int16 # the type's alignment requirements
paddingAtEndImpl*: int16 #
deferredExprsImpl*: seq[DeferredPragmaExpr] # deferred pragma expressions (nil = none)
locImpl*: TLoc
typeInstImpl*: PType # for generic instantiations the tyGenericInst that led to this
# type.
Expand Down
1 change: 1 addition & 0 deletions compiler/condsyms.nim
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,5 @@ proc initDefines*(symbols: StringTableRef) =

defineSymbol("nimHasSetLengthSeqUninitMagic")
defineSymbol("nimHasPreviewDuplicateModuleError")
defineSymbol("nimHasDeferredPragmas")

6 changes: 6 additions & 0 deletions compiler/ic/enum2nif.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,7 @@ proc genFlags*(s: set[TSymFlag]; dest: var string) =
of sfAnon: dest.add "a0"
of sfAllUntyped: dest.add "a1"
of sfTemplateRedefinition: dest.add "t1"
of sfHasDeferredPragmas: dest.add "h"


proc parse*(t: typedesc[TSymFlag]; s: string): set[TSymFlag] =
Expand Down Expand Up @@ -1362,6 +1363,7 @@ proc parse*(t: typedesc[TSymFlag]; s: string): set[TSymFlag] =
result.incl sfGoto
inc i
else: result.incl sfGlobal
of 'h': result.incl sfHasDeferredPragmas
of 'i':
if i+1 < s.len and s[i+1] == '0':
result.incl sfInfixCall
Expand Down Expand Up @@ -1588,6 +1590,7 @@ proc genFlags*(s: set[TTypeFlag]; dest: var string) =
of tfIsOutParam: dest.add "i5"
of tfSendable: dest.add "s0"
of tfImplicitStatic: dest.add "i6"
of tfHasDeferredPragmas: dest.add "h2"


proc parse*(t: typedesc[TTypeFlag]; s: string): set[TTypeFlag] =
Expand Down Expand Up @@ -1647,6 +1650,9 @@ proc parse*(t: typedesc[TTypeFlag]; s: string): set[TTypeFlag] =
elif i+1 < s.len and s[i+1] == '1':
result.incl tfHasStatic
inc i
elif i+1 < s.len and s[i+1] == '2':
result.incl tfHasDeferredPragmas
inc i
else: result.incl tfHasOwned
of 'i':
if i+1 < s.len and s[i+1] == '0':
Expand Down
146 changes: 128 additions & 18 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const
wRaises, wLocks, wTags, wForbids, wRequires, wEnsures, wEffectsOf,
wGcSafe, wCodegenDecl, wNoInit, wCompileTime}
typePragmas* = declPragmas + {wMagic, wAcyclic,
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wAlign, wShallow,
wIncompleteStruct, wCompleteStruct, wByCopy, wByRef,
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
wCppNonPod, wBorrow, wGcSafe, wPartial, wExplain, wPackage, wCodegenDecl,
Expand All @@ -97,6 +97,32 @@ const
allRoutinePragmas* = methodPragmas + iteratorPragmas + lambdaPragmas
enumFieldPragmas* = {wDeprecated}

proc isGenericParamSym(sym: PSym): bool {.inline.} =
## Returns true if sym represents a generic parameter.
if sym == nil: return false
if sym.kind == skGenericParam: return true
if sym.kind == skType and sym.typ != nil and sym.typ.kind == tyGenericParam:
return true
return false

proc containsUnresolvedIdent(c: PContext, n: PNode): bool =
## Returns true if n contains any identifiers that cannot be resolved in scope.
## Unresolved identifiers indicate potential generic params that haven't been
## added to scope yet (pragmas are processed before generic params in typeDefLeftSidePass).
if n == nil: return false
case n.kind
of nkIdent:
var ambiguous = false
let sym = searchInScopes(c, n.ident, ambiguous)
return sym == nil or isGenericParamSym(sym)
of nkSym:
return isGenericParamSym(n.sym)
else:
for child in n:
if containsUnresolvedIdent(c, child):
return true
return false

proc getPragmaVal*(procAst: PNode; name: TSpecialWord): PNode =
result = nil
let p = procAst[pragmasPos]
Expand All @@ -118,6 +144,48 @@ proc recordPragma(c: PContext; n: PNode; args: varargs[string]) =
const
errStringLiteralExpected = "string literal expected"
errIntLiteralExpected = "integer literal expected"
errPragmaRequiresArgument = "$1 pragma requires an argument"
errDeferredOnlyForImported = "deferred $1 expressions only supported for imported types"
errMustBeConstantInteger = "$1 must be a compile-time constant integer"

proc deferOrEvaluate(c: PContext, sym: PSym, it: PNode,
word: TSpecialWord, requireImportc: bool): bool =
## Generic helper: decides whether to defer or evaluate a pragma expression.
## Returns true if expression was deferred, false if it should be evaluated immediately.
## Handles nil checks, validation, and error reporting.
##
## Args:
## c: Semantic context
## sym: Symbol being processed (must have .typ for types, or be a field symbol)
## it: The pragma node
## word: Pragma word (wSize, wAlign, etc.)
## requireImportc: If true, deferred expressions require sfImportc flag
##
## Returns:
## true = expression was deferred (caller should return)
## false = expression is ready for immediate evaluation (caller continues)
let expr = if it.kind in nkPragmaCallKinds and it.len == 2: it[1] else: nil
if expr == nil:
localError(c.config, it.info, errPragmaRequiresArgument % $word)
return true

if containsUnresolvedIdent(c, expr):
if requireImportc:
let hasImportc = if sym.kind in {skLet, skVar, skField, skForVar}:
false
else:
sfImportc in sym.flags
if not hasImportc:
localError(c.config, it.info, errDeferredOnlyForImported % $word)
return true

if sym.kind in {skLet, skVar, skField, skForVar}:
sym.setDeferredExpr(word, expr)
else:
sym.typ.setDeferredExpr(word, expr)
return true

return false

proc invalidPragma*(c: PContext; n: PNode) =
localError(c.config, n.info, "invalid pragma: " & renderTree(n, {renderNoComments}))
Expand Down Expand Up @@ -232,10 +300,36 @@ proc expectIntLit(c: PContext, n: PNode): int =
of nkIntLit..nkInt64Lit: result = int(n[1].intVal)
else: localError(c.config, n.info, errIntLiteralExpected)

template expectInt(c: PContext, n: PNode, info: TLineInfo,
pragmaName: string): int =
## Validates that n is an integer constant. Returns value or 0 on error.
var resultVal = 0
if n.kind in nkIntLit..nkInt64Lit:
resultVal = int(n.intVal)
else:
localError(c.config, info, errMustBeConstantInteger % pragmaName)
resultVal

proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n)
else: result = defaultStr

proc applySize(c: PContext, typ: PType, size: int, isImported: bool, info: TLineInfo) =
## Applies size pragma to a type. Validates size for non-imported types.
if size <= 0: return
if isImported:
setImportedTypeSize(c.config, typ, size)
else:
case size
of 1, 2, 4:
typ.size = size
typ.align = int16 size
of 8:
typ.size = 8
typ.align = floatInt64Align(c.config)
else:
localError(c.config, info, "size may only be 1, 2, 4 or 8")

proc processVirtual(c: PContext, n: PNode, s: PSym, flag: TSymFlag) =
s.constraint = newEmptyStrNode(c, n, getOptionalStr(c, n, "$1"))
s.constraint.strVal = s.constraint.strVal % s.name.s
Expand Down Expand Up @@ -946,26 +1040,42 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
of wSize:
if sym.typ == nil: invalidPragma(c, it)
var size = expectIntLit(c, it)
if sfImportc in sym.flags:
# no restrictions on size for imported types
setImportedTypeSize(c.config, sym.typ, size)
if sym.kind == skType:
if not deferOrEvaluate(c, sym, it, wSize, requireImportc = true):
let expr = it[1]
let evaluated = c.semConstExpr(c, expr.copyTree)
let size = expectInt(c, evaluated, it.info, "size")
applySize(c, sym.typ, size, sfImportc in sym.flags, it.info)
else:
case size
of 1, 2, 4:
sym.typ.size = size
sym.typ.align = int16 size
of 8:
sym.typ.size = 8
sym.typ.align = floatInt64Align(c.config)
else:
localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
let size = expectIntLit(c, it)
applySize(c, sym.typ, size, sfImportc in sym.flags, it.info)
of wAlign:
let alignment = expectIntLit(c, it)
if isPowerOfTwo(alignment) and alignment > 0:
sym.alignment = max(sym.alignment, alignment)
if sym.kind == skType:
if deferOrEvaluate(c, sym, it, wAlign, requireImportc = false):
discard
else:
let expr = it[1]
let evaluated = c.semConstExpr(c, expr.copyTree)
let alignment = expectInt(c, evaluated, it.info, "align")
if isPowerOfTwo(alignment) and alignment > 0:
sym.typ.align = int16(alignment)
else:
localError(c.config, it.info, "align must be a power of two")
elif sym.kind == skField:
if deferOrEvaluate(c, sym, it, wAlign, requireImportc = false):
discard
else:
let alignment = expectIntLit(c, it)
if isPowerOfTwo(alignment) and alignment > 0:
sym.alignment = max(sym.alignment, alignment)
else:
localError(c.config, it.info, "align must be a power of two")
else:
localError(c.config, it.info, "power of two expected")
let alignment = expectIntLit(c, it)
if isPowerOfTwo(alignment) and alignment > 0:
sym.alignment = max(sym.alignment, alignment)
else:
localError(c.config, it.info, "power of two expected")
of wNodecl:
noVal(c, it)
sym.incl(lfNoDecl)
Expand Down
Loading