From 537aee774621fb301bd683bbf935a97338dc43c3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 23 Dec 2020 15:44:09 +0100 Subject: [PATCH 1/9] Fix #10810: Better error diagnostics for extension methods We gave misleading diagnostic before in the case where no extension method was found since a TypeError was thrown. --- .../tools/dotc/typer/ErrorReporting.scala | 22 ++++++++---- .../dotty/tools/dotc/typer/Implicits.scala | 10 ++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 35 +++++++++++-------- tests/neg/i10810.check | 7 ++++ tests/neg/i10810.scala | 11 ++++++ 5 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 tests/neg/i10810.check create mode 100644 tests/neg/i10810.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 9db5d7dbf697..f273b27972e3 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -13,6 +13,7 @@ import util.SrcPos import config.Feature import java.util.regex.Matcher.quoteReplacement import reporting._ +import collection.mutable import scala.util.matching.Regex @@ -143,16 +144,20 @@ object ErrorReporting { def selectErrorAddendum (tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String) (using Context): String = - val attempts: List[Tree] = qual1.getAttachment(Typer.HiddenSearchFailure) match + val attempts = mutable.ListBuffer[Tree]() + val nested = mutable.ListBuffer[NestedFailure]() + qual1.getAttachment(Typer.HiddenSearchFailure) match case Some(failures) => - for failure <- failures - if !failure.reason.isInstanceOf[Implicits.NoMatchingImplicits] - yield failure.tree - case _ => Nil + for failure <- failures do + failure.reason match + case fail: NestedFailure => nested += fail + case fail: Implicits.NoMatchingImplicits => // do nothing + case _ => attempts += failure.tree + case _ => if qualType.derivesFrom(defn.DynamicClass) then "\npossible cause: maybe a wrong Dynamic method signature?" else if attempts.nonEmpty then - val attemptStrings = attempts.map(_.showIndented(4)).distinct + val attemptStrings = attempts.toList.map(_.showIndented(4)).distinct val extMethods = if attemptStrings.length > 1 then "Extension methods were" else "An extension method was" @@ -160,6 +165,11 @@ object ErrorReporting { |$extMethods tried, but could not be fully constructed: | | $attemptStrings%\nor\n %""" + else if nested.nonEmpty then + i""". + |Extension methods were tried, but the search failed with: + | + | ${nested.head.explanation}""" else if tree.hasAttachment(desugar.MultiLineInfix) then i""". |Note that `${tree.name}` is treated as an infix operator in Scala 3. diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index bdf1f03f2e2e..84904229408b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -513,9 +513,19 @@ object Implicits: em"${err.refStr(ref)} produces a diverging implicit search when trying to $qualify" } + /** A search failure type for attempted ill-typed extension method calls */ class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType: def argument = EmptyTree def explanation(using Context) = em"$extApp does not $qualify" + + /** A search failure type for aborted searches of extension methods, typically + * because of a cyclic reference or similar. + */ + class NestedFailure(_msg: Message, val expectedType: Type) extends SearchFailureType: + def argument = EmptyTree + override def msg(using Context) = _msg + def explanation(using Context) = msg.toString + end Implicits import Implicits._ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8b0034510bc6..c239bc3c9161 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3499,24 +3499,29 @@ class Typer extends Namer // try an extension method in scope pt match { case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => + def tryExtension(using Context): Tree = - try - findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match + findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match + case ref: TermRef => + extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) + case _ => findRef(selProto.extensionName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match case ref: TermRef => extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) - case _ => findRef(selProto.extensionName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match - case ref: TermRef => - extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) - case _ => EmptyTree - catch case ex: TypeError => errorTree(tree, ex, tree.srcPos) - val nestedCtx = ctx.fresh.setNewTyperState() - val app = tryExtension(using nestedCtx) - if (!app.isEmpty && !nestedCtx.reporter.hasErrors) { - nestedCtx.typerState.commit() - return ExtMethodApply(app) - } - else if !app.isEmpty then - rememberSearchFailure(tree, SearchFailure(app.withType(FailedExtension(app, pt)))) + case _ => EmptyTree + + try + val nestedCtx = ctx.fresh.setNewTyperState() + val app = tryExtension(using nestedCtx) + if !app.isEmpty then + if !nestedCtx.reporter.hasErrors then + nestedCtx.typerState.commit() + return ExtMethodApply(app) + else + rememberSearchFailure(tree, + SearchFailure(app.withType(FailedExtension(app, pt)))) + catch case ex: TypeError => + rememberSearchFailure(tree, + SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt)))) case _ => } diff --git a/tests/neg/i10810.check b/tests/neg/i10810.check new file mode 100644 index 000000000000..c862d3627ca2 --- /dev/null +++ b/tests/neg/i10810.check @@ -0,0 +1,7 @@ +-- [E008] Not Found Error: tests/neg/i10810.scala:10:16 ---------------------------------------------------------------- +10 | def x = b.a.x // error + | ^^^^^ + | value x is not a member of A. + | Extension methods were tried, but the search failed with: + | + | Overloaded or recursive method x needs return type diff --git a/tests/neg/i10810.scala b/tests/neg/i10810.scala new file mode 100644 index 000000000000..502232dc1088 --- /dev/null +++ b/tests/neg/i10810.scala @@ -0,0 +1,11 @@ +final case class A() +final case class B(a:A) + +object Test: + + extension(a:A) + def x = 5 + + extension(b:B) + def x = b.a.x // error + From 5618836d541b01505e76badaf6a9a90ab2b12848 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 23 Dec 2020 18:50:46 +0100 Subject: [PATCH 2/9] Fix #10901: Explain why attempted extension methods could not be typed We now also show with each tried extension method call that failed type checking the first error message that indicates the failure. --- .../dotty/tools/dotc/core/Decorators.scala | 4 ++ .../tools/dotc/reporting/ErrorMessageID.scala | 3 +- .../dotty/tools/dotc/reporting/messages.scala | 5 ++ .../dotty/tools/dotc/typer/Applications.scala | 3 +- .../tools/dotc/typer/ErrorReporting.scala | 32 ++++++---- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Typer.scala | 14 +++-- tests/neg/enum-values.check | 20 ++++-- tests/neg/i10901.check | 41 ++++++++++++ tests/neg/i10901.scala | 63 +++++++++++++++++++ tests/neg/i6183.check | 7 ++- tests/neg/i9185.check | 9 ++- 12 files changed, 174 insertions(+), 29 deletions(-) create mode 100644 tests/neg/i10901.check create mode 100644 tests/neg/i10901.scala diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index ad00756769d9..809b76ba3118 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -56,6 +56,10 @@ object Decorators { termName(chars, 0, len) case name: TypeName => s.concat(name.toTermName) case _ => termName(s.concat(name.toString)) + + def indented(width: Int): String = + val padding = " " * width + padding + s.replace("\n", "\n" + padding) end extension /** Implements a findSymbol method on iterators of Symbols that diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 20becf1cce35..96c7432ab9f5 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -168,7 +168,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { CannotExtendJavaEnumID, InvalidReferenceInImplicitNotFoundAnnotationID, TraitMayNotDefineNativeMethodID, - JavaEnumParentArgsID + JavaEnumParentArgsID, + NotAnExtensionMethodID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index eca28faaacc8..f6d43049a2a9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2402,6 +2402,11 @@ import transform.SymUtils._ |""".stripMargin } + class NotAnExtensionMethod(methodRef: untpd.Tree)(using Context) + extends TypeMsg(NotAnExtensionMethodID): + def msg = em"not an extension method: $methodRef" + def explain = "" + class NoExtensionMethodAllowed(mdef: untpd.DefDef)(using Context) extends SyntaxMsg(NoExtensionMethodAllowedID) { def msg = em"No extension method allowed here, since collective parameters are given" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 10f770f6ce42..de18a2e2648e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2163,8 +2163,7 @@ trait Applications extends Compatibility { case tree @ Select(qual, nme.apply) => tree.symbol.is(ExtensionMethod) || isExtension(qual) case tree => tree.symbol.is(ExtensionMethod) } - if (!isExtension(app)) - report.error(em"not an extension method: $methodRef", receiver.srcPos) + if !isExtension(app) then report.error(NotAnExtensionMethod(methodRef), receiver.srcPos) app } diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index f273b27972e3..89bf9d6661a2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -144,27 +144,35 @@ object ErrorReporting { def selectErrorAddendum (tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String) (using Context): String = - val attempts = mutable.ListBuffer[Tree]() + + val attempts = mutable.ListBuffer[(Tree, FailedExtension)]() val nested = mutable.ListBuffer[NestedFailure]() - qual1.getAttachment(Typer.HiddenSearchFailure) match - case Some(failures) => - for failure <- failures do - failure.reason match - case fail: NestedFailure => nested += fail - case fail: Implicits.NoMatchingImplicits => // do nothing - case _ => attempts += failure.tree - case _ => + for + failures <- qual1.getAttachment(Typer.HiddenSearchFailure) + failure <- failures + do + failure.reason match + case fail: NestedFailure => nested += fail + case fail: FailedExtension => attempts += ((failure.tree, fail)) + case _ => if qualType.derivesFrom(defn.DynamicClass) then "\npossible cause: maybe a wrong Dynamic method signature?" else if attempts.nonEmpty then - val attemptStrings = attempts.toList.map(_.showIndented(4)).distinct + val attemptStrings = + attempts.toList + .map((tree, whyFailed) => (tree.showIndented(4), whyFailed)) + .distinctBy(_._1) + .map((treeStr, whyFailed) => + i""" + | $treeStr failed with + | + |${whyFailed.whyFailed.message.indented(8)}""") val extMethods = if attemptStrings.length > 1 then "Extension methods were" else "An extension method was" i""". |$extMethods tried, but could not be fully constructed: - | - | $attemptStrings%\nor\n %""" + |$attemptStrings%\n%""" else if nested.nonEmpty then i""". |Extension methods were tried, but the search failed with: diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 84904229408b..1f5973f82e16 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -514,7 +514,7 @@ object Implicits: } /** A search failure type for attempted ill-typed extension method calls */ - class FailedExtension(extApp: Tree, val expectedType: Type) extends SearchFailureType: + class FailedExtension(extApp: Tree, val expectedType: Type, val whyFailed: Message) extends SearchFailureType: def argument = EmptyTree def explanation(using Context) = em"$extApp does not $qualify" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index c239bc3c9161..80fd8a9e70a2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3513,12 +3513,14 @@ class Typer extends Namer val nestedCtx = ctx.fresh.setNewTyperState() val app = tryExtension(using nestedCtx) if !app.isEmpty then - if !nestedCtx.reporter.hasErrors then - nestedCtx.typerState.commit() - return ExtMethodApply(app) - else - rememberSearchFailure(tree, - SearchFailure(app.withType(FailedExtension(app, pt)))) + nestedCtx.reporter.allErrors + .filterNot(_.msg.isInstanceOf[NotAnExtensionMethod]) match + case Nil => + nestedCtx.typerState.commit() + return ExtMethodApply(app) + case err :: _ => + rememberSearchFailure(tree, + SearchFailure(app.withType(FailedExtension(app, pt, err.msg)))) catch case ex: TypeError => rememberSearchFailure(tree, SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt)))) diff --git a/tests/neg/enum-values.check b/tests/neg/enum-values.check index 3d1f3e84b805..3b22071aa808 100644 --- a/tests/neg/enum-values.check +++ b/tests/neg/enum-values.check @@ -6,7 +6,10 @@ | meaning a values array is not defined. | An extension method was tried, but could not be fully constructed: | - | example.Extensions.values(Tag) + | example.Extensions.values(Tag) failed with + | + | Found: example.Tag.type + | Required: Nothing -- [E008] Not Found Error: tests/neg/enum-values.scala:33:50 ----------------------------------------------------------- 33 | val listlikes: Array[ListLike[?]] = ListLike.values // error | ^^^^^^^^^^^^^^^ @@ -15,7 +18,10 @@ | meaning a values array is not defined. | An extension method was tried, but could not be fully constructed: | - | example.Extensions.values(ListLike) + | example.Extensions.values(ListLike) failed with + | + | Found: example.ListLike.type + | Required: Nothing -- [E008] Not Found Error: tests/neg/enum-values.scala:34:52 ----------------------------------------------------------- 34 | val typeCtorsK: Array[TypeCtorsK[?]] = TypeCtorsK.values // error | ^^^^^^^^^^^^^^^^^ @@ -24,7 +30,10 @@ | meaning a values array is not defined. | An extension method was tried, but could not be fully constructed: | - | example.Extensions.values(TypeCtorsK) + | example.Extensions.values(TypeCtorsK) failed with + | + | Found: example.TypeCtorsK.type + | Required: Nothing -- [E008] Not Found Error: tests/neg/enum-values.scala:36:6 ------------------------------------------------------------ 36 | Tag.valueOf("Int") // error | ^^^^^^^^^^^ @@ -54,7 +63,10 @@ | value values is not a member of object example.NotAnEnum. | An extension method was tried, but could not be fully constructed: | - | example.Extensions.values(NotAnEnum) + | example.Extensions.values(NotAnEnum) failed with + | + | Found: example.NotAnEnum.type + | Required: Nothing -- [E008] Not Found Error: tests/neg/enum-values.scala:41:12 ----------------------------------------------------------- 41 | NotAnEnum.valueOf("Foo") // error | ^^^^^^^^^^^^^^^^^ diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check new file mode 100644 index 000000000000..b11f7952bea7 --- /dev/null +++ b/tests/neg/i10901.check @@ -0,0 +1,41 @@ +-- [E008] Not Found Error: tests/neg/i10901.scala:45:38 ---------------------------------------------------------------- +45 | val pos1: Point2D[Int,Double] = x º y // error + | ^^^ + | value º is not a member of object BugExp4Point2D.IntT. + | An extension method was tried, but could not be fully constructed: + | + | º(x) failed with + | + | Ambiguous overload. The overloaded alternatives of method º in object dsl with types + | [T1, T2] + | (x: BugExp4Point2D.ColumnType[T1]) + | (y: BugExp4Point2D.ColumnType[T2]) + | (implicit evidence$7: Numeric[T1], evidence$8: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] + | [T1, T2] + | (x: T1) + | (y: BugExp4Point2D.ColumnType[T2]) + | (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] + | both match arguments ((x : BugExp4Point2D.IntT.type)) +-- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ---------------------------------------------------------------- +48 | val pos4: Point2D[Int,Double] = x º 201.1 // error + | ^^^ + |value º is not a member of object BugExp4Point2D.IntT. + |An extension method was tried, but could not be fully constructed: + | + | º(x) failed with + | + | Ambiguous overload. The overloaded alternatives of method º in object dsl with types + | [T1, T2] + | (x: BugExp4Point2D.ColumnType[T1]) + | (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] + | [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] + | both match arguments ((x : BugExp4Point2D.IntT.type)) +-- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ---------------------------------------------------------------- +62 | val y = "abc".foo // error + | ^^^^^^^^^ + | value foo is not a member of String. + | An extension method was tried, but could not be fully constructed: + | + | Test.foo("abc")(/* missing */summon[C]) failed with + | + | no implicit argument of type C was found for parameter x$1 of method foo in object Test diff --git a/tests/neg/i10901.scala b/tests/neg/i10901.scala new file mode 100644 index 000000000000..9552047c402f --- /dev/null +++ b/tests/neg/i10901.scala @@ -0,0 +1,63 @@ +import scala.annotation.targetName + +object BugExp4Point2D { + + sealed trait ColumnType[T] + case object DoubleT extends ColumnType[Double] + case object IntT extends ColumnType[Int] + + object dsl { + + + extension [T1:Numeric, T2:Numeric](x: T1) + + // N - N + @targetName("point2DConstant") + def º(y: T2): Point2D[T1,T2] = ??? + + + // N - C + @targetName("point2DConstantData") + def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + + + + extension [T1:Numeric, T2:Numeric](x: ColumnType[T1]) + // C - C + @targetName("point2DData") + def º(y: ColumnType[T2]): Point2D[T1,T2] = ??? + + // C - N + @targetName("point2DDataConstant") + def º(y: T2): Point2D[T1,T2] = ??? + + + } + + case class Point2D[T1:Numeric, T2:Numeric](x:T1, y:T2) + + import dsl._ + + def main(args: Array[String]): Unit = { + val x = IntT + val y = DoubleT + + val pos1: Point2D[Int,Double] = x º y // error + val pos2: Point2D[Int,Double] = 100 º 200.1 // ok + val pos3: Point2D[Int,Double] = 101 º y // ok + val pos4: Point2D[Int,Double] = x º 201.1 // error + + } +} + +class C + +object Container: + given C with {} + +object Test: + extension (x: String)(using C) + def foo: String = x + + val y = "abc".foo // error + diff --git a/tests/neg/i6183.check b/tests/neg/i6183.check index 0f09c0b0c166..144258bd080a 100644 --- a/tests/neg/i6183.check +++ b/tests/neg/i6183.check @@ -4,7 +4,12 @@ | value render is not a member of Int. | An extension method was tried, but could not be fully constructed: | - | render(42) + | render(42) failed with + | + | Ambiguous overload. The overloaded alternatives of method render in object Test with types + | [B](b: B)(using x$1: DummyImplicit): Char + | [A](a: A): String + | both match arguments ((42 : Int)) -- [E051] Reference Error: tests/neg/i6183.scala:7:9 ------------------------------------------------------------------- 7 | Test.render(42) // error | ^^^^^^^^^^^ diff --git a/tests/neg/i9185.check b/tests/neg/i9185.check index c469b447742e..1ba971fccff7 100644 --- a/tests/neg/i9185.check +++ b/tests/neg/i9185.check @@ -6,7 +6,9 @@ | | M.pure[A, F]("ola")( | /* ambiguous: both object listMonad in object M and object optionMonad in object M match type M[F] */summon[M[F]] - | ) + | ) failed with + | + | ambiguous implicit arguments: both object listMonad in object M and object optionMonad in object M match type M[F] of parameter m of method pure in object M -- Error: tests/neg/i9185.scala:8:28 ----------------------------------------------------------------------------------- 8 | val value3 = M.pure("ola") // error | ^ @@ -17,4 +19,7 @@ | value len is not a member of String. | An extension method was tried, but could not be fully constructed: | - | M.len("abc") + | M.len("abc") failed with + | + | Found: ("abc" : String) + | Required: Int From 0e2dde69589e81418e91cd9c33cc0cc8c17d2d89 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 23 Dec 2020 18:54:25 +0100 Subject: [PATCH 3/9] Rename test files --- tests/neg/{i10810.check => i10870.check} | 0 tests/neg/{i10810.scala => i10870.scala} | 1 - 2 files changed, 1 deletion(-) rename tests/neg/{i10810.check => i10870.check} (100%) rename tests/neg/{i10810.scala => i10870.scala} (99%) diff --git a/tests/neg/i10810.check b/tests/neg/i10870.check similarity index 100% rename from tests/neg/i10810.check rename to tests/neg/i10870.check diff --git a/tests/neg/i10810.scala b/tests/neg/i10870.scala similarity index 99% rename from tests/neg/i10810.scala rename to tests/neg/i10870.scala index 502232dc1088..55669d580dc5 100644 --- a/tests/neg/i10810.scala +++ b/tests/neg/i10870.scala @@ -8,4 +8,3 @@ object Test: extension(b:B) def x = b.a.x // error - From d6c62dd22a6b71c323a051d1c45cda00c8666bae Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 23 Dec 2020 19:02:33 +0100 Subject: [PATCH 4/9] Fix logic for handling extension methods in Typer --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 80fd8a9e70a2..038d81d490bb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3512,12 +3512,13 @@ class Typer extends Namer try val nestedCtx = ctx.fresh.setNewTyperState() val app = tryExtension(using nestedCtx) - if !app.isEmpty then + if !app.isEmpty && !nestedCtx.reporter.hasErrors then + nestedCtx.typerState.commit() + return ExtMethodApply(app) + else nestedCtx.reporter.allErrors .filterNot(_.msg.isInstanceOf[NotAnExtensionMethod]) match case Nil => - nestedCtx.typerState.commit() - return ExtMethodApply(app) case err :: _ => rememberSearchFailure(tree, SearchFailure(app.withType(FailedExtension(app, pt, err.msg)))) From cc2fbf84accd4f7ed9cbecc0379821e75411173a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 23 Dec 2020 19:52:02 +0100 Subject: [PATCH 5/9] Fix renamed check file --- tests/neg/i10870.check | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i10870.check b/tests/neg/i10870.check index c862d3627ca2..857eb128b19f 100644 --- a/tests/neg/i10870.check +++ b/tests/neg/i10870.check @@ -1,4 +1,4 @@ --- [E008] Not Found Error: tests/neg/i10810.scala:10:16 ---------------------------------------------------------------- +-- [E008] Not Found Error: tests/neg/i10870.scala:10:16 ---------------------------------------------------------------- 10 | def x = b.a.x // error | ^^^^^ | value x is not a member of A. From fb7981ca9b4575f3a7a5e924bf8fb6ea1addbfff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 Dec 2020 18:10:31 +0100 Subject: [PATCH 6/9] Drop "not an extension method" check The test was supposed to check that extension method resolution did produce an extension method and not a normal method. But that test only gave false negatives, as test sticky-extension method shows. --- .../src/dotty/tools/dotc/reporting/ErrorMessageID.scala | 3 +-- compiler/src/dotty/tools/dotc/reporting/messages.scala | 5 ----- compiler/src/dotty/tools/dotc/typer/Applications.scala | 9 +-------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 9 +++------ tests/run/eta-extension.scala | 6 ++++++ tests/run/sticky-extmethod.check | 1 + tests/run/sticky-extmethod.scala | 5 +++++ 7 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 tests/run/eta-extension.scala create mode 100644 tests/run/sticky-extmethod.check create mode 100644 tests/run/sticky-extmethod.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 96c7432ab9f5..20becf1cce35 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -168,8 +168,7 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] { CannotExtendJavaEnumID, InvalidReferenceInImplicitNotFoundAnnotationID, TraitMayNotDefineNativeMethodID, - JavaEnumParentArgsID, - NotAnExtensionMethodID + JavaEnumParentArgsID def errorNumber = ordinal - 2 } diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index f6d43049a2a9..eca28faaacc8 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -2402,11 +2402,6 @@ import transform.SymUtils._ |""".stripMargin } - class NotAnExtensionMethod(methodRef: untpd.Tree)(using Context) - extends TypeMsg(NotAnExtensionMethodID): - def msg = em"not an extension method: $methodRef" - def explain = "" - class NoExtensionMethodAllowed(mdef: untpd.DefDef)(using Context) extends SyntaxMsg(NoExtensionMethodAllowedID) { def msg = em"No extension method allowed here, since collective parameters are given" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index de18a2e2648e..85a3be7b6fdd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -2153,18 +2153,11 @@ trait Applications extends Compatibility { (tree, currentPt) val (core, pt1) = normalizePt(methodRef, pt) - val app = withMode(Mode.SynthesizeExtMethodReceiver) { + withMode(Mode.SynthesizeExtMethodReceiver) { typed( untpd.Apply(core, untpd.TypedSplice(receiver, isExtensionReceiver = true) :: Nil), pt1, ctx.typerState.ownedVars) } - def isExtension(tree: Tree): Boolean = methPart(tree) match { - case Inlined(call, _, _) => isExtension(call) - case tree @ Select(qual, nme.apply) => tree.symbol.is(ExtensionMethod) || isExtension(qual) - case tree => tree.symbol.is(ExtensionMethod) - } - if !isExtension(app) then report.error(NotAnExtensionMethod(methodRef), receiver.srcPos) - app } def isApplicableExtensionMethod(ref: TermRef, receiver: Type)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 038d81d490bb..6e839975796b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3516,12 +3516,9 @@ class Typer extends Namer nestedCtx.typerState.commit() return ExtMethodApply(app) else - nestedCtx.reporter.allErrors - .filterNot(_.msg.isInstanceOf[NotAnExtensionMethod]) match - case Nil => - case err :: _ => - rememberSearchFailure(tree, - SearchFailure(app.withType(FailedExtension(app, pt, err.msg)))) + for err <- nestedCtx.reporter.allErrors.take(1) do + rememberSearchFailure(tree, + SearchFailure(app.withType(FailedExtension(app, pt, err.msg)))) catch case ex: TypeError => rememberSearchFailure(tree, SearchFailure(tree.withType(NestedFailure(ex.toMessage, pt)))) diff --git a/tests/run/eta-extension.scala b/tests/run/eta-extension.scala new file mode 100644 index 000000000000..edf83601c0b5 --- /dev/null +++ b/tests/run/eta-extension.scala @@ -0,0 +1,6 @@ +extension (s: String) def times(i: Int): String = s * i + +val partial = "abc".times + +@main def Test = + assert(partial(2) == "abcabc") diff --git a/tests/run/sticky-extmethod.check b/tests/run/sticky-extmethod.check new file mode 100644 index 000000000000..d00491fd7e5b --- /dev/null +++ b/tests/run/sticky-extmethod.check @@ -0,0 +1 @@ +1 diff --git a/tests/run/sticky-extmethod.scala b/tests/run/sticky-extmethod.scala new file mode 100644 index 000000000000..40ffad61dc35 --- /dev/null +++ b/tests/run/sticky-extmethod.scala @@ -0,0 +1,5 @@ + +extension (x: Int) def m (y: Object): Int = x +def m (x: Int)(y: String): String = y * x + +@main def Test = println(1.m("xx")) From cd0abe95fab322724192a965248a06cdf4e0354f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 26 Dec 2020 18:32:30 +0100 Subject: [PATCH 7/9] Drop support for extension_ names --- compiler/src/dotty/tools/dotc/core/NameOps.scala | 15 --------------- .../dotty/tools/dotc/transform/PostTyper.scala | 3 +-- .../src/dotty/tools/dotc/typer/Implicits.scala | 13 ++++--------- .../src/dotty/tools/dotc/typer/ProtoTypes.scala | 8 +------- compiler/src/dotty/tools/dotc/typer/Typer.scala | 13 ++++--------- 5 files changed, 10 insertions(+), 42 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NameOps.scala b/compiler/src/dotty/tools/dotc/core/NameOps.scala index 32900e9e66c9..d0b7e811eb4b 100644 --- a/compiler/src/dotty/tools/dotc/core/NameOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NameOps.scala @@ -137,21 +137,6 @@ object NameOps { else name.toTermName } - /** Does the name match `extension`? */ - def isExtension: Boolean = name match - case name: SimpleName => - name.length == "extension".length && name.startsWith("extension") - case _ => false - - /** Does this name start with `extension_`? */ - def isExtensionName: Boolean = name match - case name: SimpleName => name.startsWith("extension_") - case _ => false - - // TODO: Drop next 3 methods once extension names have stabilized - /** Add an `extension_` in front of this name */ - def toExtensionName(using Context): SimpleName = "extension_".concat(name) - /** The expanded name. * This is the fully qualified name of `base` with `ExpandPrefixName` as separator, * followed by `kind` and the name. diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 4541c3942857..ba0337a797f3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -383,8 +383,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def checkIdent(sel: untpd.ImportSelector): Unit = if !exprTpe.member(sel.name).exists - && !exprTpe.member(sel.name.toTypeName).exists - && !exprTpe.member(sel.name.toExtensionName).exists then + && !exprTpe.member(sel.name.toTypeName).exists then report.error(NotAMember(exprTpe, sel.name, "value"), sel.imported.srcPos) if seen.contains(sel.name) then report.error(ImportRenamedTwice(sel.imported), sel.imported.srcPos) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 1f5973f82e16..d532f070ff3a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -76,9 +76,9 @@ object Implicits: */ def hasExtMethod(tp: Type, expected: Type)(using Context) = expected match case selProto @ SelectionProto(selName: TermName, _, _, _) => - tp.memberBasedOnFlags(selName, required = ExtensionMethod).exists - || tp.memberBasedOnFlags(selProto.extensionName, required = ExtensionMethod).exists - case _ => false + tp.memberBasedOnFlags(selName, required = ExtensionMethod).exists + case _ => + false def strictEquality(using Context): Boolean = ctx.mode.is(Mode.StrictEquality) || Feature.enabled(nme.strictEquality) @@ -1023,12 +1023,7 @@ trait Implicits: pt match case selProto @ SelectionProto(selName: TermName, mbrType, _, _) if cand.isExtension => def tryExtension(using Context) = - val xname = - if ref.memberBasedOnFlags(selProto.extensionName, required = ExtensionMethod).exists then - selProto.extensionName - else - selName - extMethodApply(untpd.Select(untpdGenerated, xname), argument, mbrType) + extMethodApply(untpd.Select(untpdGenerated, selName), argument, mbrType) if cand.isConversion then val extensionCtx, conversionCtx = ctx.fresh.setNewTyperState() val extensionResult = tryExtension(using extensionCtx) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4b04666a2cb8..1ac41be3b450 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -147,11 +147,6 @@ object ProtoTypes { abstract case class SelectionProto(name: Name, memberProto: Type, compat: Compatibility, privateOK: Boolean) extends CachedProxyType with ProtoType with ValueTypeOrProto { - private var myExtensionName: TermName = null - def extensionName(using Context): TermName = - if myExtensionName == null then myExtensionName = name.toExtensionName - myExtensionName - /** Is the set of members of this type unknown? This is the case if: * 1. The type has Nothing or Wildcard as a prefix or underlying type * 2. The type has an uninstantiated TypeVar as a prefix or underlying type, @@ -447,8 +442,7 @@ object ProtoTypes { ctx.typer.isApplicableType(tp, argType :: Nil, resultType) || { resType match { case selProto @ SelectionProto(selName: TermName, mbrType, _, _) => - ctx.typer.hasExtensionMethodNamed(tp, selName, argType, mbrType) - || ctx.typer.hasExtensionMethodNamed(tp, selProto.extensionName, argType, mbrType) + ctx.typer.hasExtensionMethodNamed(tp, selName, argType, mbrType) //.reporting(i"has ext $tp $name $argType $mbrType: $result") case _ => false diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 6e839975796b..ad4eb742fd47 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -3348,11 +3348,8 @@ class Typer extends Namer // implicit conversion to the receiver type. def sharpenedPt = pt match case pt: SelectionProto - if pt.name.isExtensionName - || pt.memberProto.revealIgnored.isExtensionApplyProto => - pt.deepenProto - case _ => - pt + if pt.memberProto.revealIgnored.isExtensionApplyProto => pt.deepenProto + case _ => pt def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) @@ -3504,10 +3501,8 @@ class Typer extends Namer findRef(selName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match case ref: TermRef => extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) - case _ => findRef(selProto.extensionName, WildcardType, ExtensionMethod, EmptyFlags, tree.srcPos) match - case ref: TermRef => - extMethodApply(untpd.ref(ref).withSpan(tree.span), tree, mbrType) - case _ => EmptyTree + case _ => + EmptyTree try val nestedCtx = ctx.fresh.setNewTyperState() From d4e6c778f6a82b028cb7b9b870bb2f580640f7c0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 27 Dec 2020 18:28:13 +0100 Subject: [PATCH 8/9] Another test case --- tests/run/i9507.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/run/i9507.scala diff --git a/tests/run/i9507.scala b/tests/run/i9507.scala new file mode 100644 index 000000000000..5d8923a03021 --- /dev/null +++ b/tests/run/i9507.scala @@ -0,0 +1,6 @@ +extension (x: Int) + def add(y: Int): Int = x + y + +def newFunction: Int => Int = 4.add + +@main def Test = assert(newFunction(1) == 5) \ No newline at end of file From 8324d4b5f9ccfd21a646e5776ccc8743811e867e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 28 Dec 2020 19:00:45 +0100 Subject: [PATCH 9/9] Bring back explanations of implicit searches for extensions Bring back explanations if an implicit search was tried to resolve an extension method. --- .../tools/dotc/typer/ErrorReporting.scala | 16 ++++++++++------ tests/neg/i7056.check | 7 +++++++ tests/neg/i7056.scala | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 tests/neg/i7056.check create mode 100644 tests/neg/i7056.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 89bf9d6661a2..78fca17c4758 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -141,11 +141,16 @@ object ErrorReporting { if Feature.migrateTo3 then "\nThis patch can be inserted automatically under -rewrite." else "" + def whyFailedStr(fail: FailedExtension) = + i""" failed with + | + |${fail.whyFailed.message.indented(8)}""" + def selectErrorAddendum (tree: untpd.RefTree, qual1: Tree, qualType: Type, suggestImports: Type => String) (using Context): String = - val attempts = mutable.ListBuffer[(Tree, FailedExtension)]() + val attempts = mutable.ListBuffer[(Tree, String)]() val nested = mutable.ListBuffer[NestedFailure]() for failures <- qual1.getAttachment(Typer.HiddenSearchFailure) @@ -153,8 +158,9 @@ object ErrorReporting { do failure.reason match case fail: NestedFailure => nested += fail - case fail: FailedExtension => attempts += ((failure.tree, fail)) - case _ => + case fail: FailedExtension => attempts += ((failure.tree, whyFailedStr(fail))) + case fail: Implicits.NoMatchingImplicits => // do nothing + case _ => attempts += ((failure.tree, "")) if qualType.derivesFrom(defn.DynamicClass) then "\npossible cause: maybe a wrong Dynamic method signature?" else if attempts.nonEmpty then @@ -164,9 +170,7 @@ object ErrorReporting { .distinctBy(_._1) .map((treeStr, whyFailed) => i""" - | $treeStr failed with - | - |${whyFailed.whyFailed.message.indented(8)}""") + | $treeStr$whyFailed""") val extMethods = if attemptStrings.length > 1 then "Extension methods were" else "An extension method was" diff --git a/tests/neg/i7056.check b/tests/neg/i7056.check new file mode 100644 index 000000000000..2dff4dc66b13 --- /dev/null +++ b/tests/neg/i7056.check @@ -0,0 +1,7 @@ +-- [E008] Not Found Error: tests/neg/i7056.scala:19:10 ----------------------------------------------------------------- +19 |val z = x.idnt1 // error + | ^^^^^^^ + | value idnt1 is not a member of B. + | An extension method was tried, but could not be fully constructed: + | + | i7056$package.given_T1_T[T](given_PartialId_B).idnt1() diff --git a/tests/neg/i7056.scala b/tests/neg/i7056.scala new file mode 100644 index 000000000000..d16aa949000e --- /dev/null +++ b/tests/neg/i7056.scala @@ -0,0 +1,19 @@ +type A +type B <: A + +type PartialId[X] = X match { + case B => X +} + +trait T1[T] { + extension (t1: T) def idnt1: Any +} + +given [T <: A](using PartialId[T]): T1[T] = new T1[T] { + extension (t1: T) def idnt1: Any = ??? +} + +given PartialId[B] = ??? + +val x: B = ??? +val z = x.idnt1 // error