diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index d6e9cf9fbee1..e5231018126a 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -723,10 +723,7 @@ object desugar { else if (companionMembers.nonEmpty || companionDerived.nonEmpty || isEnum) companionDefs(anyRef, companionMembers) else if (isValueClass) - impl.constr.vparamss match { - case (_ :: Nil) :: _ => companionDefs(anyRef, Nil) - case _ => Nil // error will be emitted in typer - } + companionDefs(anyRef, Nil) else Nil enumCompanionRef match { diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index dc112a59403a..ec7c7dd8b16e 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -1494,6 +1494,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] w case tp: SingletonType if tp.isStable => tp case tp: ValueType => SkolemType(tp) case tp: TypeProxy => ensureStableSingleton(tp.underlying) + case tp => assert(ctx.reporter.errorsReported); SkolemType(tp) } /** Skip refinements in `tp2` which match corresponding refinements in `tp1`. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 50e01e0f2c68..35a71f3433b2 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4599,7 +4599,7 @@ object Types { case tp: AppliedType => def mapArgs(args: List[Type], tparams: List[ParamInfo]): List[Type] = args match { - case arg :: otherArgs => + case arg :: otherArgs if tparams.nonEmpty => val arg1 = arg match { case arg: TypeBounds => this(arg) case arg => atVariance(variance * tparams.head.paramVariance)(this(arg)) diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index f2701acd60e2..545d3b86784e 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -348,7 +348,7 @@ object Erasure { tree.symbol.getAnnotation(defn.CompileTimeOnlyAnnot) match { case Some(annot) => def defaultMsg = - s"""Reference to ${tree.symbol.showLocated} should not have survived, + i"""Reference to ${tree.symbol.showLocated} should not have survived, |it should have been processed and eliminated during expansion of an enclosing macro or term erasure.""" val message = annot.argumentConstant(0).fold(defaultMsg)(_.stringValue) ctx.error(message, tree.sourcePos) @@ -750,8 +750,11 @@ object Erasure { override def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = trace(i"adapting ${tree.showSummary}: ${tree.tpe} to $pt", show = true) { - assert(ctx.phase == ctx.erasurePhase || ctx.phase == ctx.erasurePhase.next, ctx.phase) - if (tree.isEmpty) tree + if ctx.phase != ctx.erasurePhase && ctx.phase != ctx.erasurePhase.next then + // this can happen when reading annotations loaded during erasure, + // since these are loaded at phase typer. + adapt(tree, pt, locked)(given ctx.withPhase(ctx.erasurePhase.next)) + else if (tree.isEmpty) tree else if (ctx.mode is Mode.Pattern) tree // TODO: replace with assertion once pattern matcher is active else adaptToType(tree, pt) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 808586ffed96..931717617e24 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -987,7 +987,10 @@ trait Applications extends Compatibility { /** Overridden in ReTyper to handle primitive operations that can be generated after erasure */ protected def handleUnexpectedFunType(tree: untpd.Apply, fun: Tree)(implicit ctx: Context): Tree = - throw new Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") + if ctx.reporter.errorsReported then + throw TypeError(i"unexpected function type: ${methPart(fun).tpe}") + else + throw Error(i"unexpected type.\n fun = $fun,\n methPart(fun) = ${methPart(fun)},\n methPart(fun).tpe = ${methPart(fun).tpe},\n tpe = ${fun.tpe}") def typedNamedArgs(args: List[untpd.Tree])(implicit ctx: Context): List[NamedArg] = for (arg @ NamedArg(id, argtpt) <- args) yield { diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 0d8111fbd642..1f669e142f70 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -855,7 +855,7 @@ trait Implicits { self: Typer => (formal, span) => implicit ctx => { def success(t: Tree) = New(defn.ValueOfClass.typeRef.appliedTo(t.tpe), t :: Nil).withSpan(span) - formal.argTypes match { + formal.argInfos match { case arg :: Nil => fullyDefinedType(arg.dealias, "ValueOf argument", span) match { case ConstantType(c: Constant) => @@ -1401,18 +1401,17 @@ trait Implicits { self: Typer => untpd.Apply(untpdConv, untpd.TypedSplice(argument) :: Nil), pt, locked) } - if (cand.isExtension) { - val SelectionProto(name: TermName, mbrType, _, _) = pt - val result = extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) - if (!ctx.reporter.hasErrors && cand.isConversion) { - val testCtx = ctx.fresh.setExploreTyperState() - tryConversion(testCtx) - if (testCtx.reporter.hasErrors) - ctx.error(em"ambiguous implicit: $generated is eligible both as an implicit conversion and as an extension method container") - } - result - } - else tryConversion + pt match + case SelectionProto(name: TermName, mbrType, _, _) if cand.isExtension => + val result = extMethodApply(untpd.Select(untpdGenerated, name), argument, mbrType) + if !ctx.reporter.hasErrors && cand.isConversion then + val testCtx = ctx.fresh.setExploreTyperState() + tryConversion(testCtx) + if testCtx.reporter.hasErrors then + ctx.error(em"ambiguous implicit: $generated is eligible both as an implicit conversion and as an extension method container") + result + case _ => + tryConversion } if (ctx.reporter.hasErrors) { ctx.reporter.removeBufferedMessages diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 87c5c93db675..f7eee7c90ded 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1231,7 +1231,10 @@ class Typer extends Namer def caseRest(implicit ctx: Context) = { val pat1 = checkSimpleKinded(typedType(cdef.pat)(ctx.addMode(Mode.Pattern))) val pat2 = indexPattern(cdef).transform(pat1) - val body1 = typedType(cdef.body, pt) + var body1 = typedType(cdef.body, pt) + if !body1.isType then + assert(ctx.reporter.errorsReported) + body1 = TypeTree(errorType("", cdef.sourcePos)) assignType(cpy.CaseDef(cdef)(pat2, EmptyTree, body1), pat2, body1) } caseRest(ctx.fresh.setFreshGADTBounds.setNewScope) @@ -1546,7 +1549,13 @@ class Typer extends Namer def typedAlternative(tree: untpd.Alternative, pt: Type)(implicit ctx: Context): Alternative = { val nestedCtx = ctx.addMode(Mode.InPatternAlternative) + def ensureValueTypeOrWildcard(tree: Tree) = + if tree.tpe.isValueTypeOrWildcard then tree + else + assert(ctx.reporter.errorsReported) + tree.withType(defn.AnyType) val trees1 = tree.trees.mapconserve(typed(_, pt)(nestedCtx)) + .mapconserve(ensureValueTypeOrWildcard) assignType(cpy.Alternative(tree)(trees1), trees1) } @@ -1922,13 +1931,17 @@ class Typer extends Namer val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) val arg1 = typed(tree.arg, pt) if (ctx.mode is Mode.Type) { - val result = assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1) - result.tpe match { - case AnnotatedType(rhs, Annotation.WithBounds(bounds)) => - if (!bounds.contains(rhs)) ctx.error(em"type $rhs outside bounds $bounds", tree.sourcePos) - case _ => - } - result + if arg1.isType then + val result = assignType(cpy.Annotated(tree)(arg1, annot1), arg1, annot1) + result.tpe match { + case AnnotatedType(rhs, Annotation.WithBounds(bounds)) => + if (!bounds.contains(rhs)) ctx.error(em"type $rhs outside bounds $bounds", tree.sourcePos) + case _ => + } + result + else + assert(ctx.reporter.errorsReported) + TypeTree(UnspecifiedErrorType) } else { val arg2 = arg1 match { diff --git a/docs/docs/reference/contextual/extension-methods-new.md b/docs/docs/reference/contextual/extension-methods-new.md deleted file mode 100644 index fbba4973aace..000000000000 --- a/docs/docs/reference/contextual/extension-methods-new.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -layout: doc-page -title: "Extension Methods" ---- - -Extension methods allow one to add methods to a type after the type is defined. Example: - -```scala -case class Circle(x: Double, y: Double, radius: Double) - -def (c: Circle) circumference: Double = c.radius * math.Pi * 2 -``` - -Like regular methods, extension methods can be invoked with infix `.`: - -```scala -val circle = Circle(0, 0, 1) -circle.circumference -``` - -### Translation of Extension Methods - -Extension methods are methods that have a parameter clause in front of the defined -identifier. They translate to methods where the leading parameter section is moved -to after the defined identifier. So, the definition of `circumference` above translates -to the plain method, and can also be invoked as such: -```scala -def circumference(c: Circle): Double = c.radius * math.Pi * 2 - -assert(circle.circumference == circumference(circle)) -``` - -### Translation of Calls to Extension Methods - -When is an extension method applicable? There are two possibilities. - - - An extension method is applicable if it is visible under a simple name, by being defined - or inherited or imported in a scope enclosing the application. - - An extension method is applicable if it is a member of some given instance at the point of the application. - -As an example, consider an extension method `longestStrings` on `Seq[String]` defined in a trait `StringSeqOps`. - -```scala -trait StringSeqOps { - def (xs: Seq[String]) longestStrings = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} -``` -We can make the extension method available by defining a given `StringSeqOps` instance, like this: -```scala -given ops1: StringSeqOps -``` -Then -```scala -List("here", "is", "a", "list").longestStrings -``` -is legal everywhere `ops1` is available. Alternatively, we can define `longestStrings` as a member of a normal object. But then the method has to be brought into scope to be usable as an extension method. - -```scala -object ops2 extends StringSeqOps -import ops2.longestStrings -List("here", "is", "a", "list").longestStrings -``` -The precise rules for resolving a selection to an extension method are as follows. - -Assume a selection `e.m[Ts]` where `m` is not a member of `e`, where the type arguments `[Ts]` are optional, -and where `T` is the expected type. The following two rewritings are tried in order: - - 1. The selection is rewritten to `m[Ts](e)`. - 2. If the first rewriting does not typecheck with expected type `T`, and there is a given instance `i` - in either the current scope or in the implicit scope of `T`, and `i` defines an extension - method named `m`, then selection is expanded to `i.m[Ts](e)`. - This second rewriting is attempted at the time where the compiler also tries an implicit conversion - from `T` to a type containing `m`. If there is more than one way of rewriting, an ambiguity error results. - -So `circle.circumference` translates to `CircleOps.circumference(circle)`, provided -`circle` has type `Circle` and `CircleOps` is given (i.e. it is visible at the point of call or it is defined in the companion object of `Circle`). - -### Given Instances for Extension Methods - -A special syntax allows to define a given instance for one or more extension methods without listing a parent type. -Examples: - -```scala -given stringOps: (xs: Seq[String]) { - def longestStrings: Seq[String] = { - val maxLength = xs.map(_.length).max - xs.filter(_.length == maxLength) - } -} - -given [T](xs: List[T]) { - def second = xs.tail.head - def third[T]: T = xs.tail.tail.head -} -``` -These given clauses define extension methods `longestStrings`, `second`, and `third`. All extension methods defined in such a given clause -share the same leading parameters, which follow the `given`. The remainder of the extension methods is written as regular defs inside braces. - -If such given instances are anonymous (as in the second clause), their name is synthesized from the name of the first defined extension method. - - -### Operators - -The extension method syntax also applies to the definition of operators. -In each case the definition syntax mirrors the way the operator is applied. -Examples: -```scala -def (x: String) < (y: String) = ... -def (x: Elem) +: (xs: Seq[Elem]) = ... - -"ab" + "c" -1 +: List(2, 3) -``` -The two definitions above translate to -```scala -def < (x: String)(y: String) = ... -def +: (xs: Seq[Elem])(x: Elem) = ... -``` -Note that swap of the two parameters `x` and `xs` when translating -the right-binding operator `+:` to an extension method. This is analogous -to the implementation of right binding operators as normal methods. - -### Generic Extensions - -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: - -```scala -def (xs: List[T]) second [T] = - xs.tail.head - -def (xs: List[List[T]]) flattened [T] = - xs.foldLeft[List[T]](Nil)(_ ++ _) - -def (x: T) + [T : Numeric](y: T): T = - summon[Numeric[T]].plus(x, y) -``` - -As usual, type parameters of the extension method follow the defined method name. Nevertheless, such type parameters can already be used in the preceding parameter clause. - - -### Syntax - -The required syntax extension just adds one clause for extension methods relative -to the [current syntax](../../internals/syntax.md). -``` -DefSig ::= ... - | ‘(’ DefParam ‘)’ [nl] id [DefTypeParamClause] DefParamClauses -GivenDef ::= ... - [GivenSig ‘:’] [ExtParamClause] ExtMethods -ExtParamClause ::= [DefTypeParamClause] ‘(’ DefParam ‘)’ {GivenParamClause} -ExtMethods ::= [nl] ‘{’ ‘def’ DefDef {semi ‘def’ DefDef} ‘}’ -``` diff --git a/tests/neg/i7810.scala b/tests/neg/i7810.scala new file mode 100644 index 000000000000..680514b1471e --- /dev/null +++ b/tests/neg/i7810.scala @@ -0,0 +1 @@ +val a = implicitly[ValueOf[_]] // error diff --git a/tests/neg/i7811.scala b/tests/neg/i7811.scala new file mode 100644 index 000000000000..170f7acdf11c --- /dev/null +++ b/tests/neg/i7811.scala @@ -0,0 +1,8 @@ +trait A { + type Type[X,] // error + def a[X]: Type[X,] // error +} +class B extends A { + type Type[X] + var a = 1 +} \ No newline at end of file diff --git a/tests/neg/i7812.scala b/tests/neg/i7812.scala new file mode 100644 index 000000000000..264258fa4db3 --- /dev/null +++ b/tests/neg/i7812.scala @@ -0,0 +1,3 @@ +def f(): Any = ??? +var f: (UndefinedA & UndefinedB) { val x: Int } = ??? // error // error +val a = f // error \ No newline at end of file diff --git a/tests/neg/i7813.scala b/tests/neg/i7813.scala new file mode 100644 index 000000000000..b314072c2bf5 --- /dev/null +++ b/tests/neg/i7813.scala @@ -0,0 +1,3 @@ +implicit def f[X <: Undefined](implicit a: Int): X = ??? // error +def g(arg: h.NonExistent): Int = ??? +val h: Int = ??? \ No newline at end of file diff --git a/tests/neg/i7814.scala b/tests/neg/i7814.scala new file mode 100644 index 000000000000..13340dd3faa6 --- /dev/null +++ b/tests/neg/i7814.scala @@ -0,0 +1 @@ +def i0 = Unit // error \ No newline at end of file diff --git a/tests/neg/i7815.scala b/tests/neg/i7815.scala new file mode 100644 index 000000000000..d04461024b29 --- /dev/null +++ b/tests/neg/i7815.scala @@ -0,0 +1,3 @@ +trait A { + val a: Int match { case Int => this } // error +} \ No newline at end of file diff --git a/tests/neg/i7816.scala b/tests/neg/i7816.scala new file mode 100644 index 000000000000..f1eed694a085 --- /dev/null +++ b/tests/neg/i7816.scala @@ -0,0 +1,4 @@ +object A { + def f()(>) = ??? // error + import f.NonExistent // error +} \ No newline at end of file diff --git a/tests/neg/i7817.scala b/tests/neg/i7817.scala new file mode 100644 index 000000000000..93557437918c --- /dev/null +++ b/tests/neg/i7817.scala @@ -0,0 +1,8 @@ +trait Foo[A] +class A { + def F[x : Foo]() = ??? + + Int match { + case Int | F => () // error + } +} \ No newline at end of file diff --git a/tests/neg/i7818.scala b/tests/neg/i7818.scala new file mode 100644 index 000000000000..78cbee506784 --- /dev/null +++ b/tests/neg/i7818.scala @@ -0,0 +1 @@ +def foo = (x: @) => () // error \ No newline at end of file diff --git a/tests/pending/neg/i7820.scala b/tests/pending/neg/i7820.scala new file mode 100644 index 000000000000..5e4e95f0718c --- /dev/null +++ b/tests/pending/neg/i7820.scala @@ -0,0 +1 @@ +object A { type F[X >: F, Y] } diff --git a/tests/pending/pos/i7745.scala b/tests/pending/pos/i7745.scala new file mode 100644 index 000000000000..de03d3995d33 --- /dev/null +++ b/tests/pending/pos/i7745.scala @@ -0,0 +1,3 @@ +trait F[x] +implicit def foo[f[_], y, x <: f[y]](implicit ev: F[y]): F[x] = ??? +val test = implicitly \ No newline at end of file diff --git a/tests/pending/pos/i7778.scala b/tests/pending/pos/i7778.scala new file mode 100644 index 000000000000..70d58d938d2b --- /dev/null +++ b/tests/pending/pos/i7778.scala @@ -0,0 +1,5 @@ +object Example extends App { + + final case class Foo[A](run: (given A) => Int) + +} \ No newline at end of file diff --git a/tests/pos/consume.scala b/tests/pos/consume.scala new file mode 100644 index 000000000000..e8d5e15be4a7 --- /dev/null +++ b/tests/pos/consume.scala @@ -0,0 +1,57 @@ +object Test1 with + def consume(xs: List[Int], limit: Int): List[Int] = xs match + case x :: xs1 if limit > 0 => consume(xs1, limit - x) + case _ => xs + +object Test2 { + import scala.math.Numeric + import scala.math.Numeric.Implicits._ + import scala.math.Ordering.Implicits._ + + def consume[T: Numeric](xs: List[T], limit: T): List[T] = + val zero = implicitly[Numeric[T]].zero + xs match { + case x :: xs1 if limit > zero => consume(xs1, limit - x) + case _ => xs + } +} + +object math3 with + trait Ord[T] with + def (x: T) > (t: T): Boolean = ??? + def (x: T) <= (t: T): Boolean = ??? + + trait Numeric[T] extends Ord[T] with + def (x: T) + (y: T): T = ??? + def (x: T) - (y: T): T = ??? + def (x: Int) numeric: T = ??? +end math3 + +object Test3 with + import math3.Numeric + import collection.immutable.Seq + + def consume[T: Numeric](xs: List[T], limit: T): List[T] = xs match + case x :: xs1 if limit > 0.numeric => consume(xs1, limit - x) + case _ => xs + + def consume[T: Numeric](xs: LazyList[T], limit: T): LazyList[T] = xs match + case x #:: xs1 if limit > 0.numeric => consume(xs1, limit - x) + case _ => xs + + def consume[T: Numeric](xs: Seq[T], limit: T): Seq[T] = + def dropCount(it: Iterator[T], start: Int, limit: T): Int = + if limit > 0.numeric && it.hasNext then dropCount(it, start + 1, limit - it.next) + else start + xs.drop(dropCount(xs.iterator, 0, limit)) + + def consume[T: Numeric](xs: Iterable[T], limit: T): Iterable[T] = + var sum = 0.numeric + xs.dropWhile { x => + try sum <= limit finally sum += x + } + + def consume2[T: Numeric](xs: Iterable[T], limit: T): Iterable[T] = + consume2(LazyList.from(xs), limit) +end Test3 + diff --git a/tests/pos/i7819.scala b/tests/pos/i7819.scala new file mode 100644 index 000000000000..13977d5a1647 --- /dev/null +++ b/tests/pos/i7819.scala @@ -0,0 +1 @@ +class Foo()(val x: Any) extends AnyVal