Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] exportable pragmas #13016

Closed
Closed
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
4 changes: 3 additions & 1 deletion compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ type

TSymKind* = enum # the different symbols (start with the prefix sk);
# order is important for the documentation generator!
# keep in sync with `NimSymKind`
skUnknown, # unknown symbol: used for parsing assembler blocks
# and first phase symbol lookup in generics
skConditional, # symbol for the preprocessor (may become obsolete)
Expand Down Expand Up @@ -575,6 +576,7 @@ type
# mean: never)
skPackage, # symbol is a package (used for canonicalization)
skAlias # an alias (needs to be resolved immediately)
skPragma, # a pragma
TSymKinds* = set[TSymKind]

const
Expand Down Expand Up @@ -985,7 +987,7 @@ const
PtrLikeKinds*: TTypeKinds = {tyPointer, tyPtr} # for VM
ExportableSymKinds* = {skVar, skConst, skProc, skFunc, skMethod, skType,
skIterator,
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias}
skMacro, skTemplate, skConverter, skEnumField, skLet, skStub, skAlias, skPragma}
PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16,
nfDotSetter, nfDotField,
nfIsRef, nfIsPtr, nfPreventCg, nfLL,
Expand Down
2 changes: 1 addition & 1 deletion compiler/docgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ proc genSection(d: PDoc, kind: TSymKind) =
const sectionNames: array[skModule..skField, string] = [
"Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs",
"Methods", "Iterators", "Converters", "Macros", "Templates", "Exports"
]
] # consider adding more, eg for `skPragma`
if d.section[kind] == nil: return
var title = sectionNames[kind].rope
d.section[kind] = ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.section"), [
Expand Down
3 changes: 2 additions & 1 deletion compiler/importer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ proc rawImportSymbol(c: PContext, s, origin: PSym) =
importPureEnumField(c, e)
else:
if s.kind == skConverter: addConverter(c, s)
if hasPattern(s): addPattern(c, s)
if s.kind == skPragma: strTableAdd(c.userPragmas, s)
elif hasPattern(s): addPattern(c, s)
if s.owner != origin:
c.exportIndirections.incl((origin.id, s.id))

Expand Down
29 changes: 24 additions & 5 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -604,9 +604,10 @@ proc processPragma(c: PContext, n: PNode, i: int) =
elif it.safeLen != 2 or it[0].kind != nkIdent or it[1].kind != nkIdent:
invalidPragma(c, n)

var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options)
var userPragma = newSym(skPragma, it[1].ident, nil, it.info, c.config.options)
userPragma.ast = newNode(nkPragma, n.info, n.sons[i+1..^1])
strTableAdd(c.userPragmas, userPragma)
strTableAdd(c.currentScope.symbols, userPragma)

proc pragmaRaisesOrTags(c: PContext, n: PNode) =
proc processExc(c: PContext, x: PNode) =
Expand Down Expand Up @@ -709,7 +710,7 @@ proc pragmaGuard(c: PContext; it: PNode; kind: TSymKind): PSym =
else:
result = qualifiedLookUp(c, n, {checkUndeclared})

proc semCustomPragma(c: PContext, n: PNode): PNode =
proc semCustomPragmaResolve(c: PContext, n: PNode): PNode =
var callNode: PNode

if n.kind in {nkIdent, nkSym}:
Expand All @@ -723,8 +724,9 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
else:
invalidPragma(c, n)
return n
result = c.semOverloadedCall(c, callNode, n, {skTemplate}, {efNoUndeclared})

let r = c.semOverloadedCall(c, callNode, n, {skTemplate}, {efNoUndeclared})
proc semCustomPragma(c: PContext, n: PNode, r: PNode): PNode =
if r.isNil or sfCustomPragma notin r[0].sym.flags:
invalidPragma(c, n)
return n
Expand All @@ -738,6 +740,23 @@ proc semCustomPragma(c: PContext, n: PNode): PNode =
# pragma(arg) -> pragma: arg
result.kind = n.kind

proc semCustomPragmaMulti(c: PContext, n: PNode, i: var int, sym: PSym, validPragmas: TSpecialWords, isStatement: bool) =
let r = semCustomPragmaResolve(c, n[i])
template bail() =
n[i] = semCustomPragma(c, n[i], r)
return
if r.isNil: bail()
let r0 = r[0]
if r0.sym.kind != skTemplate: bail()
let userPragma = r0.sym
let body = userPragma.ast[bodyPos]
if body.len != 1: bail()
let ast2 = body[0]
if ast2.kind != nkPragma: bail()
pragma(c, sym, ast2, validPragmas, isStatement)
n.sons[i..i] = ast2.sons # expand user pragma with its content
i.inc(ast2.len - 1) # inc by -1 is ok, user pragmas was empty

proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
validPragmas: TSpecialWords,
comesFromPush, isStatement: bool): bool =
Expand All @@ -747,7 +766,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
processNote(c, it)
return
elif key.kind notin nkIdentKinds:
n[i] = semCustomPragma(c, it)
n[i] = semCustomPragma(c, it, semCustomPragmaResolve(c, it))
return
let ident = considerQuotedIdent(c, key)
var userPragma = strTableGet(c.userPragmas, ident)
Expand Down Expand Up @@ -1146,7 +1165,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
else:
if sym == nil or (sym.kind in {skVar, skLet, skParam,
skField, skProc, skFunc, skConverter, skMethod, skType}):
n[i] = semCustomPragma(c, it)
semCustomPragmaMulti(c, n, i, sym, validPragmas, isStatement)
elif sym != nil:
illegalCustomPragma(c, it, sym)
else:
Expand Down
24 changes: 22 additions & 2 deletions compiler/semtempl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,29 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
of nkPostfix:
result[1] = semTemplBody(c, n[1])
of nkPragma:
for x in n:
if x.kind == nkExprColonExpr:
var i=0
while i < n.len:
template x: untyped = n[i]
case x.kind
of nkExprColonExpr:
when false:
# this would fail with things like:
# invalid pragma: hint[ConvFromXtoItselfNotNeeded]: off
x[0] = semTemplBody(c, x[0])
x[1] = semTemplBody(c, x[1])
of nkIdent:
x = semTemplBody(c, x)
if x.kind == nkSym:
if x.sym.kind == skPragma:
# similar to semCustomPragmaMulti
n.sons[i..i] = x.sym.ast.sons # expand user pragma with its content
else:
# eg: skProc
discard
else:
doAssert false # CHECKME
i.inc

of nkBracketExpr:
result = newNodeI(nkCall, n.info)
result.add newIdentNode(getIdent(c.c.cache, "[]"), n.info)
Expand Down
3 changes: 2 additions & 1 deletion lib/core/macros.nim
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,14 @@ type

TNimTypeKinds* {.deprecated.} = set[NimTypeKind]
NimSymKind* = enum
# keep in sync with ast.TSymKind
nskUnknown, nskConditional, nskDynLib, nskParam,
nskGenericParam, nskTemp, nskModule, nskType, nskVar, nskLet,
nskConst, nskResult,
nskProc, nskFunc, nskMethod, nskIterator,
nskConverter, nskMacro, nskTemplate, nskField,
nskEnumField, nskForVar, nskLabel,
nskStub
nskStub, nskPackage, nskAlias, nskPragma

TNimSymKinds* {.deprecated.} = set[NimSymKind]

Expand Down
24 changes: 24 additions & 0 deletions tests/pragmas/mpragma_export.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
template getExportcName*(): string =
## returns at runtime the exportc name; there may be a better way (that also works at CT)
# TODO: add to stdlib
block:
var name {.inject.}: cstring
{.emit: "`name` = __func__;".}
$name

template myfoo2* = {.exportc.}
template myfoo3* = {.exportc: "myfoo3_in_c", discardable.}
template myfoo4* = {.discardable, myfoo2.}
template myfoo5 = {. .}

{.pragma: myfoo0a, exportc: "myfoo0_in_c".}
# export myfoo0a # works even if not exported
template myfoo0b* = {.cdecl.}

template myfoo6* = {.myfoo0a, myfoo0b, discardable.}

# this won't be hijacked
template myfooHijacked = {.exportc: "myfooHijacked_orig".}
template myfooHijacked_wrap* = {.myfooHijacked.}

export myfoo5
51 changes: 51 additions & 0 deletions tests/pragmas/tpragma_export.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
discard """
output: '''
ok1
ok2
in fun4
in fun5
in fun6
'''
"""

import ./mpragma_export

{.pragma: myfoo, exportc.}

proc fun1() {.myfoo.} = echo "ok1"
proc fun2() {.myfoo2.} = echo "ok2"
proc fun3(): int {.myfoo3.} = 123
proc fun4(): int {.myfoo4.} =
echo "in fun4"
124
proc fun5(): int {.myfoo5.} =
echo "in fun5"
125
proc fun6(): int {.myfoo6.} =
echo "in fun6"
125

block:
# checks that local redefinition with same name does not hijack definition of
# myfooHijacked_wrap
template myfooHijacked = {.exportc: "myfooHijacked_new".}

proc funHijackExample() {.myfooHijacked_wrap.} =
doAssert getExportcName() == "myfooHijacked_orig"
funHijackExample()

## example showing a template pragma can use a local {.pragma.} pragma.
## Using an imported {.pragma.} pragma would require https://github.com/nim-lang/Nim/pull/13030
{.pragma: myfooLocal, discardable.}
template myfoo8* = {.myfooLocal.}
proc fun8(): int {.myfoo8.} =
126

fun1()
fun2()
fun3()
doAssert fun3() == 123
fun4()
doAssert fun5() == 125
fun6()
fun8()
11 changes: 6 additions & 5 deletions tests/pragmas/tsym_as_pragma.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@

# bug #3171
when false:
# now getting: Error: invalid pragma: closure`gensym272010
template newDataWindow(): untyped =
let eventClosure = proc (closure: pointer): bool {.closure, cdecl.} =
discard

template newDataWindow(): untyped =
let eventClosure = proc (closure: pointer): bool {.closure, cdecl.} =
discard

newDataWindow()
newDataWindow()