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

Force experimental switch to be able to use call/dot operators #16924

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
73 changes: 45 additions & 28 deletions compiler/semcall.nim
Original file line number Diff line number Diff line change
Expand Up @@ -377,33 +377,50 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
excl n.flags, nfExprCall
else: return

if nfDotField in n.flags:
internalAssert c.config, f.kind == nkIdent and n.len >= 2

# leave the op head symbol empty,
# we are going to try multiple variants
n.sons[0..1] = [nil, n[1], f]
orig.sons[0..1] = [nil, orig[1], f]

template tryOp(x) =
let op = newIdentNode(getIdent(c.cache, x), n.info)
n[0] = op
orig[0] = op
pickBest(op)

if nfExplicitCall in n.flags:
tryOp ".()"

if result.state in {csEmpty, csNoMatch}:
tryOp "."

elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3:
# we need to strip away the trailing '=' here:
let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..^2]), n.info)
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
n.sons[0..1] = [callOp, n[1], calleeName]
orig.sons[0..1] = [callOp, orig[1], calleeName]
pickBest(callOp)
var insertedOp = false
if dotOperators in c.features:
if nfDotField in n.flags:
internalAssert c.config, f.kind == nkIdent and n.len >= 2

# leave the op head symbol empty,
# we are going to try multiple variants
n.sons[0..1] = [nil, n[1], f]
orig.sons[0..1] = [nil, orig[1], f]
insertedOp = true

template tryOp(x) =
let op = newIdentNode(getIdent(c.cache, x), n.info)
n[0] = op
orig[0] = op
pickBest(op)

if nfExplicitCall in n.flags:
tryOp ".()"

if result.state in {csEmpty, csNoMatch}:
tryOp "."

elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3:
# we need to strip away the trailing '=' here:
let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..^2]), n.info)
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
n.sons[0..1] = [callOp, n[1], calleeName]
orig.sons[0..1] = [callOp, orig[1], calleeName]
insertedOp = true
pickBest(callOp)
else:
# preserve error messages
if nfDotField in n.flags:
let op = newIdentNode(getIdent(c.cache, "."), n.info)
n.sons[0..1] = [op, n[1], f]
orig.sons[0..1] = [op, orig[1], f]
insertedOp = true
elif nfDotSetter in n.flags and f.kind == nkIdent and n.len == 3:
let calleeName = newIdentNode(getIdent(c.cache, f.ident.s[0..^2]), n.info)
let callOp = newIdentNode(getIdent(c.cache, ".="), n.info)
n.sons[0..1] = [callOp, n[1], calleeName]
orig.sons[0..1] = [callOp, orig[1], calleeName]
insertedOp = true
metagn marked this conversation as resolved.
Show resolved Hide resolved

if overloadsState == csEmpty and result.state == csEmpty:
if efNoUndeclared notin flags: # for tests/pragmas/tcustom_pragma.nim
Expand All @@ -414,7 +431,7 @@ proc resolveOverloads(c: PContext, n, orig: PNode,
localError(c.config, n.info, "expression '$1' cannot be called" %
renderTree(n, {renderNoComments}))
else:
if {nfDotField, nfDotSetter} * n.flags != {}:
if insertedOp:
# clean up the inserted ops
n.sons.delete(2)
n[0] = f
Expand Down
3 changes: 2 additions & 1 deletion compiler/semexprs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,8 @@ proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode =
if n.len == 1: return semObjConstr(c, n, flags)
return semConv(c, n)
else:
result = overloadedCallOpr(c, n)
if callOperator in c.features:
result = overloadedCallOpr(c, n)
# Now that nkSym does not imply an iteration over the proc/iterator space,
# the old ``prc`` (which is likely an nkIdent) has to be restored:
if result == nil:
Expand Down
15 changes: 8 additions & 7 deletions compiler/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1975,13 +1975,14 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
pushOwner(c, s)

if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n)
if s.name.s[0] in {'.', '('}:
if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}:
localError(c.config, n.info, "the overloaded " & s.name.s &
" operator has to be enabled with {.experimental: \"dotOperators\".}")
elif s.name.s == "()" and callOperator notin c.features:
localError(c.config, n.info, "the overloaded " & s.name.s &
" operator has to be enabled with {.experimental: \"callOperator\".}")
when false:
if s.name.s[0] in {'.', '('}:
if s.name.s in [".", ".()", ".="] and {Feature.destructor, dotOperators} * c.features == {}:
localError(c.config, n.info, "the overloaded " & s.name.s &
" operator has to be enabled with {.experimental: \"dotOperators\".}")
elif s.name.s == "()" and callOperator notin c.features:
localError(c.config, n.info, "the overloaded " & s.name.s &
" operator has to be enabled with {.experimental: \"callOperator\".}")

if n[bodyPos].kind != nkEmpty and sfError notin s.flags:
# for DLL generation we allow sfImportc to have a body, for use in VM
Expand Down
28 changes: 27 additions & 1 deletion doc/manual_experimental.rst
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ The matched dot operators can be symbols of any callable kind (procs,
templates and macros), depending on the desired effect:

.. code-block:: nim
template `.` (js: PJsonNode, field: untyped): JSON = js[astToStr(field)]
template `.`(js: PJsonNode, field: untyped): JSON = js[astToStr(field)]

var js = parseJson("{ x: 1, y: 2}")
echo js.x # outputs 1
Expand All @@ -434,6 +434,32 @@ This operator will be matched against assignments to missing fields.
.. code-block:: nim
a.b = c # becomes `.=`(a, b, c)

Call operator
-------------
The call operator, `()`, matches all kinds of unresolved calls, however
it does not match missing proc overloads. It must be enabled with
``{.experimental: "callOperator".}`` to use.

.. code-block:: nim
block:
let a = 1
let b = 2
a.b # becomes `()`(a, b)
metagn marked this conversation as resolved.
Show resolved Hide resolved

block:
let a = 1
proc b(): int = 2
a.b # becomes `.`(a, b)
metagn marked this conversation as resolved.
Show resolved Hide resolved

block:
let a = 1
proc b(x: int): int = x + 1
let c = 3

a.b(c) # becomes `.`(a, b, c)
a(b) # becomes `()`(a, b)
(a.b)(c) # becomes `()`(a.b, c)
a.b # becomes b(a), must discard

Not nil annotation
==================
Expand Down
5 changes: 4 additions & 1 deletion lib/js/jsffi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ runnableExamples:
# Use jQuery to make the following code run, after the document is ready.
# This uses an experimental `.()` operator for `JsObject`, to emit
# JavaScript calls, when no corresponding proc exists for `JsObject`.
{.experimental: "dotOperators".}
proc main =
jq(document).ready(proc() =
console.log("Hello JavaScript!")
Expand Down Expand Up @@ -217,11 +218,11 @@ proc `==`*(x, y: JsRoot): bool {.importcpp: "(# === #)".}
## like in JavaScript, so if your JsObjects are in fact JavaScript Objects,
## and not strings or numbers, this is a *comparison of references*.

{.experimental.}
macro `.`*(obj: JsObject, field: untyped): JsObject =
## Experimental dot accessor (get) for type JsObject.
## Returns the value of a property of name `field` from a JsObject `x`.
runnableExamples:
{.experimental: "dotOperators".}
let obj = newJsObject()
obj.a = 20
assert obj.a.to(int) == 20
Expand Down Expand Up @@ -448,8 +449,10 @@ macro `{}`*(typ: typedesc, xs: varargs[untyped]): auto =
return `a`

result = quote do:
{.push experimental: "dotOperators".}
proc inner(): `typ` {.gensym.} =
`body`
{.pop.}
inner()

# Macro to build a lambda using JavaScript's `this`
Expand Down
6 changes: 2 additions & 4 deletions lib/std/wrapnils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
## Unstable API.

runnableExamples:
{.experimental: "dotOperators".}

type Foo = ref object
x1: string
x2: Foo
Expand Down Expand Up @@ -34,8 +36,6 @@ template unwrap(a: Wrapnil): untyped =
## See top-level example.
a.valueImpl

{.push experimental: "dotOperators".}

template `.`*(a: Wrapnil, b): untyped =
## See top-level example.
let a1 = a # to avoid double evaluations
Expand All @@ -53,8 +53,6 @@ template `.`*(a: Wrapnil, b): untyped =
# nil is "sticky"; this is needed, see tests
default(T)

{.pop.}

proc isValid(a: Wrapnil): bool =
## Returns true if `a` didn't contain intermediate `nil` values (note that
## `a.valueImpl` itself can be nil even in that case)
Expand Down
2 changes: 2 additions & 0 deletions tests/destructor/tatomicptrs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ deallocating
joinable: false
"""

{.experimental: "dotOperators".}

type
SharedPtr*[T] = object
x: ptr T
Expand Down
2 changes: 2 additions & 0 deletions tests/js/tjsffi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Event { name: 'updates: test' }
'''
"""

{.experimental: "dotOperators".}

import jsffi, jsconsole

# Tests for JsObject
Expand Down
2 changes: 2 additions & 0 deletions tests/js/tjsffi_old.nim
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ true
# xxx instead of maintaining this near-duplicate test file, just have tests
# that check that importc, importcpp, importjs work and remove this file.

{.experimental: "dotOperators".}

import jsffi, jsconsole

# Tests for JsObject
Expand Down
49 changes: 49 additions & 0 deletions tests/specialops/tcallops.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
discard """
nimout: '''
()
Sym "b"
()
Sym "a"
()
Sym "b"
()
StmtList
'''
output: '''
hello
hello again
'''
"""

{.experimental: "callOperator".}

import macros

type Foo[T: proc] = object
callback: T

macro `()`(foo: Foo, args: varargs[untyped]): untyped =
result = newCall(newDotExpr(foo, ident"callback"))
for a in args:
result.add(a)

var f = Foo[proc()](callback: proc() = echo "hello")
f()
f.callback = proc() = echo "hello again"
f()

let g = Foo[proc (x: int): int](callback: proc (x: int): int = x * 2 + 1)
doAssert g(15) == 31

macro `()`(foo: untyped, args: varargs[untyped]): untyped =
result = newStmtList()
echo "()"
echo foo.treeRepr

let a = 1
let b = 2
let c = 3

a.b(c)
a(b)
(a.b)(c)
80 changes: 80 additions & 0 deletions tests/specialops/tcallprecedence.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
discard """
nimout: '''
metagn marked this conversation as resolved.
Show resolved Hide resolved
()
()
()
calling ()
Arglist
Sym "b"
Sym "a"
calling .
Arglist
Sym "a"
Ident "b"
calling .
Arglist
Sym "a"
Ident "b"
Ident "c"
calling ()
Arglist
Sym "a"
Sym "b"
calling ()
Arglist
Call
Sym "b"
Sym "a"
Sym "c"
'''
"""

{.experimental: "dotOperators".}
{.experimental: "callOperator".}
metagn marked this conversation as resolved.
Show resolved Hide resolved

import macros

block:
macro `.()`(foo: untyped, args: varargs[untyped]): untyped {.used.} =
result = newEmptyNode()
echo ".()"

macro `()`(foo: untyped, args: varargs[untyped]): untyped =
result = newEmptyNode()
echo "()"

let a = (b: 1)
let c = 3

a.b(c)
a(c)
(a.b)(c)

macro `()`(args: varargs[untyped]): untyped =
result = newEmptyNode()
echo "calling ()"
echo args.treeRepr

macro `.`(args: varargs[untyped]): untyped =
result = newEmptyNode()
echo "calling ."
echo args.treeRepr

block:
let a = 1
let b = 2
a.b # becomes `()`(a, b)

block:
let a = 1
proc b(): int {.used.} = 2
a.b # becomes `.`(a, b)

block:
let a = 1
proc b(x: int): int = x + 1
let c = 3

a.b(c) # becomes `.`(a, b, c)
a(b) # becomes `()`(a, b)
(a.b)(c) # becomes `()`(a.b, c)
2 changes: 2 additions & 0 deletions tests/specialops/tdotops.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ one param call to c with 10
'''
"""

{.experimental: "dotOperators".}
metagn marked this conversation as resolved.
Show resolved Hide resolved

type
T1 = object
x*: int
Expand Down
10 changes: 10 additions & 0 deletions tests/specialops/tusewithoutexperimental.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{.push experimental: "callOperator".}
metagn marked this conversation as resolved.
Show resolved Hide resolved

template `()`(a, b: int): int = a + b
let x = 1
let y = 2
doAssert x.y == 3
metagn marked this conversation as resolved.
Show resolved Hide resolved

{.pop.}

doAssert not compiles(x.y)
metagn marked this conversation as resolved.
Show resolved Hide resolved
Loading