From ddb8d264243f92ba8664f01fdd62a2a2a5813360 Mon Sep 17 00:00:00 2001 From: metagn Date: Fri, 23 Aug 2024 14:46:40 +0300 Subject: [PATCH] test opensym for templates + move behavior of opensymchoice to itself fixes #24002 --- compiler/ast.nim | 3 + compiler/lookups.nim | 2 + compiler/semexprs.nim | 47 +++---- compiler/semgnrc.nim | 8 +- compiler/semtempl.nim | 53 ++++++-- tests/template/topensym.nim | 197 +++++++++++++++++++++++++++++ tests/template/topensymwarning.nim | 60 +++++++++ 7 files changed, 329 insertions(+), 41 deletions(-) create mode 100644 tests/template/topensym.nim create mode 100644 tests/template/topensymwarning.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index d0d4f8db367f..9a05dc720e5b 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 diff --git a/compiler/lookups.nim b/compiler/lookups.nim index c6940a4dcbd5..b63ea9a161bf 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -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}) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f52de67505ce..6c387a7072b2 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -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 @@ -205,7 +186,7 @@ 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 @@ -213,6 +194,29 @@ proc semOpenSym(c: PContext, n: PNode, flags: TExprFlags, expectedType: PType, # 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: @@ -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 diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 8efc8a94ecdd..b4142a4f1bb3 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -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, @@ -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 diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index f7653a890418..7de921562170 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -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) diff --git a/tests/template/topensym.nim b/tests/template/topensym.nim new file mode 100644 index 000000000000..c2adce377f55 --- /dev/null +++ b/tests/template/topensym.nim @@ -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" diff --git a/tests/template/topensymwarning.nim b/tests/template/topensymwarning.nim new file mode 100644 index 000000000000..d9171c3c1e92 --- /dev/null +++ b/tests/template/topensymwarning.nim @@ -0,0 +1,60 @@ +discard """ + matrix: "--skipParentCfg --filenames:legacyRelProj" +""" + +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: + {.push warningAsError[GenericsIgnoredInjection]: on.} + # test spurious error + discard true + let _ = f + {.pop.} + res = $error #[tt.Warning + ^ a new symbol 'error' has been injected during instantiation of topensymwarning, however 'error' [enumField declared in topensymwarning.nim(6, 3)] captured at the proc declaration will be used instead; either enable --experimental:genericsOpenSym to use the injected symbol or `bind` this captured symbol explicitly [GenericsIgnoredInjection]]# + 123 + res + +discard g(int)