diff --git a/changelog.md b/changelog.md
index 3ede1ceea..7a817fd81 100644
--- a/changelog.md
+++ b/changelog.md
@@ -27,6 +27,9 @@
### Language additions
+- Dot calls combined with explicit generic instantiations can now be written
+ as ``x.y[:z]``. ``x.y[:z]`` that is transformed into ``y[z](x)`` in the parser.
+
### Language changes
- The `importcpp` pragma now allows importing the listed fields of generic
diff --git a/compiler/docgen.nim b/compiler/docgen.nim
index 65dcb73c9..67ff9edf1 100644
--- a/compiler/docgen.nim
+++ b/compiler/docgen.nim
@@ -257,7 +257,8 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe
tkBracketDotLe, tkBracketDotRi, tkParDotLe,
tkParDotRi, tkComma, tkSemiColon, tkColon, tkEquals, tkDot, tkDotDot,
tkAccent, tkColonColon,
- tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr:
+ tkGStrLit, tkGTripleStrLit, tkInfixOpr, tkPrefixOpr, tkPostfixOpr,
+ tkBracketLeColon:
dispA(result, "$1", "\\spanOther{$1}",
[rope(esc(d.target, literal))])
diff --git a/compiler/lexer.nim b/compiler/lexer.nim
index afa7642dc..e55da2f35 100644
--- a/compiler/lexer.nim
+++ b/compiler/lexer.nim
@@ -60,7 +60,7 @@ type
tkCurlyDotLe, tkCurlyDotRi, # {. and .}
tkParDotLe, tkParDotRi, # (. and .)
tkComma, tkSemiColon,
- tkColon, tkColonColon, tkEquals, tkDot, tkDotDot,
+ tkColon, tkColonColon, tkEquals, tkDot, tkDotDot, tkBracketLeColon,
tkOpr, tkComment, tkAccent,
tkSpaces, tkInfixOpr, tkPrefixOpr, tkPostfixOpr
@@ -98,7 +98,7 @@ const
"tkTripleStrLit", "tkGStrLit", "tkGTripleStrLit", "tkCharLit", "(",
")", "[", "]", "{", "}", "[.", ".]", "{.", ".}", "(.", ".)",
",", ";",
- ":", "::", "=", ".", "..",
+ ":", "::", "=", ".", "..", "[:",
"tkOpr", "tkComment", "`",
"tkSpaces", "tkInfixOpr",
"tkPrefixOpr", "tkPostfixOpr"]
@@ -1119,6 +1119,9 @@ proc rawGetTok*(L: var TLexer, tok: var TToken) =
if L.buf[L.bufpos] == '.' and L.buf[L.bufpos+1] != '.':
tok.tokType = tkBracketDotLe
inc(L.bufpos)
+ elif L.buf[L.bufpos] == ':':
+ tok.tokType = tkBracketLeColon
+ inc(L.bufpos)
else:
tok.tokType = tkBracketLe
of ']':
diff --git a/compiler/parser.nim b/compiler/parser.nim
index debb0b34d..78a89f6c6 100644
--- a/compiler/parser.nim
+++ b/compiler/parser.nim
@@ -389,20 +389,6 @@ proc exprList(p: var TParser, endTok: TTokType, result: PNode) =
getTok(p)
optInd(p, a)
-proc dotExpr(p: var TParser, a: PNode): PNode =
- #| dotExpr = expr '.' optInd symbol
- var info = p.parLineInfo
- getTok(p)
- result = newNodeI(nkDotExpr, info)
- optInd(p, result)
- addSon(result, a)
- addSon(result, parseSymbol(p, smAfterDot))
-
-proc qualifiedIdent(p: var TParser): PNode =
- #| qualifiedIdent = symbol ('.' optInd symbol)?
- result = parseSymbol(p)
- if p.tok.tokType == tkDot: result = dotExpr(p, result)
-
proc exprColonEqExprListAux(p: var TParser, endTok: TTokType, result: PNode) =
assert(endTok in {tkCurlyRi, tkCurlyDotRi, tkBracketRi, tkParRi})
getTok(p)
@@ -423,6 +409,33 @@ proc exprColonEqExprList(p: var TParser, kind: TNodeKind,
result = newNodeP(kind, p)
exprColonEqExprListAux(p, endTok, result)
+proc dotExpr(p: var TParser, a: PNode): PNode =
+ #| dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
+ #| explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
+ var info = p.parLineInfo
+ getTok(p)
+ result = newNodeI(nkDotExpr, info)
+ optInd(p, result)
+ addSon(result, a)
+ addSon(result, parseSymbol(p, smAfterDot))
+ if p.tok.tokType == tkBracketLeColon and p.tok.strongSpaceA <= 0:
+ var x = newNodeI(nkBracketExpr, p.parLineInfo)
+ # rewrite 'x.y[:z]()' to 'y[z](x)'
+ x.add result[1]
+ exprList(p, tkBracketRi, x)
+ eat(p, tkBracketRi)
+ var y = newNodeI(nkCall, p.parLineInfo)
+ y.add x
+ y.add result[0]
+ if p.tok.tokType == tkParLe and p.tok.strongSpaceA <= 0:
+ exprColonEqExprListAux(p, tkParRi, y)
+ result = y
+
+proc qualifiedIdent(p: var TParser): PNode =
+ #| qualifiedIdent = symbol ('.' optInd symbol)?
+ result = parseSymbol(p)
+ if p.tok.tokType == tkDot: result = dotExpr(p, result)
+
proc setOrTableConstr(p: var TParser): PNode =
#| setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}'
result = newNodeP(nkCurly, p)
diff --git a/doc/grammar.txt b/doc/grammar.txt
index f25b079f4..e06ebd5d9 100644
--- a/doc/grammar.txt
+++ b/doc/grammar.txt
@@ -26,9 +26,10 @@ symbol = '`' (KEYW|IDENT|literal|(operator|'('|')'|'['|']'|'{'|'}'|'=')+)+ '`'
| IDENT | KEYW
exprColonEqExpr = expr (':'|'=' expr)?
exprList = expr ^+ comma
-dotExpr = expr '.' optInd symbol
-qualifiedIdent = symbol ('.' optInd symbol)?
exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)?
+dotExpr = expr '.' optInd (symbol | '[:' exprList ']')
+explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )?
+qualifiedIdent = symbol ('.' optInd symbol)?
setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}'
castExpr = 'cast' '[' optInd typeDesc optPar ']' '(' optInd expr optPar ')'
parKeyw = 'discard' | 'include' | 'if' | 'while' | 'case' | 'try'
diff --git a/doc/manual/generics.txt b/doc/manual/generics.txt
index 4c908fefe..01f76b591 100644
--- a/doc/manual/generics.txt
+++ b/doc/manual/generics.txt
@@ -335,8 +335,8 @@ The concept types can be parametric just like the regular generic types:
AnyTransform3D* = AnyMatrix[4, 4, float]
proc transposed*(m: AnyMatrix): m.TransposedType =
- for r in 0 .. `_.
+The ``[: ]`` notation has been designed to mitigate this issue: ``x.p[:T]``
+is rewritten by the parser to ``p[T](x)``, ``x.p[:T](y)`` is rewritten to
+``p[T](x, y)``. Note that ``[: ]`` has no AST representation, the rewrite
+is performed directly in the parsing step.
+
Properties
----------
diff --git a/tests/parser/tprecedence.nim b/tests/parser/tprecedence.nim
index d2c6d0b30..d586f14a3 100644
--- a/tests/parser/tprecedence.nim
+++ b/tests/parser/tprecedence.nim
@@ -1,6 +1,7 @@
discard """
output: '''holla
-true'''
+true
+defabc 4'''
"""
# Test top level semicolon works properly:
@@ -13,3 +14,8 @@ proc `\*` (x, y: int): int = result = x * y
echo 5 \+ 1 \* 9 == 6*9
+proc foo[S, T](x: S, y: T): T = x & y
+
+proc bar[T](x: T): T = x
+
+echo "def".foo[:string, string]("abc"), " ", 4.bar[:int]