Skip to content

Fix #9132: Align with Scala 2's handling of () infix arguments #9137

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 5 commits into from
Jun 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ object desugar {
val nu = vparamss.foldLeft(makeNew(classTypeRef)) { (nu, vparams) =>
val app = Apply(nu, vparams.map(refOfDef))
vparams match {
case vparam :: _ if vparam.mods.is(Given) => app.setUsingApply()
case vparam :: _ if vparam.mods.is(Given) => app.setApplyKind(ApplyKind.Using)
case _ => app
}
}
Expand Down Expand Up @@ -1188,17 +1188,19 @@ object desugar {
case Assign(Ident(name), rhs) => cpy.NamedArg(arg)(name, rhs)
case _ => arg
}
def makeOp(fn: Tree, arg: Tree, selectPos: Span) = {
val args: List[Tree] = arg match {
case Parens(arg) => assignToNamedArg(arg) :: Nil
case Tuple(args) => args.mapConserve(assignToNamedArg)
case _ => arg :: Nil
}
def makeOp(fn: Tree, arg: Tree, selectPos: Span) =
val sel = Select(fn, op.name).withSpan(selectPos)
if (left.sourcePos.endLine < op.sourcePos.startLine)
sel.pushAttachment(MultiLineInfix, ())
Apply(sel, args)
}
arg match
case Parens(arg) =>
Apply(sel, assignToNamedArg(arg) :: Nil)
case Tuple(Nil) =>
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixUnit)
case Tuple(args) if args.nonEmpty => // this case should be dropped if auto-tupling is removed
Apply(sel, args.mapConserve(assignToNamedArg))
case _ =>
Apply(sel, arg :: Nil)

if (isLeftAssoc(op.name))
makeOp(left, right, Span(left.span.start, op.span.end, op.span.start))
Expand Down
15 changes: 13 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -443,15 +443,26 @@ object Trees {
def forwardTo: Tree[T] = fun
}

enum ApplyKind:
case Regular // r.f(x)
case Using // r.f(using x)
case InfixUnit // r f (), needs to be treated specially for an error message in typedApply

/** fun(args) */
case class Apply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
extends GenericApply[T] {
type ThisTree[-T >: Untyped] = Apply[T]

def isUsingApply = hasAttachment(untpd.ApplyGiven)
def setUsingApply() = { putAttachment(untpd.ApplyGiven, ()); this }
def setApplyKind(kind: ApplyKind) =
putAttachment(untpd.KindOfApply, kind)
this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A conceptual question: if Infix and Using are mutually exclusive, would it be better to use attachment for Infix as well to avoid creating the class InfixApply? Something like:

def setApplyKind(kind: ApplyKind) = 
  putAttachment(untpd.ApplyKind, kind)
  this

def applyKind: ApplyKind =
  attachmentOrElse(untpd.ApplyKind, ApplyKind.Regular)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that might be better. I'll try that out.


def applyKind: ApplyKind =
attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular)
}



/** fun[args] */
case class TypeApply[-T >: Untyped] private[ast] (fun: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
extends GenericApply[T] {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1370,5 +1370,5 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {


protected def FunProto(args: List[Tree], resType: Type)(using Context) =
ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, isUsingApply = false)
ProtoTypes.FunProtoTyped(args, resType)(ctx.typer, ApplyKind.Regular)
}
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
val OriginalSymbol: Property.Key[Symbol] = Property.Key()

/** Property key for contextual Apply trees of the form `fn given arg` */
val ApplyGiven: Property.StickyKey[Unit] = Property.StickyKey()
val KindOfApply: Property.StickyKey[ApplyKind] = Property.StickyKey()

// ------ Creation methods for untyped only -----------------

Expand Down Expand Up @@ -780,5 +780,5 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
}

protected def FunProto(args: List[Tree], resType: Type)(using Context) =
ProtoTypes.FunProto(args, resType)(ctx.typer, isUsingApply = false)
ProtoTypes.FunProto(args, resType)(ctx.typer, ApplyKind.Regular)
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2354,7 +2354,7 @@ object Parsers {

def mkApply(fn: Tree, args: (List[Tree], Boolean)): Tree =
val res = Apply(fn, args._1)
if args._2 then res.setUsingApply()
if args._2 then res.setApplyKind(ApplyKind.Using)
res

val argumentExpr: () => Tree = () => expr(Location.InArgs) match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
else
toTextLocal(fun)
~ "("
~ Str("using ").provided(app.isUsingApply && !homogenizedView)
~ Str("using ").provided(app.applyKind == ApplyKind.Using && !homogenizedView)
~ toTextGlobal(args, ", ")
~ ")"
case tree: TypeApply =>
Expand Down
36 changes: 27 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ trait Applications extends Compatibility {
/** The type of typed arguments: either tpd.Tree or Type */
type TypedArg

/** The kind of application that gets typed */
def applyKind: ApplyKind

/** Given an original argument and the type of the corresponding formal
* parameter, produce a typed argument.
*/
Expand Down Expand Up @@ -609,7 +612,14 @@ trait Applications extends Compatibility {

case nil =>
args match {
case arg :: args1 => fail(s"too many arguments for $methString", arg)
case arg :: args1 =>
val msg = arg match
case untpd.Tuple(Nil)
if applyKind == ApplyKind.InfixUnit && funType.widen.isNullaryMethod =>
i"can't supply unit value with infix notation because nullary $methString takes no arguments; use dotted invocation instead: (...).${methRef.name}()"
case _ =>
i"too many arguments for $methString"
fail(msg, arg)
case nil =>
}
}
Expand All @@ -624,6 +634,8 @@ trait Applications extends Compatibility {
type TypedArg = Arg
type Result = Unit

def applyKind = ApplyKind.Regular

protected def argOK(arg: TypedArg, formal: Type): Boolean = argType(arg, formal) match {
case ref: TermRef if ref.denot.isOverloaded =>
// in this case we could not resolve overloading because no alternative
Expand Down Expand Up @@ -687,7 +699,8 @@ trait Applications extends Compatibility {
* types of arguments are either known or unknown.
*/
abstract class TypedApply[T >: Untyped](
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type)(using Context)
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Trees.Tree[T]], resultType: Type,
override val applyKind: ApplyKind)(using Context)
extends Application(methRef, fun.tpe, args, resultType) {
type TypedArg = Tree
def isVarArg(arg: Trees.Tree[T]): Boolean = untpd.isWildcardStarArg(arg)
Expand Down Expand Up @@ -795,16 +808,20 @@ trait Applications extends Compatibility {
}

/** Subclass of Application for type checking an Apply node with untyped arguments. */
class ApplyToUntyped(app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context)
extends TypedApply(app, fun, methRef, proto.args, resultType) {
class ApplyToUntyped(
app: untpd.Apply, fun: Tree, methRef: TermRef, proto: FunProto,
resultType: Type)(using Context)
extends TypedApply(app, fun, methRef, proto.args, resultType, proto.applyKind) {
def typedArg(arg: untpd.Tree, formal: Type): TypedArg = proto.typedArg(arg, formal)
def treeToArg(arg: Tree): untpd.Tree = untpd.TypedSplice(arg)
def typeOfArg(arg: untpd.Tree): Type = proto.typeOfArg(arg)
}

/** Subclass of Application for type checking an Apply node with typed arguments. */
class ApplyToTyped(app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree], resultType: Type)(using Context)
extends TypedApply(app, fun, methRef, args, resultType) {
class ApplyToTyped(
app: untpd.Apply, fun: Tree, methRef: TermRef, args: List[Tree],
resultType: Type, applyKind: ApplyKind)(using Context)
extends TypedApply(app, fun, methRef, args, resultType, applyKind) {
def typedArg(arg: Tree, formal: Type): TypedArg = arg
def treeToArg(arg: Tree): Tree = arg
def typeOfArg(arg: Tree): Type = arg.tpe
Expand Down Expand Up @@ -840,7 +857,8 @@ trait Applications extends Compatibility {
def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree = {

def realApply(using Context): Tree = {
val originalProto = new FunProto(tree.args, IgnoredProto(pt))(this, tree.isUsingApply)(using argCtx(tree))
val originalProto =
new FunProto(tree.args, IgnoredProto(pt))(this, tree.applyKind)(using argCtx(tree))
record("typedApply")
val fun1 = typedFunPart(tree.fun, originalProto)

Expand Down Expand Up @@ -998,7 +1016,7 @@ trait Applications extends Compatibility {
def ApplyTo(app: untpd.Apply, fun: tpd.Tree, methRef: TermRef, proto: FunProto, resultType: Type)(using Context): tpd.Tree =
val typer = ctx.typer
if (proto.allArgTypesAreCurrent())
typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType).result
typer.ApplyToTyped(app, fun, methRef, proto.typedArgs(), resultType, proto.applyKind).result
else
typer.ApplyToUntyped(app, fun, methRef, proto, resultType)(
using fun.nullableInArgContext(using argCtx(app))).result
Expand Down Expand Up @@ -1655,7 +1673,7 @@ trait Applications extends Compatibility {
def resolve(alts: List[TermRef]): List[TermRef] =
pt match
case pt: FunProto =>
if pt.isUsingApply then
if pt.applyKind == ApplyKind.Using then
val alts0 = alts.filterConserve { alt =>
val mt = alt.widen.stripPoly
mt.isImplicitMethod || mt.isContextualMethod
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ object EtaExpansion extends LiftImpure {
if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam)
ids = ids.init :+ repeated(ids.last)
val app = Apply(lifted, ids)
if (mt.isContextualMethod) app.setUsingApply()
if (mt.isContextualMethod) app.setApplyKind(ApplyKind.Using)
val body = if (isLastApplication) app else PostfixOp(app, Ident(nme.WILDCARD))
val fn =
if (mt.isContextualMethod) new untpd.FunctionWithMods(params, body, Modifiers(Given))
Expand Down
19 changes: 9 additions & 10 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,8 @@ object ProtoTypes {
class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true)

trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto
trait FunOrPolyProto extends ProtoType { // common trait of PolyProto and FunProto
def isUsingApply: Boolean = false
}
trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto
def applyKind: ApplyKind = ApplyKind.Regular

class FunProtoState {

Expand All @@ -249,7 +248,7 @@ object ProtoTypes {
* [](args): resultType
*/
case class FunProto(args: List[untpd.Tree], resType: Type)(typer: Typer,
override val isUsingApply: Boolean, state: FunProtoState = new FunProtoState)(using protoCtx: Context)
override val applyKind: ApplyKind, state: FunProtoState = new FunProtoState)(using protoCtx: Context)
extends UncachedGroundType with ApplyingProto with FunOrPolyProto {
override def resultType(using Context): Type = resType

Expand All @@ -263,7 +262,7 @@ object ProtoTypes {

def derivedFunProto(args: List[untpd.Tree] = this.args, resultType: Type, typer: Typer = this.typer): FunProto =
if ((args eq this.args) && (resultType eq this.resultType) && (typer eq this.typer)) this
else new FunProto(args, resultType)(typer, isUsingApply)
else new FunProto(args, resultType)(typer, applyKind)

/** @return True if all arguments have types.
*/
Expand Down Expand Up @@ -353,7 +352,7 @@ object ProtoTypes {
case pt: FunProto =>
pt
case _ =>
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, isUsingApply)
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, applyKind)
tupled
}

Expand Down Expand Up @@ -388,15 +387,15 @@ object ProtoTypes {

override def withContext(newCtx: Context): ProtoType =
if newCtx `eq` protoCtx then this
else new FunProto(args, resType)(typer, isUsingApply, state)(using newCtx)
else new FunProto(args, resType)(typer, applyKind, state)(using newCtx)
}

/** A prototype for expressions that appear in function position
*
* [](args): resultType, where args are known to be typed
*/
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, isUsingApply: Boolean)(using Context)
extends FunProto(args, resultType)(typer, isUsingApply):
class FunProtoTyped(args: List[tpd.Tree], resultType: Type)(typer: Typer, applyKind: ApplyKind)(using Context)
extends FunProto(args, resultType)(typer, applyKind):
override def typedArgs(norm: (untpd.Tree, Int) => untpd.Tree)(using Context): List[tpd.Tree] = args
override def typedArg(arg: untpd.Tree, formal: Type)(using Context): tpd.Tree = arg.asInstanceOf[tpd.Tree]
override def allArgTypesAreCurrent()(using Context): Boolean = true
Expand Down Expand Up @@ -444,7 +443,7 @@ object ProtoTypes {
}

class UnapplyFunProto(argType: Type, typer: Typer)(using Context) extends FunProto(
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, isUsingApply = false)
untpd.TypedSplice(dummyTreeOfType(argType)(ctx.source))(ctx) :: Nil, WildcardType)(typer, applyKind = ApplyKind.Regular)

/** A prototype for expressions [] that are type-parameterized:
*
Expand Down
13 changes: 7 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ class Typer extends Namer
val nestedCtx = outerCtx.fresh.setNewTyperState()
inContext(nestedCtx) {
val protoArgs = args map (_ withType WildcardType)
val callProto = FunProto(protoArgs, WildcardType)(this, app.isUsingApply)
val callProto = FunProto(protoArgs, WildcardType)(this, app.applyKind)
val expr1 = typedExpr(expr, callProto)
if nestedCtx.reporter.hasErrors then NoType
else inContext(outerCtx) {
Expand Down Expand Up @@ -2887,7 +2887,7 @@ class Typer extends Namer
errorTree(tree, NoMatchingOverload(altDenots, pt))
def hasEmptyParams(denot: SingleDenotation) = denot.info.paramInfoss == ListOfNil
pt match {
case pt: FunOrPolyProto if !pt.isUsingApply =>
case pt: FunOrPolyProto if pt.applyKind != ApplyKind.Using =>
// insert apply or convert qualifier, but only for a regular application
tryInsertApplyOrImplicit(tree, pt, locked)(noMatches)
case _ =>
Expand Down Expand Up @@ -3047,7 +3047,7 @@ class Typer extends Namer
}
tryEither {
val app = cpy.Apply(tree)(untpd.TypedSplice(tree), namedArgs)
if (wtp.isContextualMethod) app.setUsingApply()
if (wtp.isContextualMethod) app.setApplyKind(ApplyKind.Using)
typr.println(i"try with default implicit args $app")
typed(app, pt, locked)
} { (_, _) =>
Expand All @@ -3063,7 +3063,7 @@ class Typer extends Namer
}
}
pt.revealIgnored match {
case pt: FunProto if pt.isUsingApply =>
case pt: FunProto if pt.applyKind == ApplyKind.Using =>
// We can end up here if extension methods are called with explicit given arguments.
// See for instance #7119.
tree
Expand Down Expand Up @@ -3535,8 +3535,9 @@ class Typer extends Namer
* Overridden in `ReTyper`, where all applications are treated the same
*/
protected def matchingApply(methType: MethodOrPoly, pt: FunProto)(using Context): Boolean =
methType.isContextualMethod == pt.isUsingApply ||
methType.isImplicitMethod && pt.isUsingApply // for a transition allow `with` arguments for regular implicit parameters
val isUsingApply = pt.applyKind == ApplyKind.Using
methType.isContextualMethod == isUsingApply
|| methType.isImplicitMethod && isUsingApply // for a transition allow `with` arguments for regular implicit parameters

/** Check that `tree == x: pt` is typeable. Used when checking a pattern
* against a selector of type `pt`. This implementation accounts for
Expand Down
9 changes: 9 additions & 0 deletions tests/neg/i2033.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Error: tests/neg/i2033.scala:7:30 -----------------------------------------------------------------------------------
7 | val arr = bos toByteArray () // error
| ^^
|can't supply unit value with infix notation because nullary method toByteArray: (): Array[Byte] takes no arguments; use dotted invocation instead: (...).toByteArray()
-- [E007] Type Mismatch Error: tests/neg/i2033.scala:20:35 -------------------------------------------------------------
20 | val out = new ObjectOutputStream(println) // error
| ^^^^^^^
| Found: Unit
| Required: String
2 changes: 1 addition & 1 deletion tests/neg/i2033.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ object Test {
def check(obj: AnyRef): Unit = {
val bos = new ByteArrayOutputStream()
val out = new ObjectOutputStream(println)
val arr = bos toByteArray ()
val arr = bos toByteArray () // error
val in = (())
val deser = ()
val lhs = mutable LinkedHashSet ()
Expand Down
13 changes: 13 additions & 0 deletions tests/neg/leading-infix-miss.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- [E008] Not Found Error: tests/neg/leading-infix-miss.scala:3:2 ------------------------------------------------------
2 | val x = false
3 | ! true // error // error
| ^
| value ! is not a member of Boolean.
| Note that `!` is treated as an infix operator in Scala 3.
| If you do not want that, insert a `;` or empty line in front
| or drop any spaces behind the operator.
-- [E007] Type Mismatch Error: tests/neg/leading-infix-miss.scala:3:8 --------------------------------------------------
3 | ! true // error // error
| ^
| Found: Unit
| Required: Boolean
3 changes: 3 additions & 0 deletions tests/neg/leading-infix-miss.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def test: Boolean =
val x = false
! true // error // error
2 changes: 2 additions & 0 deletions tests/run/i9132.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

()
4 changes: 4 additions & 0 deletions tests/run/i9132.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@main def Test =
scala.collection.mutable.ArrayBuilder.make[Unit] += ()
Console.println ()
Console println ()
4 changes: 2 additions & 2 deletions tests/run/iterators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ object Test {
def check_drop: Int = {
val it1 = Iterator.from(0)
val it2 = it1 map { 2 * _ }
val n1 = it1 drop 2 next()
val n2 = it2 drop 2 next();
val n1 = it1.drop(2).next()
val n2 = it2.drop(2).next()
n1 + n2
}

Expand Down
Loading