diff --git a/compiler/ast.nim b/compiler/ast.nim index cd002eef1f..fe724f4ddb 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -409,7 +409,9 @@ type # efficiency nfTransf, # node has been transformed nfSem # node has been checked for semantics - nfDelegate # the call can use a delegator + nfDotField # the call can use a dot operator + nfDotSetter # the call can use a setter dot operarator + nfExplicitCall # x.y() was used instead of x.y nfExprCall # this is an attempt to call a regular expression nfIsRef # this node is a 'ref' node; used for the VM @@ -843,7 +845,8 @@ const ExportableSymKinds* = {skVar, skConst, skProc, skMethod, skType, skIterator, skMacro, skTemplate, skConverter, skEnumField, skLet, skStub} PersistentNodeFlags*: TNodeFlags = {nfBase2, nfBase8, nfBase16, - nfAllConst, nfDelegate, nfIsRef} + nfDotSetter, nfDotField, + nfAllConst,nfIsRef} namePos* = 0 patternPos* = 1 # empty except for term rewriting macros genericParamsPos* = 2 @@ -1044,6 +1047,10 @@ proc newStrNode(kind: TNodeKind, strVal: string): PNode = result = newNode(kind) result.strVal = strVal +proc withInfo*(n: PNode, info: TLineInfo): PNode = + n.info = info + return n + proc newIdentNode(ident: PIdent, info: TLineInfo): PNode = result = newNode(nkIdent) result.ident = ident diff --git a/compiler/parser.nim b/compiler/parser.nim index ff3324b471..5a5bfb5743 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -281,7 +281,7 @@ proc parseSymbol(p: var TParser): PNode = add(result, newIdentNodeP(getIdent"{}", p)) getTok(p) eat(p, tkCurlyRi) - of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDotDot: + of tokKeywordLow..tokKeywordHigh, tkSymbol, tkOpr, tkDot, tkDotDot: add(result, newIdentNodeP(p.tok.ident, p)) getTok(p) of tkIntLit..tkCharLit: diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 6b19dc359e..0cd27a4434 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -82,7 +82,7 @@ proc notFoundError*(c: PContext, n: PNode, errors: seq[string]) = # fail fast: globalError(n.info, errTypeMismatch, "") var result = msgKindToString(errTypeMismatch) - add(result, describeArgs(c, n, 1 + ord(nfDelegate in n.flags))) + add(result, describeArgs(c, n, 1 + ord(nfDotField in n.flags))) add(result, ')') var candidates = "" @@ -138,17 +138,35 @@ proc resolveOverloads(c: PContext, n, orig: PNode, let overloadsState = result.state if overloadsState != csMatch: - if nfDelegate in n.flags: - internalAssert f.kind == nkIdent - let calleeName = newStrNode(nkStrLit, f.ident.s) - calleeName.info = n.info + if nfDotField in n.flags: + internalAssert f.kind == nkIdent and n.sonsLen >= 2 + let calleeName = newStrNode(nkStrLit, f.ident.s).withInfo(n.info) - let callOp = newIdentNode(idDelegator, n.info) - n.sons[0..0] = [callOp, calleeName] - orig.sons[0..0] = [callOp, calleeName] - + # leave the op head symbol empty, + # we are going to try multiple variants + n.sons[0..1] = [nil, n[1], calleeName] + orig.sons[0..1] = [nil, orig[1], calleeName] + + template tryOp(x) = + let op = newIdentNode(getIdent(x), n.info) + n.sons[0] = op + orig.sons[0] = op + pickBest(op) + + if nfExplicitCall in n.flags: + tryOp ".()" + + if result.state in {csEmpty, csNoMatch}: + tryOp "." + + elif nfDotSetter in n.flags: + internalAssert f.kind == nkIdent and n.sonsLen == 3 + let calleeName = newStrNode(nkStrLit, f.ident.s[0.. -2]).withInfo(n.info) + let callOp = newIdentNode(getIdent".=", n.info) + n.sons[0..1] = [callOp, n[1], calleeName] + orig.sons[0..1] = [callOp, orig[1], calleeName] pickBest(callOp) - + if overloadsState == csEmpty and result.state == csEmpty: localError(n.info, errUndeclaredIdentifier, considerAcc(f).s) return diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 3fe1367ec8..30ab344c26 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -683,20 +683,21 @@ proc semOverloadedCallAnalyseEffects(c: PContext, n: PNode, nOrig: PNode, incl(c.p.owner.flags, sfSideEffect) proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode -proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = +proc semIndirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode = result = nil checkMinSonsLen(n, 1) var prc = n.sons[0] - if n.sons[0].kind == nkDotExpr: + if n.sons[0].kind == nkDotExpr: checkSonsLen(n.sons[0], 2) n.sons[0] = semFieldAccess(c, n.sons[0]) - if n.sons[0].kind == nkDotCall: + if n.sons[0].kind == nkDotCall: # it is a static call! result = n.sons[0] result.kind = nkCall + result.flags.incl nfExplicitCall for i in countup(1, sonsLen(n) - 1): addSon(result, n.sons[i]) return semExpr(c, result, flags) - else: + else: n.sons[0] = semExpr(c, n.sons[0]) let nOrig = n.copyTree semOpAux(c, n) @@ -999,7 +1000,7 @@ proc dotTransformation(c: PContext, n: PNode): PNode = else: var i = considerAcc(n.sons[1]) result = newNodeI(nkDotCall, n.info) - result.flags.incl nfDelegate + result.flags.incl nfDotField addSon(result, newIdentNode(i, n[1].info)) addSon(result, copyTree(n[0])) @@ -1082,12 +1083,13 @@ proc semArrayAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = proc propertyWriteAccess(c: PContext, n, nOrig, a: PNode): PNode = var id = considerAcc(a[1]) - let setterId = newIdentNode(getIdent(id.s & '='), n.info) + var setterId = newIdentNode(getIdent(id.s & '='), n.info) # a[0] is already checked for semantics, that does ``builtinFieldAccess`` # this is ugly. XXX Semantic checking should use the ``nfSem`` flag for # nodes? let aOrig = nOrig[0] result = newNode(nkCall, n.info, sons = @[setterId, a[0], semExpr(c, n[1])]) + result.flags.incl nfDotSetter let orig = newNode(nkCall, n.info, sons = @[setterId, aOrig[0], nOrig[1]]) result = semOverloadedCallAnalyseEffects(c, result, orig, {}) @@ -1777,22 +1779,6 @@ proc semBlock(c: PContext, n: PNode): PNode = closeScope(c) dec(c.p.nestedBlockCounter) -proc buildCall(n: PNode): PNode = - if n.kind == nkDotExpr and n.len == 2: - # x.y --> y(x) - result = newNodeI(nkCall, n.info, 2) - result.sons[0] = n.sons[1] - result.sons[1] = n.sons[0] - elif n.kind in nkCallKinds and n.sons[0].kind == nkDotExpr: - # x.y(a) -> y(x, a) - let a = n.sons[0] - result = newNodeI(nkCall, n.info, n.len+1) - result.sons[0] = a.sons[1] - result.sons[1] = a.sons[0] - for i in 1 .. delegator("b", a) - a.b(c, d) => delegator("b", a, c) - a b, c, d => delegator("a", b, c, d) - - -The delegators can be any callable symbol type (procs, templates, macros) -depending on the desired effect: - -.. code-block:: nimrod - proc `()` (field: string, js: PJsonNode): JSON {.delegator.} = js[field] - - var js = parseJson("{ x: 1, y: 2}") - echo js.x # outputs 1 - echo js.y # outputs 2 - - procvar pragma -------------- The `procvar`:idx: pragma is used to mark a proc that it can be passed to a diff --git a/tests/specialops/tdotops.nim b/tests/specialops/tdotops.nim new file mode 100644 index 0000000000..ce5b3942d7 --- /dev/null +++ b/tests/specialops/tdotops.nim @@ -0,0 +1,66 @@ +discard """ + output: ''' +10 +assigning z = 20 +reading field y +20 +call to y +dot call +no params call to a +100 +no params call to b +100 +one param call to c with 10 +100''' +""" + +type + T1 = object + x*: int + + TD = distinct T1 + + T2 = object + x: int + +proc `.`*(v: T1, f: string): int = + echo "reading field ", f + return v.x + +proc `.=`(x: var T1, f: string{lit}, v: int) = + echo "assigning ", f, " = ", v + x.x = v + +template `.()`(x: T1, f: string, args: varargs[expr]): string = + echo "call to ", f + "dot call" + +echo "" + +var t = T1(x: 10) + +echo t.x +t.z = 20 +echo t.y +echo t.y() + +var d = TD(t) +assert(not compiles(d.y)) + +proc `.`(v: T2, f: string): int = + echo "no params call to ", f + return v.x + +proc `.`*(v: T2, f: string, a: int): int = + echo "one param call to ", f, " with ", a + return v.x + +var tt = T2(x: 100) + +echo tt.a +echo tt.b() +echo tt.c(10) + +assert(not compiles(tt.d("x"))) +assert(not compiles(tt.d(1, 2))) + diff --git a/web/news.txt b/web/news.txt index a045eb8803..b28e5c64a4 100644 --- a/web/news.txt +++ b/web/news.txt @@ -68,8 +68,8 @@ Language Additions - Exported templates are allowed to access hidden fields. - The ``using statement`` enables you to more easily author domain-specific languages and libraries providing OOP-like syntactic sugar. -- Added a new ``delegator pragma`` for handling calls to missing procs and - fields at compile-time. +- Added the possibility to override various dot operators in order to handle + calls to missing procs and reads from undeclared fields at compile-time. - The overload resolution now supports ``static[T]`` params that must be evaluable at compile-time. - Support for user-defined type classes has been added.