Skip to content

Commit

Permalink
test opensym for templates + move behavior of opensymchoice to itself
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed Aug 26, 2024
1 parent 69ea133 commit ddb8d26
Show file tree
Hide file tree
Showing 7 changed files with 329 additions and 41 deletions.
3 changes: 3 additions & 0 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,9 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result.typ = sym.typ
result.info = info

proc newOpenSym*(n: PNode): PNode {.inline.} =
result = newTreeI(nkOpenSym, n.info, n)

proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
result = newNode(kind)
result.intVal = intVal
Expand Down
2 changes: 2 additions & 0 deletions compiler/lookups.nim
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,8 @@ proc qualifiedLookUp*(c: PContext, n: PNode, flags: set[TLookupFlag]): PSym =
c.isAmbiguous = amb
of nkSym:
result = n.sym
of nkOpenSym:
result = qualifiedLookUp(c, n[0], flags)
of nkDotExpr:
result = nil
var m = qualifiedLookUp(c, n[0], (flags * {checkUndeclared}) + {checkModule})
Expand Down
47 changes: 24 additions & 23 deletions compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,6 @@ proc resolveSymChoice(c: PContext, n: var PNode, flags: TExprFlags = {}, expecte
# to mirror behavior before overloadable enums
n = n[0]

proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
result = n
resolveSymChoice(c, result, flags, expectedType)
if isSymChoice(result) and result.len == 1:
# resolveSymChoice can leave 1 sym
result = result[0]
if isSymChoice(result) and efAllowSymChoice notin flags:
var err = "ambiguous identifier: '" & result[0].sym.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)

proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
warnDisabled = false): PNode =
## sem the child of an `nkOpenSym` node, that is, captured symbols that can be
Expand Down Expand Up @@ -205,14 +186,37 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType,
break
o = o.owner
# nothing found
if not warnDisabled:
if not warnDisabled and isSym:
result = semExpr(c, n, flags, expectedType)
else:
result = nil
if not isSym:
# set symchoice node type back to None
n.typ = newTypeS(tyNone, c)

proc semSymChoice(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType = nil): PNode =
if n.kind == nkOpenSymChoice:
result = semOpenSym(c, n, flags, expectedType, warnDisabled = nfDisabledOpenSym in n.flags)
if result != nil:
return
result = n
resolveSymChoice(c, result, flags, expectedType)
if isSymChoice(result) and result.len == 1:
# resolveSymChoice can leave 1 sym
result = result[0]
if isSymChoice(result) and efAllowSymChoice notin flags:
var err = "ambiguous identifier: '" & result[0].sym.name.s &
"' -- use one of the following:\n"
for child in n:
let candidate = child.sym
err.add " " & candidate.owner.name.s & "." & candidate.name.s
err.add ": " & typeToString(candidate.typ) & "\n"
localError(c.config, n.info, err)
n.typ = errorType(c)
result = n
if result.kind == nkSym:
result = semSym(c, result, result.sym, flags)

proc inlineConst(c: PContext, n: PNode, s: PSym): PNode {.inline.} =
result = copyTree(s.astdef)
if result.isNil:
Expand Down Expand Up @@ -3230,9 +3234,6 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType
if isSymChoice(result):
result = semSymChoice(c, result, flags, expectedType)
of nkClosedSymChoice, nkOpenSymChoice:
if nfDisabledOpenSym in n.flags:
let res = semOpenSym(c, n, flags, expectedType, warnDisabled = true)
assert res == nil
result = semSymChoice(c, n, flags, expectedType)
of nkSym:
let s = n.sym
Expand Down
8 changes: 4 additions & 4 deletions compiler/semgnrc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ template isMixedIn(sym): bool =
template canOpenSym(s): bool =
{withinMixin, withinConcept} * flags == {withinMixin} and s.id notin ctx.toBind

proc newOpenSym*(n: PNode): PNode {.inline.} =
result = newTreeI(nkOpenSym, n.info, n)

proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
ctx: var GenericCtx; flags: TSemGenericFlags,
isAmbiguous: bool,
Expand All @@ -78,7 +75,10 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
result = symChoice(c, n, s, scOpen)
if canOpenSym(s):
if genericsOpenSym in c.features:
result = newOpenSym(result)
if result.kind == nkSym:
result = newOpenSym(result)
else:
result.typ = nil
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
Expand Down
53 changes: 39 additions & 14 deletions compiler/semtempl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -230,29 +230,54 @@ proc semTemplSymbol(c: PContext, n: PNode, s: PSym; isField, isAmbiguous: bool):
of skUnknown:
# Introduced in this pass! Leave it as an identifier.
result = n
of OverloadableSyms-{skTemplate,skMacro}:
of OverloadableSyms:
result = symChoice(c, n, s, scOpen, isField)
of skTemplate, skMacro:
result = symChoice(c, n, s, scOpen, isField)
if result.kind == nkSym:
# template/macro symbols might need to be semchecked again
# prepareOperand etc don't do this without setting the type to nil
result.typ = nil
if result.kind in {nkSym, nkOpenSymChoice}:
if genericsOpenSym in c.features:
if result.kind == nkSym:
result = newOpenSym(result)
else:
result.typ = nil
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
of skGenericParam:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
else:
result = newSymNodeTypeDesc(s, c.idgen, n.info)
if genericsOpenSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
of skParam:
result = n
of skType:
if isField and sfGenSym in s.flags: result = n
elif isAmbiguous:
# ambiguous types should be symchoices since lookup behaves
# differently for them in regular expressions
result = symChoice(c, n, s, scOpen, isField)
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
else:
if isAmbiguous:
# ambiguous types should be symchoices since lookup behaves
# differently for them in regular expressions
result = symChoice(c, n, s, scOpen, isField)
else: result = newSymNodeTypeDesc(s, c.idgen, n.info)
if result.kind in {nkSym, nkOpenSymChoice}:
if genericsOpenSym in c.features:
if result.kind == nkSym:
result = newOpenSym(result)
else:
result.typ = nil
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
else:
if isField and sfGenSym in s.flags: result = n
else: result = newSymNode(s, n.info)
else:
result = newSymNode(s, n.info)
if genericsOpenSym in c.features:
result = newOpenSym(result)
else:
result.flags.incl nfDisabledOpenSym
result.typ = nil
# Issue #12832
when defined(nimsuggest):
suggestSym(c.graph, n.info, s, c.graph.usageSym, false)
Expand Down
197 changes: 197 additions & 0 deletions tests/template/topensym.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
{.experimental: "genericsOpenSym".}

block: # issue #24002
type Result[T, E] = object
func value[T, E](self: Result[T, E]): T {.inline.} =
discard
func value[T: not void, E](self: var Result[T, E]): var T {.inline.} =
discard
template unrecognizedFieldWarning =
doAssert value == 123
let x = value
doAssert value == x
proc readValue(value: var int) =
unrecognizedFieldWarning()
var foo: int = 123
readValue(foo)

block: # issue #22605 for templates, normal call syntax
const error = "bad"

template valueOr(self: int, def: untyped): untyped =
case false
of true: ""
of false:
template error: untyped {.used, inject.} = "good"
def

template g(T: type): string =
var res = "ok"
let x = valueOr 123:
res = $error
"dummy"
res

doAssert g(int) == "good"

template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = valueOr 123:
res = $error
"dummy"
res

doAssert g2(int) == "bad"

block: # issue #22605 for templates, method call syntax
const error = "bad"

template valueOr(self: int, def: untyped): untyped =
case false
of true: ""
of false:
template error: untyped {.used, inject.} = "good"
def

template g(T: type): string =
var res = "ok"
let x = 123.valueOr:
res = $error
"dummy"
res

doAssert g(int) == "good"

template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = 123.valueOr:
res = $error
"dummy"
res

doAssert g2(int) == "bad"

block: # issue #22605 for templates, original complex example
type Xxx = enum
error
value

type
Result[T, E] = object
when T is void:
when E is void:
oResultPrivate*: bool
else:
case oResultPrivate*: bool
of false:
eResultPrivate*: E
of true:
discard
else:
when E is void:
case oResultPrivate*: bool
of false:
discard
of true:
vResultPrivate*: T
else:
case oResultPrivate*: bool
of false:
eResultPrivate*: E
of true:
vResultPrivate*: T

template valueOr[T: not void, E](self: Result[T, E], def: untyped): untyped =
let s = (self) # TODO avoid copy
case s.oResultPrivate
of true:
s.vResultPrivate
of false:
when E isnot void:
template error: untyped {.used, inject.} = s.eResultPrivate
def

proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")

template g(T: type): string =
var res = "ok"
let x = f().valueOr:
res = $error
123
res

doAssert g(int) == "f"

template g2(T: type): string =
bind error # use the bad version on purpose
var res = "ok"
let x = f().valueOr:
res = $error
123
res

doAssert g2(int) == "error"

block: # issue #23865 for templates
type Xxx = enum
error
value

type
Result[T, E] = object
when T is void:
when E is void:
oResultPrivate: bool
else:
case oResultPrivate: bool
of false:
eResultPrivate: E
of true:
discard
else:
when E is void:
case oResultPrivate: bool
of false:
discard
of true:
vResultPrivate: T
else:
case oResultPrivate: bool
of false:
eResultPrivate: E
of true:
vResultPrivate: T

func error[T, E](self: Result[T, E]): E =
## Fetch error of result if set, or raise Defect
case self.oResultPrivate
of true:
when T isnot void:
raiseResultDefect("Trying to access error when value is set", self.vResultPrivate)
else:
raiseResultDefect("Trying to access error when value is set")
of false:
when E isnot void:
self.eResultPrivate

template valueOr[T: not void, E](self: Result[T, E], def: untyped): untyped =
let s = (self) # TODO avoid copy
case s.oResultPrivate
of true:
s.vResultPrivate
of false:
when E isnot void:
template error: untyped {.used, inject.} = s.eResultPrivate
def
proc f(): Result[int, cstring] =
Result[int, cstring](oResultPrivate: false, eResultPrivate: "f")
template g(T: type): string =
var res = "ok"
let x = f().valueOr:
res = $error
123
res
doAssert g(int) == "f"
Loading

0 comments on commit ddb8d26

Please sign in to comment.