Skip to content

Commit 3bdf240

Browse files
committed
Implement @infix for types and patterns
1 parent 68ebca8 commit 3bdf240

File tree

5 files changed

+110
-42
lines changed

5 files changed

+110
-42
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,13 +1401,6 @@ object desugar {
14011401
}
14021402
// This is a deliberate departure from scalac, where StringContext is not rooted (See #4732)
14031403
Apply(Select(Apply(scalaDot(nme.StringContext), strs), id), elems)
1404-
case InfixOp(l, op, r) =>
1405-
if (ctx.mode is Mode.Type)
1406-
AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
1407-
else {
1408-
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
1409-
Apply(op, l :: r :: Nil) // op(l, r)
1410-
}
14111404
case PostfixOp(t, op) =>
14121405
if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) {
14131406
val seqType = if (ctx.compilationUnit.isJava) defn.ArrayType else defn.SeqType

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -697,8 +697,9 @@ object Trees {
697697
* if (result.isDefined) "match patterns against result"
698698
*/
699699
case class UnApply[-T >: Untyped] private[ast] (fun: Tree[T], implicits: List[Tree[T]], patterns: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
700-
extends PatternTree[T] {
700+
extends ProxyTree[T] with PatternTree[T] {
701701
type ThisTree[-T >: Untyped] = UnApply[T]
702+
def forwardTo = fun
702703
}
703704

704705
/** mods val name: tpt = rhs */

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -675,28 +675,47 @@ trait Checking {
675675
/** Check that `tree` is a valid infix operation. That is, if the
676676
* operator is alphanumeric, it must be declared `@infix`.
677677
*/
678-
def checkValidInfix(tree: untpd.InfixOp, app: Tree)(implicit ctx: Context): Unit =
678+
def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = {
679+
680+
def isInfix(sym: Symbol): Boolean =
681+
sym.hasAnnotation(defn.InfixAnnot) ||
682+
defn.isInfix(sym) ||
683+
(sym.name == nme.unapply || sym.name == nme.unapplySeq) &&
684+
sym.owner.is(Module) && sym.owner.linkedClass.is(Case) &&
685+
isInfix(sym.owner.linkedClass)
686+
679687
tree.op match {
680688
case _: untpd.BackquotedIdent =>
681689
()
682-
case Ident(name: SimpleName)
683-
if !name.exists(isOperatorPart) &&
684-
!app.symbol.hasAnnotation(defn.InfixAnnot) &&
685-
!defn.isInfix(app.symbol) &&
686-
!app.symbol.maybeOwner.is(Scala2x) &&
687-
!infixOKSinceFollowedBy(tree.right) &&
688-
ctx.settings.strict.value =>
689-
ctx.deprecationWarning(
690-
i"""Alphanumeric method $name is not declared @infix; it should not be used as infix operator.
691-
|The operation can be rewritten automatically to `$name` under -deprecation -rewrite.
692-
|Or rewrite to method syntax .$name(...) manually.""",
693-
tree.op.sourcePos)
694-
if (ctx.settings.deprecation.value) {
695-
patch(Span(tree.op.span.start, tree.op.span.start), "`")
696-
patch(Span(tree.op.span.end, tree.op.span.end), "`")
690+
case Ident(name: Name) =>
691+
name.toTermName match {
692+
case name: SimpleName
693+
if !name.exists(isOperatorPart) &&
694+
!isInfix(meth) &&
695+
!meth.maybeOwner.is(Scala2x) &&
696+
!infixOKSinceFollowedBy(tree.right) &&
697+
ctx.settings.strict.value =>
698+
val (kind, alternative) =
699+
if (ctx.mode.is(Mode.Type))
700+
("type", (n: Name) => s"prefix syntax $n[...]")
701+
else if (ctx.mode.is(Mode.Pattern))
702+
("extractor", (n: Name) => s"prefix syntax $n(...)")
703+
else
704+
("method", (n: Name) => s"method syntax .$n(...)")
705+
ctx.deprecationWarning(
706+
i"""Alphanumeric $kind $name is not declared @infix; it should not be used as infix operator.
707+
|The operation can be rewritten automatically to `$name` under -deprecation -rewrite.
708+
|Or rewrite to ${alternative(name)} manually.""",
709+
tree.op.sourcePos)
710+
if (ctx.settings.deprecation.value) {
711+
patch(Span(tree.op.span.start, tree.op.span.start), "`")
712+
patch(Span(tree.op.span.end, tree.op.span.end), "`")
713+
}
714+
case _ =>
697715
}
698716
case _ =>
699717
}
718+
}
700719

701720
/** Issue a feature warning if feature is not enabled */
702721
def checkFeature(name: TermName,
@@ -1087,6 +1106,6 @@ trait NoChecking extends ReChecking {
10871106
override def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = ()
10881107
override def checkMembersOK(tp: Type, pos: SourcePosition)(implicit ctx: Context): Type = tp
10891108
override def checkInInlineContext(what: String, posd: Positioned)(implicit ctx: Context): Unit = ()
1090-
override def checkValidInfix(tree: untpd.InfixOp, app: Tree)(implicit ctx: Context): Unit = ()
1109+
override def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = ()
10911110
override def checkFeature(name: TermName, description: => String, featureUseSite: Symbol, pos: SourcePosition)(implicit ctx: Context): Unit = ()
10921111
}

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,28 +1874,39 @@ class Typer extends Namer
18741874

18751875
/** Translate infix operation expression `l op r` to
18761876
*
1877-
* l.op(r) if `op` is left-associative
1877+
* l.op(r) if `op` is left-associative
18781878
* { val x = l; r.op(l) } if `op` is right-associative call-by-value and `l` is impure
18791879
* r.op(l) if `op` is right-associative call-by-name or `l` is pure
1880+
*
1881+
* Translate infix type `l op r` to `op[l, r]`
1882+
* Translate infix pattern `l op r` to `op(l, r)`
18801883
*/
18811884
def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = {
18821885
val untpd.InfixOp(l, op, r) = tree
1883-
val app = typedApply(desugar.binop(l, op, r), pt)
1884-
checkValidInfix(tree, app)
1885-
if (untpd.isLeftAssoc(op.name)) app
1886-
else {
1887-
val defs = new mutable.ListBuffer[Tree]
1888-
def lift(app: Tree): Tree = (app: @unchecked) match {
1889-
case Apply(fn, args) =>
1890-
if (app.tpe.isError) app
1891-
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
1892-
case Assign(lhs, rhs) =>
1893-
tpd.cpy.Assign(app)(lhs, lift(rhs))
1894-
case Block(stats, expr) =>
1895-
tpd.cpy.Block(app)(stats, lift(expr))
1886+
val result =
1887+
if (ctx.mode.is(Mode.Type))
1888+
typedAppliedTypeTree(cpy.AppliedTypeTree(tree)(op, l :: r :: Nil))
1889+
else if (ctx.mode.is(Mode.Pattern))
1890+
typedUnApply(cpy.Apply(tree)(op, l :: r :: Nil), pt)
1891+
else {
1892+
val app = typedApply(desugar.binop(l, op, r), pt)
1893+
if (untpd.isLeftAssoc(op.name)) app
1894+
else {
1895+
val defs = new mutable.ListBuffer[Tree]
1896+
def lift(app: Tree): Tree = (app: @unchecked) match {
1897+
case Apply(fn, args) =>
1898+
if (app.tpe.isError) app
1899+
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
1900+
case Assign(lhs, rhs) =>
1901+
tpd.cpy.Assign(app)(lhs, lift(rhs))
1902+
case Block(stats, expr) =>
1903+
tpd.cpy.Block(app)(stats, lift(expr))
1904+
}
1905+
wrapDefs(defs, lift(app))
1906+
}
18961907
}
1897-
wrapDefs(defs, lift(app))
1898-
}
1908+
checkValidInfix(tree, result.symbol)
1909+
result
18991910
}
19001911

19011912
/** Translate tuples of all arities */
@@ -2129,7 +2140,7 @@ class Typer extends Namer
21292140
case tree: untpd.UnApply => typedUnApply(tree, pt)
21302141
case tree: untpd.Tuple => typedTuple(tree, pt)
21312142
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withSpan(tree.span), pt)
2132-
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
2143+
case tree: untpd.InfixOp => typedInfixOp(tree, pt)
21332144
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
21342145
case untpd.EmptyTree => tpd.EmptyTree
21352146
case tree: untpd.Quote => typedQuote(tree, pt)

tests/neg-custom-args/infix.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import scala.annotation.infix
33
class C {
44
@infix def op(x: Int): Int = ???
55
def meth(x: Int): Int = ???
6+
def matching(x: Int => Int) = ???
7+
def +(x: Int): Int = ???
68
}
79

810
val c = C()
@@ -12,4 +14,46 @@ def test() = {
1214

1315
c.op(2)
1416
c meth 2 // error: should not be used as infix operator
17+
c `meth` 2 // OK, sincd `meth` is backquoted
18+
c + 3 // OK, since `+` is symbolic
19+
1 to 2 // OK, since `to` is defined by Scala-2
20+
c meth { // OK, since `meth` is followed by `{...}`
21+
3
22+
}
23+
c matching { // OK, since `meth` is followed by `{...}`
24+
case x => x
25+
}
26+
27+
@infix class Or[X, Y]
28+
class AndC[X, Y]
29+
@infix type And[X, Y] = AndC[X, Y]
30+
@infix type &&[X, Y] = AndC[X, Y]
31+
32+
class Map[X, Y]
33+
34+
val x1: Int Map String = ??? // error
35+
val x2: Int Or String = ??? // OK since Or is declared `@infix`
36+
val x3: Int AndC String = ??? // error
37+
val x4: Int `AndC` String = ??? // OK
38+
val x5: Int And String = ??? // OK
39+
val x6: Int && String = ???
40+
41+
case class Pair[T](x: T, y: T)
42+
@infix case class Q[T](x: T, y: T)
43+
44+
object PP {
45+
@infix def unapply[T](x: Pair[T]): Option[(T, T)] = Some((x.x, x.y))
46+
}
47+
48+
val p = Pair(1, 2)
49+
val Pair(_, _) = p
50+
val _ Pair _ = p // error
51+
val _ `Pair` _ = p // OK
52+
val _ PP _ = p // OK
53+
54+
val q = Q(1, 2)
55+
val Q(_, _) = q
56+
val _ Q _ = p // OK
57+
58+
1559
}

0 commit comments

Comments
 (0)