Skip to content

Commit addd807

Browse files
committed
Implement @infix for types and patterns
1 parent fc69c1d commit addd807

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
@@ -1440,13 +1440,6 @@ object desugar {
14401440
}
14411441
// This is a deliberate departure from scalac, where StringContext is not rooted (See #4732)
14421442
Apply(Select(Apply(scalaDot(nme.StringContext), strs), id), elems)
1443-
case InfixOp(l, op, r) =>
1444-
if (ctx.mode is Mode.Type)
1445-
AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
1446-
else {
1447-
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
1448-
Apply(op, l :: r :: Nil) // op(l, r)
1449-
}
14501443
case PostfixOp(t, op) =>
14511444
if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) {
14521445
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
@@ -719,8 +719,9 @@ object Trees {
719719
* if (result.isDefined) "match patterns against result"
720720
*/
721721
case class UnApply[-T >: Untyped] private[ast] (fun: Tree[T], implicits: List[Tree[T]], patterns: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
722-
extends PatternTree[T] {
722+
extends ProxyTree[T] with PatternTree[T] {
723723
type ThisTree[-T >: Untyped] = UnApply[T]
724+
def forwardTo = fun
724725
}
725726

726727
/** 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
@@ -728,28 +728,47 @@ trait Checking {
728728
/** Check that `tree` is a valid infix operation. That is, if the
729729
* operator is alphanumeric, it must be declared `@infix`.
730730
*/
731-
def checkValidInfix(tree: untpd.InfixOp, app: Tree)(implicit ctx: Context): Unit =
731+
def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = {
732+
733+
def isInfix(sym: Symbol): Boolean =
734+
sym.hasAnnotation(defn.InfixAnnot) ||
735+
defn.isInfix(sym) ||
736+
(sym.name == nme.unapply || sym.name == nme.unapplySeq) &&
737+
sym.owner.is(Module) && sym.owner.linkedClass.is(Case) &&
738+
isInfix(sym.owner.linkedClass)
739+
732740
tree.op match {
733741
case _: untpd.BackquotedIdent =>
734742
()
735-
case Ident(name: SimpleName)
736-
if !name.exists(isOperatorPart) &&
737-
!app.symbol.hasAnnotation(defn.InfixAnnot) &&
738-
!defn.isInfix(app.symbol) &&
739-
!app.symbol.maybeOwner.is(Scala2x) &&
740-
!infixOKSinceFollowedBy(tree.right) &&
741-
ctx.settings.strict.value =>
742-
ctx.deprecationWarning(
743-
i"""Alphanumeric method $name is not declared @infix; it should not be used as infix operator.
744-
|The operation can be rewritten automatically to `$name` under -deprecation -rewrite.
745-
|Or rewrite to method syntax .$name(...) manually.""",
746-
tree.op.sourcePos)
747-
if (ctx.settings.deprecation.value) {
748-
patch(Span(tree.op.span.start, tree.op.span.start), "`")
749-
patch(Span(tree.op.span.end, tree.op.span.end), "`")
743+
case Ident(name: Name) =>
744+
name.toTermName match {
745+
case name: SimpleName
746+
if !name.exists(isOperatorPart) &&
747+
!isInfix(meth) &&
748+
!meth.maybeOwner.is(Scala2x) &&
749+
!infixOKSinceFollowedBy(tree.right) &&
750+
ctx.settings.strict.value =>
751+
val (kind, alternative) =
752+
if (ctx.mode.is(Mode.Type))
753+
("type", (n: Name) => s"prefix syntax $n[...]")
754+
else if (ctx.mode.is(Mode.Pattern))
755+
("extractor", (n: Name) => s"prefix syntax $n(...)")
756+
else
757+
("method", (n: Name) => s"method syntax .$n(...)")
758+
ctx.deprecationWarning(
759+
i"""Alphanumeric $kind $name is not declared @infix; it should not be used as infix operator.
760+
|The operation can be rewritten automatically to `$name` under -deprecation -rewrite.
761+
|Or rewrite to ${alternative(name)} manually.""",
762+
tree.op.sourcePos)
763+
if (ctx.settings.deprecation.value) {
764+
patch(Span(tree.op.span.start, tree.op.span.start), "`")
765+
patch(Span(tree.op.span.end, tree.op.span.end), "`")
766+
}
767+
case _ =>
750768
}
751769
case _ =>
752770
}
771+
}
753772

754773
/** Issue a feature warning if feature is not enabled */
755774
def checkFeature(name: TermName,
@@ -1134,6 +1153,6 @@ trait NoChecking extends ReChecking {
11341153
override def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = ()
11351154
override def checkMembersOK(tp: Type, pos: SourcePosition)(implicit ctx: Context): Type = tp
11361155
override def checkInInlineContext(what: String, posd: Positioned)(implicit ctx: Context): Unit = ()
1137-
override def checkValidInfix(tree: untpd.InfixOp, app: Tree)(implicit ctx: Context): Unit = ()
1156+
override def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = ()
11381157
override def checkFeature(name: TermName, description: => String, featureUseSite: Symbol, pos: SourcePosition)(implicit ctx: Context): Unit = ()
11391158
}

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

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

18801880
/** Translate infix operation expression `l op r` to
18811881
*
1882-
* l.op(r) if `op` is left-associative
1882+
* l.op(r) if `op` is left-associative
18831883
* { val x = l; r.op(l) } if `op` is right-associative call-by-value and `l` is impure
18841884
* r.op(l) if `op` is right-associative call-by-name or `l` is pure
1885+
*
1886+
* Translate infix type `l op r` to `op[l, r]`
1887+
* Translate infix pattern `l op r` to `op(l, r)`
18851888
*/
18861889
def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = {
18871890
val untpd.InfixOp(l, op, r) = tree
1888-
val app = typedApply(desugar.binop(l, op, r), pt)
1889-
checkValidInfix(tree, app)
1890-
if (untpd.isLeftAssoc(op.name)) app
1891-
else {
1892-
val defs = new mutable.ListBuffer[Tree]
1893-
def lift(app: Tree): Tree = (app: @unchecked) match {
1894-
case Apply(fn, args) =>
1895-
if (app.tpe.isError) app
1896-
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
1897-
case Assign(lhs, rhs) =>
1898-
tpd.cpy.Assign(app)(lhs, lift(rhs))
1899-
case Block(stats, expr) =>
1900-
tpd.cpy.Block(app)(stats, lift(expr))
1891+
val result =
1892+
if (ctx.mode.is(Mode.Type))
1893+
typedAppliedTypeTree(cpy.AppliedTypeTree(tree)(op, l :: r :: Nil))
1894+
else if (ctx.mode.is(Mode.Pattern))
1895+
typedUnApply(cpy.Apply(tree)(op, l :: r :: Nil), pt)
1896+
else {
1897+
val app = typedApply(desugar.binop(l, op, r), pt)
1898+
if (untpd.isLeftAssoc(op.name)) app
1899+
else {
1900+
val defs = new mutable.ListBuffer[Tree]
1901+
def lift(app: Tree): Tree = (app: @unchecked) match {
1902+
case Apply(fn, args) =>
1903+
if (app.tpe.isError) app
1904+
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
1905+
case Assign(lhs, rhs) =>
1906+
tpd.cpy.Assign(app)(lhs, lift(rhs))
1907+
case Block(stats, expr) =>
1908+
tpd.cpy.Block(app)(stats, lift(expr))
1909+
}
1910+
wrapDefs(defs, lift(app))
1911+
}
19011912
}
1902-
wrapDefs(defs, lift(app))
1903-
}
1913+
checkValidInfix(tree, result.symbol)
1914+
result
19041915
}
19051916

19061917
/** Translate tuples of all arities */
@@ -2153,7 +2164,7 @@ class Typer extends Namer
21532164
case tree: untpd.UnApply => typedUnApply(tree, pt)
21542165
case tree: untpd.Tuple => typedTuple(tree, pt)
21552166
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withSpan(tree.span), pt)
2156-
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
2167+
case tree: untpd.InfixOp => typedInfixOp(tree, pt)
21572168
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
21582169
case untpd.EmptyTree => tpd.EmptyTree
21592170
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)