Skip to content

Add @infix annotation #5975

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

Merged
merged 13 commits into from
May 15, 2019
9 changes: 1 addition & 8 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object desugar {
* case class method that clashes with a user-defined method?
*/
def isRetractableCaseClassMethodName(name: Name)(implicit ctx: Context): Boolean = name match {
case nme.apply | nme.unapply | nme.copy => true
case nme.apply | nme.unapply | nme.unapplySeq | nme.copy => true
case DefaultGetterName(nme.copy, _) => true
case _ => false
}
Expand Down Expand Up @@ -1440,13 +1440,6 @@ object desugar {
}
// This is a deliberate departure from scalac, where StringContext is not rooted (See #4732)
Apply(Select(Apply(scalaDot(nme.StringContext), strs), id), elems)
case InfixOp(l, op, r) =>
if (ctx.mode is Mode.Type)
AppliedTypeTree(op, l :: r :: Nil) // op[l, r]
else {
assert(ctx.mode is Mode.Pattern) // expressions are handled separately by `binop`
Apply(op, l :: r :: Nil) // op(l, r)
}
case PostfixOp(t, op) =>
if ((ctx.mode is Mode.Type) && !op.isBackquoted && op.name == tpnme.raw.STAR) {
val seqType = if (ctx.compilationUnit.isJava) defn.ArrayType else defn.SeqType
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,9 @@ object Trees {
* if (result.isDefined) "match patterns against result"
*/
case class UnApply[-T >: Untyped] private[ast] (fun: Tree[T], implicits: List[Tree[T]], patterns: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
extends PatternTree[T] {
extends ProxyTree[T] with PatternTree[T] {
type ThisTree[-T >: Untyped] = UnApply[T]
def forwardTo = fun
}

/** mods val name: tpt = rhs */
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,10 @@ class Definitions {
def ShowAsInfixAnnot(implicit ctx: Context): ClassSymbol = ShowAsInfixAnotType.symbol.asClass
lazy val FunctionalInterfaceAnnotType = ctx.requiredClassRef("java.lang.FunctionalInterface")
def FunctionalInterfaceAnnot(implicit ctx: Context) = FunctionalInterfaceAnnotType.symbol.asClass
lazy val InfixAnnotType = ctx.requiredClassRef("scala.annotation.infix")
def InfixAnnot(implicit ctx: Context) = InfixAnnotType.symbol.asClass
lazy val AlphaAnnotType = ctx.requiredClassRef("scala.annotation.alpha")
def AlphaAnnot(implicit ctx: Context) = AlphaAnnotType.symbol.asClass

// convenient one-parameter method types
def methOfAny(tp: Type): MethodType = MethodType(List(AnyType), tp)
Expand Down Expand Up @@ -1283,6 +1287,10 @@ class Definitions {
else parents
}

/** Is synthesized symbol with alphanumeric name allowed to be used as an infix operator? */
def isInfix(sym: Symbol)(implicit ctx: Context): Boolean =
(sym eq Object_eq) || (sym eq Object_ne)

// ----- primitive value class machinery ------------------------------------------

/** This class would also be obviated by the implicit function type design */
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ object NameOps {
def isSelectorName: Boolean = testSimple(n => n.startsWith("_") && n.drop(1).forall(_.isDigit))
def isAnonymousClassName: Boolean = name.startsWith(str.ANON_CLASS)
def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN)
def isUnapplyName: Boolean = name == nme.unapply || name == nme.unapplySeq

/** Is name a variable name? */
def isVariableName: Boolean = testSimple { n =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,7 @@ object SymDenotations {

def isInlineMethod(implicit ctx: Context): Boolean =
is(InlineMethod, butNot = Accessor) &&
name != nme.unapply // unapply methods do not count as inline methods
!name.isUnapplyName // unapply methods do not count as inline methods
// we need an inline flag on them only do that
// reduceProjection gets access to their rhs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
changePrec(OrPrec) { toText(trees, " | ") }
case UnApply(fun, implicits, patterns) =>
val extractor = fun match {
case Select(extractor, nme.unapply) => extractor
case Select(extractor, name) if name.isUnapplyName => extractor
case _ => fun
}
toTextLocal(extractor) ~
Expand Down
65 changes: 60 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,24 @@ import rewrites.Rewrites.patch
import util.Spans.Span

import util.SourcePosition
import util.Spans.Span
import rewrites.Rewrites.patch
import transform.SymUtils._
import transform.ValueClasses._
import Decorators._
import ErrorReporting.{err, errorType}
import config.Printers.{typr, patmatch}
import NameKinds.DefaultGetterName
import NameOps._
import SymDenotations.{NoCompleter, NoDenotation}
import Applications.unapplyArgs
import transform.patmat.SpaceEngine.isIrrefutableUnapply


import collection.mutable
import SymDenotations.{NoCompleter, NoDenotation}
import dotty.tools.dotc.reporting.diagnostic.Message
import dotty.tools.dotc.reporting.diagnostic.messages._
import dotty.tools.dotc.transform.ValueClasses._
import reporting.diagnostic.Message
import reporting.diagnostic.messages._
import scala.tasty.util.Chars.isOperatorPart

object Checking {
import tpd._
Expand Down Expand Up @@ -716,6 +721,55 @@ trait Checking {
i"Use of implicit conversion ${conv.showLocated}", NoSymbol, posd.sourcePos)
}

private def infixOKSinceFollowedBy(tree: untpd.Tree): Boolean = tree match {
case _: untpd.Block | _: untpd.Match => true
case _ => false
}

/** Check that `tree` is a valid infix operation. That is, if the
* operator is alphanumeric, it must be declared `@infix`.
*/
def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = {

def isInfix(sym: Symbol): Boolean =
sym.hasAnnotation(defn.InfixAnnot) ||
defn.isInfix(sym) ||
sym.name.isUnapplyName &&
sym.owner.is(Module) && sym.owner.linkedClass.is(Case) &&
isInfix(sym.owner.linkedClass)

tree.op match {
case _: untpd.BackquotedIdent =>
()
case Ident(name: Name) =>
name.toTermName match {
case name: SimpleName
if !name.exists(isOperatorPart) &&
!isInfix(meth) &&
!meth.maybeOwner.is(Scala2x) &&
!infixOKSinceFollowedBy(tree.right) &&
ctx.settings.strict.value =>
val (kind, alternative) =
if (ctx.mode.is(Mode.Type))
("type", (n: Name) => s"prefix syntax $n[...]")
else if (ctx.mode.is(Mode.Pattern))
("extractor", (n: Name) => s"prefix syntax $n(...)")
else
("method", (n: Name) => s"method syntax .$n(...)")
ctx.deprecationWarning(
i"""Alphanumeric $kind $name is not declared @infix; it should not be used as infix operator.
|The operation can be rewritten automatically to `$name` under -deprecation -rewrite.
|Or rewrite to ${alternative(name)} manually.""",
tree.op.sourcePos)
if (ctx.settings.deprecation.value) {
patch(Span(tree.op.span.start, tree.op.span.start), "`")
patch(Span(tree.op.span.end, tree.op.span.end), "`")
}
case _ =>
}
}
}

/** Issue a feature warning if feature is not enabled */
def checkFeature(name: TermName,
description: => String,
Expand Down Expand Up @@ -985,7 +1039,7 @@ trait Checking {
def checkInInlineContext(what: String, posd: Positioned)(implicit ctx: Context): Unit =
if (!ctx.inInlineMethod && !ctx.isInlineContext) {
val inInlineUnapply = ctx.owner.ownersIterator.exists(owner =>
owner.name == nme.unapply && owner.is(Inline) && owner.is(Method))
owner.name.isUnapplyName && owner.is(Inline) && owner.is(Method))
val msg =
if (inInlineUnapply) "cannot be used in an inline unapply"
else "can only be used in an inline method"
Expand Down Expand Up @@ -1099,5 +1153,6 @@ trait NoChecking extends ReChecking {
override def checkNoForwardDependencies(vparams: List[ValDef])(implicit ctx: Context): Unit = ()
override def checkMembersOK(tp: Type, pos: SourcePosition)(implicit ctx: Context): Type = tp
override def checkInInlineContext(what: String, posd: Positioned)(implicit ctx: Context): Unit = ()
override def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(implicit ctx: Context): Unit = ()
override def checkFeature(name: TermName, description: => String, featureUseSite: Symbol, pos: SourcePosition)(implicit ctx: Context): Unit = ()
}
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import StdNames.nme
import Contexts.Context
import Names.Name
import NameKinds.{InlineAccessorName, UniqueInlineName}
import NameOps._
import Annotations._
import transform.{AccessProxies, PCPCheckAndHeal, Splicer, TreeMapWithStages}
import config.Printers.inlining
Expand Down Expand Up @@ -246,7 +247,7 @@ object PrepareInlineable {
def checkInlineMethod(inlined: Symbol, body: Tree)(implicit ctx: Context): Unit = {
if (ctx.outer.inInlineMethod)
ctx.error(ex"implementation restriction: nested inline methods are not supported", inlined.sourcePos)
if (inlined.name == nme.unapply && tupleArgs(body).isEmpty)
if (inlined.name.isUnapplyName && tupleArgs(body).isEmpty)
ctx.warning(
em"inline unapply method can be rewritten only if its right hand side is a tuple (e1, ..., eN)",
body.sourcePos)
Expand Down
44 changes: 28 additions & 16 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1879,27 +1879,39 @@ class Typer extends Namer

/** Translate infix operation expression `l op r` to
*
* l.op(r) if `op` is left-associative
* l.op(r) if `op` is left-associative
* { val x = l; r.op(l) } if `op` is right-associative call-by-value and `l` is impure
* r.op(l) if `op` is right-associative call-by-name or `l` is pure
*
* Translate infix type `l op r` to `op[l, r]`
* Translate infix pattern `l op r` to `op(l, r)`
*/
def typedInfixOp(tree: untpd.InfixOp, pt: Type)(implicit ctx: Context): Tree = {
val untpd.InfixOp(l, op, r) = tree
val app = typedApply(desugar.binop(l, op, r), pt)
if (untpd.isLeftAssoc(op.name)) app
else {
val defs = new mutable.ListBuffer[Tree]
def lift(app: Tree): Tree = (app: @unchecked) match {
case Apply(fn, args) =>
if (app.tpe.isError) app
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
case Assign(lhs, rhs) =>
tpd.cpy.Assign(app)(lhs, lift(rhs))
case Block(stats, expr) =>
tpd.cpy.Block(app)(stats, lift(expr))
val result =
if (ctx.mode.is(Mode.Type))
typedAppliedTypeTree(cpy.AppliedTypeTree(tree)(op, l :: r :: Nil))
else if (ctx.mode.is(Mode.Pattern))
typedUnApply(cpy.Apply(tree)(op, l :: r :: Nil), pt)
else {
val app = typedApply(desugar.binop(l, op, r), pt)
if (untpd.isLeftAssoc(op.name)) app
else {
val defs = new mutable.ListBuffer[Tree]
def lift(app: Tree): Tree = (app: @unchecked) match {
case Apply(fn, args) =>
if (app.tpe.isError) app
else tpd.cpy.Apply(app)(fn, LiftImpure.liftArgs(defs, fn.tpe, args))
case Assign(lhs, rhs) =>
tpd.cpy.Assign(app)(lhs, lift(rhs))
case Block(stats, expr) =>
tpd.cpy.Block(app)(stats, lift(expr))
}
wrapDefs(defs, lift(app))
}
}
wrapDefs(defs, lift(app))
}
checkValidInfix(tree, result.symbol)
result
}

/** Translate tuples of all arities */
Expand Down Expand Up @@ -2152,7 +2164,7 @@ class Typer extends Namer
case tree: untpd.UnApply => typedUnApply(tree, pt)
case tree: untpd.Tuple => typedTuple(tree, pt)
case tree: untpd.DependentTypeTree => typed(untpd.TypeTree().withSpan(tree.span), pt)
case tree: untpd.InfixOp if ctx.mode.isExpr => typedInfixOp(tree, pt)
case tree: untpd.InfixOp => typedInfixOp(tree, pt)
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
case untpd.EmptyTree => tpd.EmptyTree
case tree: untpd.Quote => typedQuote(tree, pt)
Expand Down
3 changes: 2 additions & 1 deletion compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ class CompilationTests extends ParallelTesting {
"tests/neg-custom-args/toplevel-samesource/S.scala",
"tests/neg-custom-args/toplevel-samesource/nested/S.scala"),
defaultOptions),
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes)
compileFile("tests/neg-custom-args/i6300.scala", allowDeepSubtypes),
compileFile("tests/neg-custom-args/infix.scala", defaultOptions.and("-strict", "-deprecation", "-Xfatal-warnings"))
).checkExpectedErrors()
}

Expand Down
Loading