diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index dbdb46aba334..d6db7348ddc1 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -39,14 +39,12 @@ object NamerOps: */ extension (tp: Type) def separateRefinements(cls: ClassSymbol, refinements: mutable.LinkedHashMap[Name, Type] | Null)(using Context): Type = - val widenSkolemsMap = new TypeMap: - def apply(tp: Type) = mapOver(tp.widenSkolem) tp match case RefinedType(tp1, rname, rinfo) => try tp1.separateRefinements(cls, refinements) finally if refinements != null then - val rinfo1 = widenSkolemsMap(rinfo) + val rinfo1 = rinfo.widenSkolems refinements(rname) = refinements.get(rname) match case Some(tp) => tp & rinfo1 case None => rinfo1 diff --git a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala index ab035db68d9f..eb526c2b4d85 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeUtils.scala @@ -53,6 +53,11 @@ class TypeUtils: case ps => ps.reduceLeft(AndType(_, _)) } + def widenSkolems(using Context): Type = + val widenSkolemsMap = new TypeMap: + def apply(tp: Type) = mapOver(tp.widenSkolem) + widenSkolemsMap(self) + /** The element types of this tuple type, which can be made up of EmptyTuple, TupleX and `*:` pairs */ def tupleElementTypes(using Context): Option[List[Type]] = @@ -134,7 +139,7 @@ class TypeUtils: case t => throw TypeError(em"Malformed NamedTuple: names must be string types, but $t was found.") val values = vals.tupleElementTypesUpTo(bound, normalize).getOrElse(Nil) names.zip(values) - + (if normalize then self.normalized else self).dealias match // for desugaring and printer, ignore derived types to avoid infinite recursion in NamedTuple.unapply case defn.NamedTupleDirect(nmes, vals) => extractNamesTypes(nmes, vals) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 3a43f53f3ca4..e396839c972e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1331,16 +1331,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 @@ -2051,7 +2041,7 @@ object Parsers { /** SimpleType ::= SimpleLiteral * | ‘?’ TypeBounds * | SimpleType1 - * | SimpleType ‘(’ Singletons ‘)’ -- under language.experimental.dependent, checked in Typer + * | SimpleType ‘(’ Singletons ‘)’ * Singletons ::= Singleton {‘,’ Singleton} */ def simpleType(): Tree = @@ -2083,11 +2073,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 cafe99973701..3f95789789bd 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -225,6 +225,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case FormatInterpolationErrorID // errorNumber: 209 case ValueClassCannotExtendAliasOfAnyValID // errorNumber: 210 case MatchIsNotPartialFunctionID // errorNumber: 211 + case OnlyFullyDependentAppliedConstructorTypeID // errorNumber: 212 + case PointlessAppliedConstructorTypeID // errorNumber: 213 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 ab9fc507b052..0db766d4e337 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3487,3 +3487,24 @@ class MatchIsNotPartialFunction(using Context) extends SyntaxMsg(MatchIsNotParti | |Efficient operations will use `applyOrElse` to avoid computing the match twice, |but the `apply` body would be executed "per element" in the example.""" + +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 where all parameters in the first parameter list are tracked" + + 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 54f033fe6fd8..07ddb37d93b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1282,6 +1282,10 @@ trait Applications extends Compatibility { } else { val app = tree.fun match + case _ if ctx.mode.is(Mode.Type) && Feature.enabled(Feature.modularity) && !ctx.isAfterTyper => + untpd.methPart(tree.fun) match + case Select(nw @ New(_), _) => typedAppliedConstructorType(nw, tree.args, tree) + case _ => realApply case untpd.TypeApply(_: untpd.SplicePattern, _) if Feature.quotedPatternsWithPolymorphicFunctionsEnabled => typedAppliedSpliceWithTypes(tree, pt) case _: untpd.SplicePattern => typedAppliedSplice(tree, pt) @@ -1715,6 +1719,28 @@ trait Applications extends Compatibility { def typedUnApply(tree: untpd.UnApply, selType: Type)(using Context): UnApply = throw new UnsupportedOperationException("cannot type check an UnApply node") + /** Typecheck an applied constructor type – An Apply node in Type mode. + * This expands to the type this term would have if it were typed as an expression. + * + * e.g. + * ```scala + * // class C(tracked val v: Any) + * val c: C(42) = ??? + * ``` + */ + def typedAppliedConstructorType(nw: untpd.New, args: List[untpd.Tree], tree: untpd.Apply)(using Context) = + val tree1 = typedExpr(tree) + val preciseTp = tree1.tpe.widenSkolems + val classTp = typedType(nw.tpt).tpe + def classSymbolHasOnlyTrackedParameters = + !classTp.classSymbol.primaryConstructor.paramSymss.nestedExists: param => + param.isTerm && !param.is(Tracked) + if !preciseTp.isError && !classSymbolHasOnlyTrackedParameters then + report.warning(OnlyFullyDependentAppliedConstructorType(), tree.srcPos) + if !preciseTp.isError && (preciseTp frozen_=:= classTp) then + report.warning(PointlessAppliedConstructorType(nw.tpt, args, classTp), tree.srcPos) + TypeTree(preciseTp) + /** 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 58119981dfc4..a6254c0d5c00 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 79aaf367851a..428568ecc795 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2557,17 +2557,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(_)) @@ -2691,7 +2683,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) 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 6c144f436690..5a0e543a6d42 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..580044ce4d66 100644 --- a/docs/_docs/reference/experimental/modularity.md +++ b/docs/_docs/reference/experimental/modularity.md @@ -196,6 +196,43 @@ 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`. + +The arguments are used to typecheck the whole type, as if it was a normal +constructor application. For classes with `tracked` parameters this will mean +that the resulting type will have a refinement for each `tracked` parameter. + +For example, given the following class definition: +```scala +class Person(tracked val name: String, tracked val age: Int) +``` +**Type** `Person("Kasia", 27)` will be translated to `Person { val name: "Kasia"; val age: 27 }`. ## 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..b2d2b82f4e09 --- /dev/null +++ b/tests/warn/applied_constructor_types.check @@ -0,0 +1,4 @@ +-- [E212] 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 where all parameters in the first parameter list are tracked 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