Skip to content

Commit 1172795

Browse files
authored
Merge pull request #8308 from dotty-staging/change-infix-x
Imply @infix annotation for dotless extension methods
2 parents 558a3ef + 07e7f93 commit 1172795

File tree

10 files changed

+46
-205
lines changed

10 files changed

+46
-205
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
445445

446446
def rootDot(name: Name)(implicit src: SourceFile): Select = Select(Ident(nme.ROOTPKG), name)
447447
def scalaDot(name: Name)(implicit src: SourceFile): Select = Select(rootDot(nme.scala_), name)
448+
def scalaAnnotationDot(name: Name)(using SourceFile): Select = Select(scalaDot(nme.annotation), name)
448449
def scalaUnit(implicit src: SourceFile): Select = scalaDot(tpnme.Unit)
449450
def scalaAny(implicit src: SourceFile): Select = scalaDot(tpnme.Any)
450451
def javaDotLangDot(name: Name)(implicit src: SourceFile): Select = Select(Select(Ident(nme.java), nme.lang), name)

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ object NameOps {
7373
def isAnonymousFunctionName: Boolean = name.startsWith(str.ANON_FUN)
7474
def isUnapplyName: Boolean = name == nme.unapply || name == nme.unapplySeq
7575

76+
def isOperatorName: Boolean = name match
77+
case name: SimpleName => name.exists(isOperatorPart)
78+
case _ => false
79+
7680
/** Is name a variable name? */
7781
def isVariableName: Boolean = testSimple { n =>
7882
n.length > 0 && {

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ object StdNames {
486486
val implicitly: N = "implicitly"
487487
val in: N = "in"
488488
val inline: N = "inline"
489+
val infix: N = "infix"
489490
val info: N = "info"
490491
val inlinedEquals: N = "inlinedEquals"
491492
val internal: N = "internal"

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3205,24 +3205,32 @@ object Parsers {
32053205
makeConstructor(Nil, vparamss, rhs).withMods(mods).setComment(in.getDocComment(start))
32063206
}
32073207
else {
3208+
var mods1 = addFlag(mods, Method)
3209+
var isInfix = false
32083210
def extParamss() =
32093211
try paramClause(0, prefix = true) :: Nil
32103212
finally
3213+
mods1 = addFlag(mods, Extension)
32113214
if in.token == DOT then in.nextToken()
3212-
else newLineOpt()
3213-
val (leadingTparams, leadingVparamss, flags) =
3215+
else
3216+
isInfix = true
3217+
newLineOpt()
3218+
val (leadingTparams, leadingVparamss) =
32143219
if in.token == LBRACKET then
3215-
(typeParamClause(ParamOwner.Def), extParamss(), Method | Extension)
3220+
(typeParamClause(ParamOwner.Def), extParamss())
32163221
else if in.token == LPAREN then
3217-
(Nil, extParamss(), Method | Extension)
3222+
(Nil, extParamss())
32183223
else
3219-
(Nil, Nil, Method)
3220-
val mods1 = addFlag(mods, flags)
3224+
(Nil, Nil)
32213225
val ident = termIdent()
32223226
val name = ident.name.asTermName
3227+
if isInfix && !name.isOperatorName then
3228+
val infixAnnot = Apply(wrapNew(scalaAnnotationDot(tpnme.infix)), Nil)
3229+
.withSpan(Span(start, start))
3230+
mods1 = mods1.withAddedAnnotation(infixAnnot)
32233231
val tparams =
32243232
if in.token == LBRACKET then
3225-
if flags.is(Extension) then
3233+
if mods1.is(Extension) then
32263234
if leadingTparams.isEmpty then
32273235
deprecationWarning("type parameters in extension methods should be written after `def`")
32283236
else

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ object Checking {
277277
def checkValidOperator(sym: Symbol)(implicit ctx: Context): Unit =
278278
sym.name.toTermName match {
279279
case name: SimpleName
280-
if name.exists(isOperatorPart)
280+
if name.isOperatorName
281281
&& !name.isSetterName
282282
&& !name.isConstructorName
283283
&& !sym.getAnnotation(defn.AlphaAnnot).isDefined
@@ -754,7 +754,7 @@ trait Checking {
754754
name.toTermName match {
755755
case name: SimpleName
756756
if !untpd.isBackquoted(id) &&
757-
!name.exists(isOperatorPart) &&
757+
!name.isOperatorName &&
758758
!isInfix(meth) &&
759759
!meth.maybeOwner.is(Scala2x) &&
760760
!infixOKSinceFollowedBy(tree.right) &&

docs/docs/reference/contextual/extension-methods-new.md

Lines changed: 0 additions & 180 deletions
This file was deleted.

docs/docs/reference/contextual/extension-methods.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@ layout: doc-page
33
title: "Extension Methods"
44
---
55

6-
**Note:** The syntax of extension methods is about to change. Here is the
7-
[doc page with the new syntax](./extension-methods-new.html), supported from Dotty 0.22 onwards.
8-
96
Extension methods allow one to add methods to a type after the type is defined. Example:
107

118
```scala
129
case class Circle(x: Double, y: Double, radius: Double)
1310

14-
def (c: Circle) circumference: Double = c.radius * math.Pi * 2
11+
def (c: Circle).circumference: Double = c.radius * math.Pi * 2
1512
```
1613

1714
Like regular methods, extension methods can be invoked with infix `.`:
@@ -72,8 +69,8 @@ Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type ar
7269
and where `T` is the expected type. The following two rewritings are tried in order:
7370

7471
1. The selection is rewritten to `m[Ts](e)`.
75-
2. If the first rewriting does not typecheck with expected type `T`, and there is a given `g`
76-
in either the current scope or in the context scope of `T` such that `g` defines an extension
72+
2. If the first rewriting does not typecheck with expected type `T`, and there is a given instance `g`
73+
in either the current scope or in the context scope of `T`, and `g` defines an extension
7774
method named `m`, then selection is expanded to `g.m[Ts](e)`.
7875
This second rewriting is attempted at the time where the compiler also tries an implicit conversion
7976
from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results.
@@ -84,8 +81,7 @@ So `circle.circumference` translates to `CircleOps.circumference(circle)`, provi
8481
### Operators
8582

8683
The extension method syntax also applies to the definition of operators.
87-
In this case it is allowed and preferable to omit the period between the leading parameter list
88-
and the operator. In each case the definition syntax mirrors the way the operator is applied.
84+
This case is indicated by omitting the period between the leading parameter list and the operator. In each case the definition syntax mirrors the way the operator is applied.
8985
Examples:
9086
```scala
9187
def (x: String) < (y: String) = ...
@@ -96,6 +92,8 @@ def (x: Number) min (y: Number) = ...
9692
1 +: List(2, 3)
9793
x min 3
9894
```
95+
For alphanumeric extension operators like `min` an `@infix` annotation is implied.
96+
9997
The three definitions above translate to
10098
```scala
10199
def < (x: String)(y: String) = ...
@@ -106,6 +104,7 @@ Note the swap of the two parameters `x` and `xs` when translating
106104
the right-binding operator `+:` to an extension method. This is analogous
107105
to the implementation of right binding operators as normal methods.
108106

107+
109108
### Generic Extensions
110109

111110
The `StringSeqOps` examples extended a specific instance of a generic type. It is also possible to extend a generic type by adding type parameters to an extension method. Examples:
@@ -170,15 +169,14 @@ given extension_largest_List_T as AnyRef {
170169
### Syntax
171170

172171
Here are the syntax changes for extension methods and collective extensions relative
173-
to the [current syntax](../../internals/syntax.md). `extension` is a soft keyword, recognized only
174-
in tandem with `on`. It can be used as an identifier everywhere else.
172+
to the [current syntax](../../internals/syntax.md). `extension` is a soft keyword, recognized only in tandem with `on`. It can be used as an identifier everywhere else.
173+
175174
```
176175
DefSig ::= ...
177176
| ExtParamClause [nl] [‘.’] id DefParamClauses
178177
ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’
179178
TmplDef ::= ...
180179
| ‘extension’ ExtensionDef
181-
ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ‘with’ ExtMethods
180+
ExtensionDef ::= [id] ‘on’ ExtParamClause {GivenParamClause} ExtMethods
182181
ExtMethods ::= ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’
183182
```
184-

docs/docs/reference/contextual/typeclasses.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ with canonical implementations defined by given instances. Here are some example
1111

1212
```scala
1313
trait SemiGroup[T] {
14-
@infix def (x: T) combine (y: T): T
14+
def (x: T) combine (y: T): T
1515
}
1616

1717
trait Monoid[T] extends SemiGroup[T] {

tests/neg-custom-args/infix.scala

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
// Compile with -strict -Xfatal-warnings -deprecation
22
import scala.annotation.infix
3-
class C {
3+
class C:
44
@infix def op(x: Int): Int = ???
55
def meth(x: Int): Int = ???
66
def matching(x: Int => Int) = ???
77
def +(x: Int): Int = ???
8-
}
8+
9+
object C:
10+
given AnyRef:
11+
def (x: C) iop (y: Int) = ???
12+
def (x: C).mop (y: Int) = ???
13+
def (x: C) ++ (y: Int) = ???
914

1015
val c = C()
1116
def test() = {
1217
c op 2
18+
c iop 2
1319
c.meth(2)
20+
c ++ 2
1421

1522
c.op(2)
23+
c.iop(2)
24+
c mop 2 // error: should not be used as infix operator
1625
c meth 2 // error: should not be used as infix operator
1726
c `meth` 2 // OK, sincd `meth` is backquoted
1827
c + 3 // OK, since `+` is symbolic

tests/printing/i620.check

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package O {
1111
protected[A] def f: Int = 0
1212
def g: Int = 0
1313
def g1(t: Int): Int = 0
14-
def (c: D.this.C) g1: Int = 0
14+
@_root_.scala.annotation.infix() def (c: D.this.C) g1: Int = 0
1515
}
1616
private[D] class E() extends Object() {}
1717
private[this] class F() extends Object() {}

0 commit comments

Comments
 (0)