diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 29d6a5365978..66a83c66b5f8 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 19af6b02c25d..09daa429a08f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -959,7 +959,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) diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 339d1f2f7bc6..2da934c6013a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -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._ @@ -266,7 +267,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 diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 29a35ccbdac0..3872d0dd79d0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -75,7 +75,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 @@ -437,7 +437,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" @@ -842,8 +842,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 } @@ -1137,10 +1137,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 diff --git a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala index 70addd442100..a21a94aab271 100644 --- a/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala +++ b/compiler/src/dotty/tools/dotc/typer/ImportSuggestions.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index bde279c582e6..31cca0301d7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -19,6 +19,7 @@ import util.SourceFile import TypeComparer.necessarySubType import scala.annotation.internal.sharable +import dotty.tools.dotc.util.Spans.{NoSpan, Span} object ProtoTypes { @@ -165,7 +166,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 @@ -230,9 +231,9 @@ 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 @@ -240,14 +241,14 @@ object ProtoTypes { 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) @@ -268,12 +269,12 @@ 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 } } @@ -281,11 +282,11 @@ object ProtoTypes { /** 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 * @@ -295,12 +296,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 @@ -599,7 +600,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 _ => @@ -921,7 +922,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), diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 34940d2f5277..c18a369207a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -752,7 +752,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) = @@ -782,7 +782,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) @@ -3442,7 +3442,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 @@ -3467,7 +3467,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]() @@ -3897,7 +3897,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 diff --git a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala index 4869cdf0fa3b..2b49d2db3f08 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/definition/PcDefinitionSuite.scala @@ -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 <> = i.value % 2 == 1 + | + |val a = MyIntOut(1).un@@even + |""".stripMargin, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala index 6ed5d6c636e3..0ed40c6c537f 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/highlight/DocumentHighlightSuite.scala @@ -1143,3 +1143,34 @@ class DocumentHighlightSuite extends BaseDocumentHighlightSuite: | case MySome[<>](value: <>) extends MyOption[Int] |""".stripMargin, ) + + @Test def `implicit-extension` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <> = i.value % 2 == 1 + | + |val a = MyIntOut(1) + |val m = a.<> + |""".stripMargin, + ) + + @Test def `implicit-extension-2` = + check( + """|class MyIntOut(val value: Int) + |object MyIntOut: + | extension (i: MyIntOut) def <>(u: Int) = i.value % 2 == 1 + | + |val a = MyIntOut(1).<>(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, + ) diff --git a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala index 12c3a7be584f..984066fb7803 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/hover/HoverTypeSuite.scala @@ -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, + ) diff --git a/tests/semanticdb/expect/Extension.expect.scala b/tests/semanticdb/expect/Extension.expect.scala index b40e965d4885..f6f76b17b698 100644 --- a/tests/semanticdb/expect/Extension.expect.scala +++ b/tests/semanticdb/expect/Extension.expect.scala @@ -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().*/ diff --git a/tests/semanticdb/expect/Extension.scala b/tests/semanticdb/expect/Extension.scala index c204b1ff7fcc..76a012e4b758 100644 --- a/tests/semanticdb/expect/Extension.scala +++ b/tests/semanticdb/expect/Extension.scala @@ -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 diff --git a/tests/semanticdb/metac.expect b/tests/semanticdb/metac.expect index 00727163fda4..d8aa1e39abef 100644 --- a/tests/semanticdb/metac.expect +++ b/tests/semanticdb/metac.expect @@ -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 @@ -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)