Skip to content

Commit

Permalink
Add missing span to extension method select (scala#18557)
Browse files Browse the repository at this point in the history
For:
```Demo.scala
class MyIntOut(val value: Int)
object MyIntOut:
  extension (i: MyIntOut) def uneven = i.value % 2 == 1

val a = MyIntOut(1).uneven
```
`uneven` call in the typed tree has span `<126..126>`, where it should
be `<138..144>`.

connected to: scalameta/metals#5630

I'm open to suggestions on how to do it nicer.
  • Loading branch information
bishabosha authored Sep 20, 2023
2 parents a37dac6 + fe4a685 commit 2486191
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 37 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/inlines/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ class Inliner(val call: tpd.Tree)(using Context):

override def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = {
val locked = ctx.typerState.ownedVars
val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
val qual1 = typed(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
val resNoReduce = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
val reducedProjection = reducer.reduceProjection(resNoReduce)
if reducedProjection.isType then
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -976,7 +976,7 @@ trait Applications extends Compatibility {
val resultType =
if !originalResultType.isRef(defn.ObjectClass) then originalResultType
else AvoidWildcardsMap()(proto.resultType.deepenProtoTrans) match
case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _) => resTp
case SelectionProto(nme.asInstanceOf_, PolyProto(_, resTp), _, _, _) => resTp
case resTp if isFullyDefined(resTp, ForceDegree.all) => resTp
case _ => defn.ObjectType
val methType = MethodType(proto.typedArgs().map(_.tpe.widen), resultType)
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Types._, ProtoTypes._, Contexts._, Decorators._, Denotations._, Symbols._
import Implicits._, Flags._, Constants.Constant
import Trees._
import NameOps._
import util.Spans.NoSpan
import util.SrcPos
import config.Feature
import reporting._
Expand Down Expand Up @@ -275,7 +276,7 @@ object ErrorReporting {
else
val add = suggestImports(
ViewProto(qualType.widen,
SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false)))
SelectionProto(tree.name, WildcardType, NoViewsAllowed, privateOK = false, NoSpan)))
if add.isEmpty then ""
else ", but could be made available as an extension method." ++ add
end selectErrorAddendum
Expand Down
12 changes: 6 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object Implicits:
* method with the selecting name? False otherwise.
*/
def hasExtMethod(tp: Type, expected: Type)(using Context) = expected match
case selProto @ SelectionProto(selName: TermName, _, _, _) =>
case selProto @ SelectionProto(selName: TermName, _, _, _, _) =>
tp.memberBasedOnFlags(selName, required = ExtensionMethod).exists
case _ =>
false
Expand Down Expand Up @@ -454,7 +454,7 @@ object Implicits:
def clarify(tp: Type)(using Context): Type = tp

final protected def qualify(using Context): String = expectedType match {
case SelectionProto(name, mproto, _, _) if !argument.isEmpty =>
case SelectionProto(name, mproto, _, _, _) if !argument.isEmpty =>
i"provide an extension method `$name` on ${argument.tpe}"
case NoType =>
if (argument.isEmpty) i"match expected type"
Expand Down Expand Up @@ -866,8 +866,8 @@ trait Implicits:
NoMatchingImplicitsFailure
else {
def adjust(to: Type) = to.stripTypeVar.widenExpr match {
case SelectionProto(name, memberProto, compat, true) =>
SelectionProto(name, memberProto, compat, privateOK = false)
case SelectionProto(name, memberProto, compat, true, nameSpan) =>
SelectionProto(name, memberProto, compat, privateOK = false, nameSpan)
case tp => tp
}

Expand Down Expand Up @@ -1161,10 +1161,10 @@ trait Implicits:
pt, locked)
}
pt match
case selProto @ SelectionProto(selName: TermName, mbrType, _, _) =>
case selProto @ SelectionProto(selName: TermName, mbrType, _, _, nameSpan) =>

def tryExtension(using Context) =
extMethodApply(untpd.Select(untpdGenerated, selName), argument, mbrType)
extMethodApply(untpd.Select(untpdGenerated, selName).withSpan(nameSpan), argument, mbrType)

def tryConversionForSelection(using Context) =
val converted = tryConversion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ trait ImportSuggestions:
// don't suggest things that are imported by default

def extensionImports = pt match
case ViewProto(argType, SelectionProto(name: TermName, _, _, _)) =>
case ViewProto(argType, SelectionProto(name: TermName, _, _, _, _)) =>
roots.flatMap(extensionMethod(_, name, argType))
case _ =>
Nil
Expand Down
39 changes: 20 additions & 19 deletions compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import dotty.tools.dotc.core.Flags.Transparent
import dotty.tools.dotc.config.{ Feature, SourceVersion }

import scala.annotation.internal.sharable
import dotty.tools.dotc.util.Spans.{NoSpan, Span}

object ProtoTypes {

Expand Down Expand Up @@ -180,7 +181,7 @@ object ProtoTypes {
*
* [ ].name: proto
*/
abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)
extends CachedProxyType with ProtoType with ValueTypeOrProto {

/** Is the set of members of this type unknown, in the sense that we
Expand Down Expand Up @@ -243,24 +244,24 @@ object ProtoTypes {

def underlying(using Context): Type = WildcardType

def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility)(using Context): SelectionProto =
if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat)) this
else SelectionProto(name, memberProto, compat, privateOK)
def derivedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, nameSpan: Span)(using Context): SelectionProto =
if ((name eq this.name) && (memberProto eq this.memberProto) && (compat eq this.compat) && (nameSpan == this.nameSpan)) this
else SelectionProto(name, memberProto, compat, privateOK, nameSpan)

override def isErroneous(using Context): Boolean =
memberProto.isErroneous

override def unusableForInference(using Context): Boolean =
memberProto.unusableForInference

def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat)
def map(tm: TypeMap)(using Context): SelectionProto = derivedSelectionProto(name, tm(memberProto), compat, nameSpan)
def fold[T](x: T, ta: TypeAccumulator[T])(using Context): T = ta(x, memberProto)

override def deepenProto(using Context): SelectionProto =
derivedSelectionProto(name, memberProto.deepenProto, compat)
derivedSelectionProto(name, memberProto.deepenProto, compat, nameSpan)

override def deepenProtoTrans(using Context): SelectionProto =
derivedSelectionProto(name, memberProto.deepenProtoTrans, compat)
derivedSelectionProto(name, memberProto.deepenProtoTrans, compat, nameSpan)

override def computeHash(bs: Hashable.Binders): Int = {
val delta = (if (compat eq NoViewsAllowed) 1 else 0) | (if (privateOK) 2 else 0)
Expand All @@ -281,24 +282,24 @@ object ProtoTypes {
}
}

class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)
extends SelectionProto(name, memberProto, compat, privateOK)
class CachedSelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)
extends SelectionProto(name, memberProto, compat, privateOK, nameSpan)

object SelectionProto {
def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean)(using Context): SelectionProto = {
val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK)
def apply(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean, nameSpan: Span)(using Context): SelectionProto = {
val selproto = new CachedSelectionProto(name, memberProto, compat, privateOK, nameSpan)
if (compat eq NoViewsAllowed) unique(selproto) else selproto
}
}

/** Create a selection proto-type, but only one level deep;
* treat constructors specially
*/
def shallowSelectionProto(name: Name, tp: Type, typer: Typer)(using Context): TermType =
def shallowSelectionProto(name: Name, tp: Type, typer: Typer, nameSpan: Span)(using Context): TermType =
if (name.isConstructorName) WildcardType
else tp match
case tp: UnapplyFunProto => new UnapplySelectionProto(name)
case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true)
case tp: UnapplyFunProto => new UnapplySelectionProto(name, nameSpan)
case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true, nameSpan)

/** A prototype for expressions [] that are in some unspecified selection operation
*
Expand All @@ -308,12 +309,12 @@ object ProtoTypes {
* operation is further selection. In this case, the expression need not be a value.
* @see checkValue
*/
@sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true)
@sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan)

@sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true)
@sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan)

/** A prototype for selections in pattern constructors */
class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed, true)
class UnapplySelectionProto(name: Name, nameSpan: Span) extends SelectionProto(name, WildcardType, NoViewsAllowed, true, nameSpan)

trait ApplyingProto extends ProtoType // common trait of ViewProto and FunProto
trait FunOrPolyProto extends ProtoType: // common trait of PolyProto and FunProto
Expand Down Expand Up @@ -612,7 +613,7 @@ object ProtoTypes {
def isMatchedBy(tp: Type, keepConstraint: Boolean)(using Context): Boolean =
ctx.typer.isApplicableType(tp, argType :: Nil, resultType) || {
resType match {
case selProto @ SelectionProto(selName: TermName, mbrType, _, _) =>
case selProto @ SelectionProto(selName: TermName, mbrType, _, _, _) =>
ctx.typer.hasExtensionMethodNamed(tp, selName, argType, mbrType)
//.reporting(i"has ext $tp $name $argType $mbrType: $result")
case _ =>
Expand Down Expand Up @@ -934,7 +935,7 @@ object ProtoTypes {
}
approxOr
case tp: SelectionProto =>
tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed)
tp.derivedSelectionProto(tp.name, wildApprox(tp.memberProto, theMap, seen, internal), NoViewsAllowed, tp.nameSpan)
case tp: ViewProto =>
tp.derivedViewProto(
wildApprox(tp.argType, theMap, seen, internal),
Expand Down
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
record("typedSelect")

def typeSelectOnTerm(using Context): Tree =
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
typedSelect(tree, pt, qual).withSpan(tree.span).computeNullable()

def javaSelectOnType(qual: Tree)(using Context) =
Expand Down Expand Up @@ -790,7 +790,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
tryAlternatively(typeSelectOnTerm)(fallBack)

if (tree.qualifier.isType) {
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this))
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
}
else if (ctx.isJava && tree.name.isTypeName)
Expand Down Expand Up @@ -3513,7 +3513,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
then
Some(adapt(tree, pt, locked))
else
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false)
val selProto = SelectionProto(name, pt, NoViewsAllowed, privateOK = false, tree.nameSpan)
if selProto.isMatchedBy(qual.tpe) || tree.hasAttachment(InsertedImplicitOnQualifier) then
None
else
Expand All @@ -3538,7 +3538,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
(tree: untpd.Select, pt: Type, mbrProto: Type, qual: Tree, locked: TypeVars, compat: Compatibility, inSelect: Boolean)
(using Context): Tree =

def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect)
def selectionProto = SelectionProto(tree.name, mbrProto, compat, privateOK = inSelect, tree.nameSpan)

def tryExtension(using Context): Tree =
val altImports = new mutable.ListBuffer[TermRef]()
Expand Down Expand Up @@ -3968,7 +3968,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* function prototype `(...)R`. Otherwise `pt`.
*/
def ptWithoutRedundantApply: Type = pt.revealIgnored match
case SelectionProto(nme.apply, mpt, _, _) =>
case SelectionProto(nme.apply, mpt, _, _, _) =>
mpt.revealIgnored match
case fpt: FunProto => fpt
case _ => pt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,13 @@ class PcDefinitionSuite extends BasePcDefinitionSuite:
|
|""".stripMargin
)

@Test def `implicit-extension` =
check(
"""|class MyIntOut(val value: Int)
|object MyIntOut:
| extension (i: MyIntOut) def <<uneven>> = i.value % 2 == 1
|
|val a = MyIntOut(1).un@@even
|""".stripMargin,
)
Original file line number Diff line number Diff line change
Expand Up @@ -1143,3 +1143,34 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite:
| case MySome[<<AA>>](value: <<A@@A>>) extends MyOption[Int]
|""".stripMargin,
)

@Test def `implicit-extension` =
check(
"""|class MyIntOut(val value: Int)
|object MyIntOut:
| extension (i: MyIntOut) def <<uneven>> = i.value % 2 == 1
|
|val a = MyIntOut(1)
|val m = a.<<un@@even>>
|""".stripMargin,
)

@Test def `implicit-extension-2` =
check(
"""|class MyIntOut(val value: Int)
|object MyIntOut:
| extension (i: MyIntOut) def <<uneven>>(u: Int) = i.value % 2 == 1
|
|val a = MyIntOut(1).<<un@@even>>(3)
|""".stripMargin,
)

@Test def `implicit-extension-infix` =
check(
"""|class MyIntOut(val value: Int)
|object MyIntOut:
| extension (i: MyIntOut) def <<++>>(u: Int) = i.value + u
|
|val a = MyIntOut(1) <<+@@+>> 3
|""".stripMargin,
)
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,15 @@ class HoverTypeSuite extends BaseHoverSuite:
"""|val ddd: Int
|""".stripMargin.hover,
)

@Test def `infix-extension` =
check(
"""|class MyIntOut(val value: Int)
|object MyIntOut:
| extension (i: MyIntOut) def uneven = i.value % 2 == 1
|
|val a = MyIntOut(1).un@@even
|""".stripMargin,
"""|extension (i: MyIntOut) def uneven: Boolean
|""".stripMargin.hover,
)
9 changes: 9 additions & 0 deletions tests/semanticdb/expect/Extension.expect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ extension (s/*<-ext::Extension$package.readInto().(s)*/: String/*->scala::Predef

trait Functor/*<-ext::Functor#*/[F/*<-ext::Functor#[F]*/[_]]:
extension [T/*<-ext::Functor#map().[T]*/](t/*<-ext::Functor#map().(t)*/: F/*->ext::Functor#[F]*/[T/*->ext::Functor#map().[T]*/]) def map/*<-ext::Functor#map().*/[U/*<-ext::Functor#map().[U]*/](f/*<-ext::Functor#map().(f)*/: T/*->ext::Functor#map().[T]*/ => U/*->ext::Functor#map().[U]*/): F/*->ext::Functor#[F]*/[U/*->ext::Functor#map().[U]*/]

opaque type Deck/*<-ext::Extension$package.Deck#*/ = Long/*->scala::Long#*/
object Deck/*<-ext::Extension$package.Deck.*/:
extension (data/*<-ext::Extension$package.Deck.fooSize().(data)*/: Deck/*->ext::Extension$package.Deck#*/)
def fooSize/*<-ext::Extension$package.Deck.fooSize().*/: Int/*->scala::Int#*/ = ???/*->scala::Predef.`???`().*/

object DeckUsage/*<-ext::DeckUsage.*/:
val deck/*<-ext::DeckUsage.deck.*/: Deck/*->ext::Extension$package.Deck#*/ = ???/*->scala::Predef.`???`().*/
deck/*->ext::DeckUsage.deck.*/.fooSize/*->ext::Extension$package.Deck.fooSize().*/
9 changes: 9 additions & 0 deletions tests/semanticdb/expect/Extension.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ extension (s: String)

trait Functor[F[_]]:
extension [T](t: F[T]) def map[U](f: T => U): F[U]

opaque type Deck = Long
object Deck:
extension (data: Deck)
def fooSize: Int = ???

object DeckUsage:
val deck: Deck = ???
deck.fooSize
26 changes: 23 additions & 3 deletions tests/semanticdb/metac.expect
Original file line number Diff line number Diff line change
Expand Up @@ -1450,12 +1450,18 @@ Schema => SemanticDB v4
Uri => Extension.scala
Text => empty
Language => Scala
Symbols => 26 entries
Occurrences => 52 entries
Symbols => 32 entries
Occurrences => 66 entries
Synthetics => 1 entries

Symbols:
ext/Extension$package. => final package object ext extends Object { self: ext.type => +6 decls }
ext/DeckUsage. => final object DeckUsage extends Object { self: DeckUsage.type => +2 decls }
ext/DeckUsage.deck. => val method deck Deck
ext/Extension$package. => final package object ext extends Object { self: ext.type { opaque type Deck } => +9 decls }
ext/Extension$package.Deck# => opaque type Deck
ext/Extension$package.Deck. => final object Deck extends Object { self: Deck.type => +2 decls }
ext/Extension$package.Deck.fooSize(). => method fooSize (param data: Deck): Int
ext/Extension$package.Deck.fooSize().(data) => param data: Deck
ext/Extension$package.`#*#`(). => method #*# (param s: String)(param i: Int): Tuple2[String, Int]
ext/Extension$package.`#*#`().(i) => param i: Int
ext/Extension$package.`#*#`().(s) => param s: String
Expand Down Expand Up @@ -1535,6 +1541,20 @@ Occurrences:
[17:44..17:45): U -> ext/Functor#map().[U]
[17:48..17:49): F -> ext/Functor#[F]
[17:50..17:51): U -> ext/Functor#map().[U]
[19:12..19:16): Deck <- ext/Extension$package.Deck#
[19:19..19:23): Long -> scala/Long#
[20:7..20:11): Deck <- ext/Extension$package.Deck.
[21:13..21:17): data <- ext/Extension$package.Deck.fooSize().(data)
[21:19..21:23): Deck -> ext/Extension$package.Deck#
[22:8..22:15): fooSize <- ext/Extension$package.Deck.fooSize().
[22:17..22:20): Int -> scala/Int#
[22:23..22:26): ??? -> scala/Predef.`???`().
[24:7..24:16): DeckUsage <- ext/DeckUsage.
[25:6..25:10): deck <- ext/DeckUsage.deck.
[25:12..25:16): Deck -> ext/Extension$package.Deck#
[25:19..25:22): ??? -> scala/Predef.`???`().
[26:2..26:6): deck -> ext/DeckUsage.deck.
[26:7..26:14): fooSize -> ext/Extension$package.Deck.fooSize().

Synthetics:
[14:46..14:61):summon[Read[T]] => *(x$2)
Expand Down

0 comments on commit 2486191

Please sign in to comment.