diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ef07d477c303..96e3d97d7aca 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1320,16 +1320,6 @@ object Parsers { */ def qualId(): Tree = dotSelectors(termIdent()) - /** Singleton ::= SimpleRef - * | SimpleLiteral - * | Singleton ‘.’ id - * -- not yet | Singleton ‘(’ Singletons ‘)’ - * -- not yet | Singleton ‘[’ Types ‘]’ - */ - def singleton(): Tree = - if isSimpleLiteral then simpleLiteral() - else dotSelectors(simpleRef()) - /** SimpleLiteral ::= [‘-’] integerLiteral * | [‘-’] floatingPointLiteral * | booleanLiteral @@ -2018,7 +2008,7 @@ object Parsers { /** SimpleType ::= SimpleLiteral * | ‘?’ TypeBounds * | SimpleType1 - * | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer + * | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.modularity, checked in Typer * Singletons ::= Singleton {‘,’ Singleton} */ def simpleType(): Tree = @@ -2050,11 +2040,11 @@ object Parsers { val start = in.skipToken() typeBounds().withSpan(Span(start, in.lastOffset, start)) else - def singletonArgs(t: Tree): Tree = - if in.token == LPAREN && in.featureEnabled(Feature.dependent) - then singletonArgs(AppliedTypeTree(t, inParensWithCommas(commaSeparated(singleton)))) - else t - singletonArgs(simpleType1()) + val tpt = simpleType1() + if in.featureEnabled(Feature.modularity) && in.token == LPAREN then + parArgumentExprss(wrapNew(tpt)) + else + tpt /** SimpleType1 ::= id * | Singleton `.' id diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index 25f2f879077e..505a57a4d3b6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -221,6 +221,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case GivenSearchPriorityID // errorNumber: 205 case EnumMayNotBeValueClassesID // errorNumber: 206 case IllegalUnrollPlacementID // errorNumber: 207 + case PointlessAppliedConstructorTypeID // errorNumber: 208 + case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 209 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index fd85a65822eb..a7dc2a6d1df1 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3433,3 +3433,24 @@ extends DeclarationMsg(IllegalUnrollPlacementID): def explain(using Context) = "" end IllegalUnrollPlacement + +final class PointlessAppliedConstructorType(tpt: untpd.Tree, args: List[untpd.Tree], tpe: Type)(using Context) extends TypeMsg(PointlessAppliedConstructorTypeID): + override protected def msg(using Context): String = + val act = i"$tpt(${args.map(_.show).mkString(", ")})" + i"""|Applied constructor type $act has no effect. + |The resulting type of $act is the same as its base type, namely: $tpe""".stripMargin + + override protected def explain(using Context): String = + i"""|Applied constructor types are used to ascribe specialized types of constructor applications. + |To benefit from this feature, the constructor in question has to have a more specific type than the class itself. + | + |If you want to track a precise type of any of the class parameters, make sure to mark the parameter as `tracked`. + |Otherwise, you can safely remove the argument list from the type. + |""" + +final class OnlyFullyDependentAppliedConstructorType()(using Context) + extends TypeMsg(OnlyFullyDependentAppliedConstructorTypeID): + override protected def msg(using Context): String = + i"Applied constructor type can only be used with classes that only have tracked parameters" + + override protected def explain(using Context): String = "" diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 98bfbe69ff8c..8d7b5ebbe263 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1694,6 +1694,23 @@ trait Applications extends Compatibility { def typedUnApply(tree: untpd.UnApply, selType: Type)(using Context): UnApply = throw new UnsupportedOperationException("cannot type check an UnApply node") + def typedAppliedConstructorType(tree: untpd.Apply)(using Context) = untpd.methPart(tree) match + case Select(New(tpt), _) => + val tree1 = typedExpr(tree) + val widenSkolemsMap = new TypeMap: + def apply(tp: Type) = mapOver(tp.widenSkolem) + val preciseTp = widenSkolemsMap(tree1.tpe) + val classTp = typedType(tpt).tpe + def classSymbolHasOnlyTrackedParameters = + classTp.classSymbol.primaryConstructor.paramSymss.flatten.filter(_.isTerm).forall(_.is(Tracked)) + if !preciseTp.isError && !classSymbolHasOnlyTrackedParameters then + report.warning(OnlyFullyDependentAppliedConstructorType(), tree.srcPos) + if !preciseTp.isError && (preciseTp frozen_=:= classTp) then + report.warning(PointlessAppliedConstructorType(tpt, tree.args, classTp), tree.srcPos) + TypeTree(preciseTp) + case _ => + throw TypeError(em"Unexpected applied constructor type: $tree") + /** Is given method reference applicable to argument trees `args`? * @param resultType The expected result type of the application */ diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 13e75be75838..3b96d4383f6c 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -294,7 +294,7 @@ object ErrorReporting { def dependentMsg = """Term-dependent types are experimental, - |they must be enabled with a `experimental.dependent` language import or setting""".stripMargin.toMessage + |they must be enabled with a `experimental.modularity` language import or setting""".stripMargin.toMessage def err(using Context): Errors = new Errors } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 8ba63dfc1e67..d169446e773f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2515,17 +2515,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAppliedTypeTree(tree: untpd.AppliedTypeTree)(using Context): Tree = { - tree.args match - case arg :: _ if arg.isTerm => - if Feature.dependentEnabled then - return errorTree(tree, em"Not yet implemented: T(...)") - else - return errorTree(tree, dependentMsg) - case _ => - - val tpt1 = withoutMode(Mode.Pattern) { + val tpt1 = withoutMode(Mode.Pattern): typed(tree.tpt, AnyTypeConstructorProto) - } + val tparams = tpt1.tpe.typeParams if tpt1.tpe.isError then val args1 = tree.args.mapconserve(typedType(_)) @@ -2649,7 +2641,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer typeIndexedLambdaTypeTree(tree, tparams, body) def typedTermLambdaTypeTree(tree: untpd.TermLambdaTypeTree)(using Context): Tree = - if Feature.dependentEnabled then + if Feature.enabled(Feature.modularity) then errorTree(tree, em"Not yet implemented: (...) =>> ...") else errorTree(tree, dependentMsg) @@ -3485,7 +3477,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer /** Typecheck tree without adapting it, returning a typed tree. * @param initTree the untyped tree - * @param pt the expected result type + * @param pt the expected result typ * @param locked the set of type variables of the current typer state that cannot be interpolated * at the present time */ @@ -3525,7 +3517,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def typedUnnamed(tree: untpd.Tree): Tree = tree match { case tree: untpd.Apply => - if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) else typedApply(tree, pt) + if (ctx.mode is Mode.Pattern) typedUnApply(tree, pt) + else if (Feature.enabled(modularity) && ctx.mode.is(Mode.Type) && !ctx.isAfterTyper) typedAppliedConstructorType(tree) + else typedApply(tree, pt) case tree: untpd.This => typedThis(tree) case tree: untpd.Number => typedNumber(tree, pt) case tree: untpd.Literal => typedLiteral(tree) diff --git a/compiler/test/dotc/neg-best-effort-unpickling.excludelist b/compiler/test/dotc/neg-best-effort-unpickling.excludelist index d57f7e0176e8..9c20bf3ccc03 100644 --- a/compiler/test/dotc/neg-best-effort-unpickling.excludelist +++ b/compiler/test/dotc/neg-best-effort-unpickling.excludelist @@ -18,3 +18,6 @@ i18750.scala # Crash on invalid prefix ([A] =>> Int) i22357a.scala + +# `110 (of class java.lang.Integer)` +context-function-syntax.scala diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 665b4f5144ba..f33851fdb662 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -196,7 +196,7 @@ AnnotType1 ::= SimpleType1 {Annotation} SimpleType ::= SimpleLiteral SingletonTypeTree(l) | ‘?’ TypeBounds - | SimpleType1 + | SimpleType1 {ParArgumentExprs} SimpleType1 ::= id Ident(name) | Singleton ‘.’ id Select(t, name) | Singleton ‘.’ ‘type’ SingletonTypeTree(p) diff --git a/docs/_docs/reference/experimental/modularity.md b/docs/_docs/reference/experimental/modularity.md index 1a3d47695861..1c8c30bdea31 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -196,6 +196,33 @@ LocalModifier ::= ‘tracked’ The (soft) `tracked` modifier is allowed as a local modifier. +## Applied constructor types + +A new syntax is also introduced, to make classes with `tracked` parameters +easier to use. The new syntax is essentially the ability to use an application +of a class constructor as a type, we call such types applied constructor types. + +With this new feature the following example compiles correctly and the type in +the comment is the resulting type of the applied constructor types. + +```scala +import scala.language.experimental.modularity + +class C(tracked val v: Any) + +val c: C(42) /* C { val v: 42 } */ = C(42) +``` + +### Syntax change + +``` +SimpleType ::= SimpleLiteral + | ‘?’ TypeBounds +--- | SimpleType1 ++++ | SimpleType1 {ParArgumentExprs} +``` + +A `SimpleType` can now optionally be followed by `ParArgumentExprs`. ## Allow Class Parents to be Refined Types diff --git a/tests/neg/applied_constructor_types.check b/tests/neg/applied_constructor_types.check new file mode 100644 index 000000000000..e966c5d15f27 --- /dev/null +++ b/tests/neg/applied_constructor_types.check @@ -0,0 +1,18 @@ +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:8:10 ---------------------------------------------- +8 | val v1: f(1) = f(1) // error + | ^ + | Not found: type f + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:9:10 ---------------------------------------------- +9 | val v2: id(1) = f(1) // error + | ^^ + | Not found: type id - did you mean is? + | + | longer explanation available when compiling with `-explain` +-- [E006] Not Found Error: tests/neg/applied_constructor_types.scala:10:10 --------------------------------------------- +10 | val v3: idDependent(1) = f(1) // error + | ^^^^^^^^^^^ + | Not found: type idDependent + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/applied_constructor_types.scala b/tests/neg/applied_constructor_types.scala new file mode 100644 index 000000000000..8207b1213851 --- /dev/null +++ b/tests/neg/applied_constructor_types.scala @@ -0,0 +1,10 @@ +import scala.language.experimental.modularity + +def f(x: Int): Int = x +def id[T](x: T): T = x +def idDependent(x: Any): x.type = x + +def test = + val v1: f(1) = f(1) // error + val v2: id(1) = f(1) // error + val v3: idDependent(1) = f(1) // error diff --git a/tests/neg/context-function-syntax.scala b/tests/neg/context-function-syntax.scala index 1f9b74cc69a6..e411e840d8b5 100644 --- a/tests/neg/context-function-syntax.scala +++ b/tests/neg/context-function-syntax.scala @@ -1,5 +1,5 @@ val test = - (using x: Int) => x // error // error + (using x: Int) => x // error // error // error val f = () ?=> 23 // error val g: ContextFunction0[Int] = ??? // ok diff --git a/tests/neg/deptypes.scala b/tests/neg/deptypes.scala index 39b1e42ccbca..a64143a05185 100644 --- a/tests/neg/deptypes.scala +++ b/tests/neg/deptypes.scala @@ -2,10 +2,10 @@ type Vec[T] = (n: Int) =>> Array[T] // error: not yet implemented -type Matrix[T](m: Int, n: Int) = Vec[Vec[T](n)](m) // error: not yet implemented +type Matrix[T](m: Int, n: Int) = Vec[Vec[T](n)](m) // error // error: not yet implemented -type Tensor2[T](m: Int)(n: Int) = Matrix[T](m, n) // error: not yet implemented +type Tensor2[T](m: Int)(n: Int) = Matrix[T](m, n) -val x: Vec[Int](10) = ??? // error: not yet implemented +val x: Vec[Int](10) = ??? val n = 10 -type T = Vec[String](n) // error: not yet implemented \ No newline at end of file +type T = Vec[String](n) diff --git a/tests/neg/i7751.scala b/tests/neg/i7751.scala index fd66e7d451be..18070cfd0551 100644 --- a/tests/neg/i7751.scala +++ b/tests/neg/i7751.scala @@ -1,3 +1,3 @@ import language.`3.3` -val a = Some(a=a,)=> // error // error // error // error +val a = Some(a=a,)=> // error // error // error val a = Some(x=y,)=> diff --git a/tests/pos/applied_constructor_types.scala b/tests/pos/applied_constructor_types.scala new file mode 100644 index 000000000000..8ae7f663dd5e --- /dev/null +++ b/tests/pos/applied_constructor_types.scala @@ -0,0 +1,55 @@ +import scala.language.experimental.modularity + +class Box(tracked val v: Any) +class C(tracked val x: Int) +class NC(tracked val c: C) +class NNC(tracked val c: NC) +class F[A](tracked val a: Int) +class G[A](tracked val a: A) +class NF[A](tracked val f: F[A]) + +class Person(val name: String, tracked val age: Int) +class PersonPrime(val name: String)(tracked val age: Int) +class PersonBis(tracked val name: String)(val age: Int) + +class Generic[A](val a: A) + +object O: + val m: Int = 27 + + class InnerClass(tracked val x: Int) + +object Test extends App { + val c: C(42) = C(42) + val nc: NC(C(42)) = NC(C(42)) + val nc1: NC(c) = NC(c) + val nnc: NNC(NC(C(42))) = NNC(NC(C(42))) + val f: F[Int](42) = F[Int](42) + val f2: F[Int](42) = F(42) + val f3: F(42) = F(42) + val g: G(42) = G(42) + + val n: Int = 27 + val c2: C(n) = C(n) + val c3: C(O.m) = C(O.m) + + val box: Box(O.InnerClass(42)) = Box(O.InnerClass(42)) + val box2: Box(O.InnerClass(n)) = Box(O.InnerClass(n)) + val box3: Box(O.InnerClass(O.m)) = Box(O.InnerClass(O.m)) + + val person: Person("Kasia", 27) = Person("Kasia", 27) // warn + val person1: Person("Kasia", n) = Person("Kasia", n) // warn + val person2: Person("Kasia", O.m) = Person("Kasia", O.m) // warn + + val personPrime: PersonPrime("Kasia")(27) = PersonPrime("Kasia")(27) // warn + val personPrime1: PersonPrime("Kasia")(n) = PersonPrime("Kasia")(n) // warn + val personPrime2: PersonPrime("Kasia")(O.m) = PersonPrime("Kasia")(O.m) // warn + + val personBis: PersonBis("Kasia")(27) = PersonBis("Kasia")(27) // warn + val personBis1: PersonBis("Kasia")(n) = PersonBis("Kasia")(n) // warn + val personBis2: PersonBis("Kasia")(O.m) = PersonBis("Kasia")(O.m) // warn + + val generic1: Generic(compiletime.erasedValue[Int]) = Generic(42) // warn + val generic2: Generic(??? : Int) = Generic(42) // warn + val generic3: Generic(43) = Generic(42) // warn +} diff --git a/tests/warn/applied_constructor_types.check b/tests/warn/applied_constructor_types.check new file mode 100644 index 000000000000..e1f3ec434db0 --- /dev/null +++ b/tests/warn/applied_constructor_types.check @@ -0,0 +1,4 @@ +-- [E209] Type Warning: tests/warn/applied_constructor_types.scala:6:10 ------------------------------------------------ +6 | val v1: UnspecificBox(4) = UnspecificBox(4) // warn + | ^^^^^^^^^^^^^^^^ + | Applied constructor type can only be used with classes that only have tracked parameters diff --git a/tests/warn/applied_constructor_types.scala b/tests/warn/applied_constructor_types.scala new file mode 100644 index 000000000000..d575dabe80f0 --- /dev/null +++ b/tests/warn/applied_constructor_types.scala @@ -0,0 +1,6 @@ +import scala.language.experimental.modularity + +class UnspecificBox(val v: Any) + +def test = + val v1: UnspecificBox(4) = UnspecificBox(4) // warn