Skip to content

Leave arguments of infix operations tupled. #9159

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 2 commits into from
Jun 22, 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
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1195,10 +1195,10 @@ object desugar {
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
case Tuple(args) if args.exists(_.isInstanceOf[Assign]) =>
Apply(sel, args.mapConserve(assignToNamedArg))
case Tuple(args) =>
Apply(sel, arg :: Nil).setApplyKind(ApplyKind.InfixTuple)
case _ =>
Apply(sel, arg :: Nil)

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ object Trees {
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
case InfixTuple // r f (x1, ..., xN) where N != 1; 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)
Expand Down
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,12 @@ class SymUtils(val self: Symbol) extends AnyVal {
def isScalaStatic(using Context): Boolean =
self.hasAnnotation(ctx.definitions.ScalaStaticAnnot)

/** Is symbol assumed or declared as an infix symbol? */
def isDeclaredInfix(using Context): Boolean =
self.hasAnnotation(defn.InfixAnnot)
|| defn.isInfix(self)
|| self.name.isUnapplyName
&& self.owner.is(Module)
&& self.owner.linkedClass.is(Case)
&& self.owner.linkedClass.isDeclaredInfix
}
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ trait Applications extends Compatibility {
case arg :: args1 =>
val msg = arg match
case untpd.Tuple(Nil)
if applyKind == ApplyKind.InfixUnit && funType.widen.isNullaryMethod =>
if applyKind == ApplyKind.InfixTuple && 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"
Expand Down Expand Up @@ -862,14 +862,15 @@ trait Applications extends Compatibility {
record("typedApply")
val fun1 = typedFunPart(tree.fun, originalProto)

// Warning: The following lines are dirty and fragile. We record that auto-tupling was demanded as
// a side effect in adapt. If it was, we assume the tupled proto-type in the rest of the application,
// Warning: The following lines are dirty and fragile.
// We record that auto-tupling or untupling was demanded as a side effect in adapt.
// If it was, we assume the tupled-dual proto-type in the rest of the application,
// until, possibly, we have to fall back to insert an implicit on the qualifier.
// This crucially relies on he fact that `proto` is used only in a single call of `adapt`,
// otherwise we would get possible cross-talk between different `adapt` calls using the same
// prototype. A cleaner alternative would be to return a modified prototype from `adapt` together with
// a modified tree but this would be more convoluted and less efficient.
val proto = if (originalProto.isTupled) originalProto.tupled else originalProto
val proto = if (originalProto.hasTupledDual) originalProto.tupledDual else originalProto

// If some of the application's arguments are function literals without explicitly declared
// parameter types, relate the normalized result type of the application with the
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -810,21 +810,13 @@ trait Checking {
* operator is alphanumeric, it must be declared `@infix`.
*/
def checkValidInfix(tree: untpd.InfixOp, meth: Symbol)(using 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 id @ Ident(name: Name) =>
name.toTermName match {
case name: SimpleName
if !untpd.isBackquoted(id) &&
!name.isOperatorName &&
!isInfix(meth) &&
!meth.isDeclaredInfix &&
!meth.maybeOwner.is(Scala2x) &&
!infixOKSinceFollowedBy(tree.right) &&
sourceVersion.isAtLeast(`3.1`) =>
Expand Down
17 changes: 10 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ object ProtoTypes {
/** A map in which typed arguments can be stored to be later integrated in `typedArgs`. */
var typedArg: SimpleIdentityMap[untpd.Tree, Tree] = SimpleIdentityMap.Empty

/** The tupled version of this prototype, if it has been computed */
var tupled: Type = NoType
/** The tupled or untupled version of this prototype, if it has been computed */
var tupledDual: Type = NoType

/** If true, the application of this prototype was canceled. */
var toDrop: Boolean = false
Expand Down Expand Up @@ -348,16 +348,19 @@ object ProtoTypes {
}

/** The same proto-type but with all arguments combined in a single tuple */
def tupled: FunProto = state.tupled match {
def tupledDual: FunProto = state.tupledDual match {
case pt: FunProto =>
pt
case _ =>
state.tupled = new FunProto(untpd.Tuple(args) :: Nil, resultType)(typer, applyKind)
tupled
val dualArgs = args match
case untpd.Tuple(elems) :: Nil => elems
case _ => untpd.Tuple(args) :: Nil
state.tupledDual = new FunProto(dualArgs, resultType)(typer, applyKind)
tupledDual
}

/** Somebody called the `tupled` method of this prototype */
def isTupled: Boolean = state.tupled.isInstanceOf[FunProto]
/** Somebody called the `tupledDual` method of this prototype */
def hasTupledDual: Boolean = state.tupledDual.isInstanceOf[FunProto]

/** Cancel the application of this prototype. This can happen for a nullary
* application `f()` if `f` refers to a symbol that exists both in parameterless
Expand Down
29 changes: 23 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2930,14 +2930,31 @@ class Typer extends Namer
false
}

/** Should we tuple or untuple the argument before application?
* If auto-tupling is enabled then
*
* - we tuple n-ary arguments where n > 0 if the function consists
* only of unary alternatives
* - we untuple tuple arguments of infix operations if the function
* does not consist only of unary alternatives.
*/
def needsTupledDual(funType: Type, pt: FunProto): Boolean =
pt.args match
case untpd.Tuple(elems) :: Nil =>
elems.length > 1
&& pt.applyKind == ApplyKind.InfixTuple
&& !isUnary(funType)
case args =>
args.lengthCompare(1) > 0
&& isUnary(funType)
&& autoTuplingEnabled

def adaptToArgs(wtp: Type, pt: FunProto): Tree = wtp match {
case wtp: MethodOrPoly =>
def methodStr = methPart(tree).symbol.showLocated
if (matchingApply(wtp, pt))
if (pt.args.lengthCompare(1) > 0 && isUnary(wtp) && autoTuplingEnabled)
adapt(tree, pt.tupled, locked)
else
tree
if needsTupledDual(wtp, pt) then adapt(tree, pt.tupledDual, locked)
else tree
else if (wtp.isContextualMethod)
def isContextBoundParams = wtp.stripPoly match
case MethodType(EvidenceParamName(_) :: _) => true
Expand Down Expand Up @@ -3465,8 +3482,8 @@ class Typer extends Namer
case ref: TermRef =>
pt match {
case pt: FunProto
if pt.args.lengthCompare(1) > 0 && isUnary(ref) && autoTuplingEnabled =>
adapt(tree, pt.tupled, locked)
if needsTupledDual(ref, pt) && autoTuplingEnabled =>
adapt(tree, pt.tupledDual, locked)
case _ =>
adaptOverloaded(ref)
}
Expand Down