From 87cdbc8c7851830c16de0c8c828cde0f7cd0ce48 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 1 Oct 2024 11:39:00 +0200 Subject: [PATCH 1/5] Make named tuples a standard feature - Deprecate experimental language import - Make named tuple features conditional on -source >= 3.6 instead - Make the NamedTuple object non-experimental. - Move NamedTuple it to src-bootstrapped since it relies on clause interleaving which is only standard in 3.6 as well. - Drop the experimental.namedTuple import from tests --- .../src/dotty/tools/dotc/config/Feature.scala | 2 - .../dotc/config/ScalaSettingsProperties.scala | 2 +- .../dotty/tools/dotc/parsing/Parsers.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../scala/NamedTuple.scala | 4 - .../runtime/stdLibPatches/language.scala | 1 + .../pc/tests/completion/CompletionSuite.scala | 8 +- tests/neg/i20517.check | 14 +-- tests/neg/i20517.scala | 1 - tests/neg/named-tuple-selectable.scala | 2 - tests/neg/named-tuples-2.check | 8 +- tests/neg/named-tuples-2.scala | 1 - tests/neg/named-tuples-3.check | 4 +- tests/neg/named-tuples-3.scala | 2 - tests/neg/named-tuples.check | 92 +++++++++---------- tests/neg/named-tuples.scala | 3 +- tests/new/test.scala | 2 - tests/pos/fieldsOf.scala | 2 - tests/pos/i20377.scala | 1 - tests/pos/i21300.scala | 6 +- tests/pos/named-tuple-combinators.scala | 1 - tests/pos/named-tuple-selectable.scala | 1 - tests/pos/named-tuple-selections.scala | 1 - tests/pos/named-tuple-unstable.scala | 1 - tests/pos/named-tuple-widen.scala | 1 - tests/pos/named-tuples-ops-mirror.scala | 1 - tests/pos/named-tuples1.scala | 1 - tests/pos/namedtuple-src-incompat.scala | 1 - tests/pos/tuple-ops.scala | 1 - .../stdlibExperimentalDefinitions.scala | 6 -- tests/run/named-patmatch.scala | 1 - tests/run/named-patterns.scala | 1 - tests/run/named-tuple-ops.scala | 1 - tests/run/named-tuples-xxl.scala | 1 - tests/run/named-tuples.scala | 1 - tests/run/tyql.scala | 1 - 36 files changed, 70 insertions(+), 112 deletions(-) rename library/{src => src-bootstrapped}/scala/NamedTuple.scala (98%) diff --git a/compiler/src/dotty/tools/dotc/config/Feature.scala b/compiler/src/dotty/tools/dotc/config/Feature.scala index 8b9a64924ace..ad20bab46c1e 100644 --- a/compiler/src/dotty/tools/dotc/config/Feature.scala +++ b/compiler/src/dotty/tools/dotc/config/Feature.scala @@ -34,7 +34,6 @@ object Feature: val pureFunctions = experimental("pureFunctions") val captureChecking = experimental("captureChecking") val into = experimental("into") - val namedTuples = experimental("namedTuples") val modularity = experimental("modularity") val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors") val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions") @@ -66,7 +65,6 @@ object Feature: (pureFunctions, "Enable pure functions for capture checking"), (captureChecking, "Enable experimental capture checking"), (into, "Allow into modifier on parameter types"), - (namedTuples, "Allow named tuples"), (modularity, "Enable experimental modularity features"), (betterMatchTypeExtractors, "Enable better match type extractors"), (betterFors, "Enable improvements in `for` comprehensions") diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala index 022916cc9f53..e42d2d53529e 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettingsProperties.scala @@ -25,7 +25,7 @@ object ScalaSettingsProperties: ScalaRelease.values.toList.map(_.show) def supportedSourceVersions: List[String] = - SourceVersion.values.toList.map(_.toString) + (SourceVersion.values.toList.diff(SourceVersion.illegalSourceVersionNames)).toList.map(_.toString) def supportedLanguageFeatures: List[ChoiceWithHelp[String]] = Feature.values.map((n, d) => ChoiceWithHelp(n.toString, d)) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8a173faa3cec..3acaf009bd36 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -651,7 +651,7 @@ object Parsers { else leading :: Nil def maybeNamed(op: () => Tree): () => Tree = () => - if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then + if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then atSpan(in.offset): val name = ident() in.nextToken() @@ -2137,7 +2137,7 @@ object Parsers { if namedOK && isIdent && in.lookahead.token == EQUALS then commaSeparated(() => namedArgType()) - else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then + else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then commaSeparated(() => namedElem()) else commaSeparated(() => argType()) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 159ce8354a30..93ea3f3c3ae0 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -789,7 +789,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def tryNamedTupleSelection() = val namedTupleElems = qual.tpe.widenDealias.namedTupleElementTypes val nameIdx = namedTupleElems.indexWhere(_._1 == selName) - if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then + if nameIdx >= 0 && sourceVersion.isAtLeast(`3.6`) then typed( untpd.Apply( untpd.Select(untpd.TypedSplice(qual), nme.apply), diff --git a/library/src/scala/NamedTuple.scala b/library/src-bootstrapped/scala/NamedTuple.scala similarity index 98% rename from library/src/scala/NamedTuple.scala rename to library/src-bootstrapped/scala/NamedTuple.scala index f237d1d487fe..71bcd26a16e2 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src-bootstrapped/scala/NamedTuple.scala @@ -1,9 +1,6 @@ package scala -import scala.language.experimental.clauseInterleaving -import annotation.experimental import compiletime.ops.boolean.* -@experimental object NamedTuple: /** The type to which named tuples get mapped to. For instance, @@ -133,7 +130,6 @@ object NamedTuple: end NamedTuple /** Separate from NamedTuple object so that we can match on the opaque type NamedTuple. */ -@experimental object NamedTupleDecomposition: import NamedTuple.* extension [N <: Tuple, V <: Tuple](x: NamedTuple[N, V]) diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index 547710d55293..b8d990cf56f5 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -97,6 +97,7 @@ object language: * @see [[https://dotty.epfl.ch/docs/reference/experimental/named-tuples]] */ @compileTimeOnly("`namedTuples` can only be used at compile time in import statements") + @deprecated("The experimental.namedTuples language import is no longer needed since the feature is now standard", since = "3.6") object namedTuples /** Experimental support for new features for better modularity, including diff --git a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala index 1cd26858b934..57975d2c8e98 100644 --- a/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala +++ b/presentation-compiler/test/dotty/tools/pc/tests/completion/CompletionSuite.scala @@ -1988,8 +1988,7 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `namedTuple completions` = check( - """|import scala.language.experimental.namedTuples - |import scala.NamedTuple.* + """|import scala.NamedTuple.* | |val person = (name = "Jamie", city = "Lausanne") | @@ -2000,8 +1999,7 @@ class CompletionSuite extends BaseCompletionSuite: @Test def `Selectable with namedTuple Fields member` = check( - """|import scala.language.experimental.namedTuples - |import scala.NamedTuple.* + """|import scala.NamedTuple.* | |class NamedTupleSelectable extends Selectable { | type Fields <: AnyNamedTuple @@ -2091,7 +2089,7 @@ class CompletionSuite extends BaseCompletionSuite: |""".stripMargin ) - @Test def `conflict-3` = + @Test def `conflict-3` = check( """|package a |object A { diff --git a/tests/neg/i20517.check b/tests/neg/i20517.check index 55aeff46572b..119c34025ee0 100644 --- a/tests/neg/i20517.check +++ b/tests/neg/i20517.check @@ -1,7 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg/i20517.scala:10:43 ------------------------------------------------------------ -10 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error - | ^^^^^^^^^^^ - | Found: (elem : String) - | Required: NamedTuple.From[(foo : Foo[Any])] - | - | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg/i20517.scala:9:43 ------------------------------------------------------------- +9 | def dep(foo: Foo[Any]): From[foo.type] = (elem = "") // error + | ^^^^^^^^^^^ + | Found: (elem : String) + | Required: NamedTuple.From[(foo : Foo[Any])] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i20517.scala b/tests/neg/i20517.scala index 11c4432434dd..342a7d86ca7e 100644 --- a/tests/neg/i20517.scala +++ b/tests/neg/i20517.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples import NamedTuple.From case class Foo[+T](elem: T) diff --git a/tests/neg/named-tuple-selectable.scala b/tests/neg/named-tuple-selectable.scala index 5cf7e68654ef..c81eba1237ff 100644 --- a/tests/neg/named-tuple-selectable.scala +++ b/tests/neg/named-tuple-selectable.scala @@ -1,5 +1,3 @@ -import scala.language.experimental.namedTuples - class FromFields extends Selectable: type Fields = (i: Int) def selectDynamic(key: String) = diff --git a/tests/neg/named-tuples-2.check b/tests/neg/named-tuples-2.check index 0a52d5f3989b..daa1c0d69069 100644 --- a/tests/neg/named-tuples-2.check +++ b/tests/neg/named-tuples-2.check @@ -1,8 +1,8 @@ --- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- -5 | case (name, age) => () // error +-- Error: tests/neg/named-tuples-2.scala:4:9 --------------------------------------------------------------------------- +4 | case (name, age) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple2 --- Error: tests/neg/named-tuples-2.scala:6:9 --------------------------------------------------------------------------- -6 | case (n, a, m, x) => () // error +-- Error: tests/neg/named-tuples-2.scala:5:9 --------------------------------------------------------------------------- +5 | case (n, a, m, x) => () // error | ^ | this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4 diff --git a/tests/neg/named-tuples-2.scala b/tests/neg/named-tuples-2.scala index 0507891e0549..b3917d9ad57c 100644 --- a/tests/neg/named-tuples-2.scala +++ b/tests/neg/named-tuples-2.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples def Test = val person = (name = "Bob", age = 33, married = true) person match diff --git a/tests/neg/named-tuples-3.check b/tests/neg/named-tuples-3.check index 2091c36191c0..2809836b4803 100644 --- a/tests/neg/named-tuples-3.check +++ b/tests/neg/named-tuples-3.check @@ -1,5 +1,5 @@ --- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 ----------------------------------------------------- -7 |val p: Person = f // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 ----------------------------------------------------- +5 |val p: Person = f // error | ^ | Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)] | Required: Person diff --git a/tests/neg/named-tuples-3.scala b/tests/neg/named-tuples-3.scala index 0f1215338b0a..21e6ed9b3741 100644 --- a/tests/neg/named-tuples-3.scala +++ b/tests/neg/named-tuples-3.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ??? type Person = (name: Int, age: String) diff --git a/tests/neg/named-tuples.check b/tests/neg/named-tuples.check index db3cc703722f..8ec958b6a75d 100644 --- a/tests/neg/named-tuples.check +++ b/tests/neg/named-tuples.check @@ -1,101 +1,101 @@ --- Error: tests/neg/named-tuples.scala:9:19 ---------------------------------------------------------------------------- -9 | val illformed = (_2 = 2) // error +-- Error: tests/neg/named-tuples.scala:8:19 ---------------------------------------------------------------------------- +8 | val illformed = (_2 = 2) // error | ^^^^^^ | _2 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:10:20 --------------------------------------------------------------------------- -10 | type Illformed = (_1: Int) // error - | ^^^^^^^ - | _1 cannot be used as the name of a tuple element because it is a regular tuple selector --- Error: tests/neg/named-tuples.scala:11:40 --------------------------------------------------------------------------- -11 | val illformed2 = (name = "", age = 0, name = true) // error +-- Error: tests/neg/named-tuples.scala:9:20 ---------------------------------------------------------------------------- +9 | type Illformed = (_1: Int) // error + | ^^^^^^^ + | _1 cannot be used as the name of a tuple element because it is a regular tuple selector +-- Error: tests/neg/named-tuples.scala:10:40 --------------------------------------------------------------------------- +10 | val illformed2 = (name = "", age = 0, name = true) // error | ^^^^^^^^^^^ | Duplicate tuple element name --- Error: tests/neg/named-tuples.scala:12:45 --------------------------------------------------------------------------- -12 | type Illformed2 = (name: String, age: Int, name: Boolean) // error +-- Error: tests/neg/named-tuples.scala:11:45 --------------------------------------------------------------------------- +11 | type Illformed2 = (name: String, age: Int, name: Boolean) // error | ^^^^^^^^^^^^^ | Duplicate tuple element name --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:20 ------------------------------------------------------ -20 | val _: NameOnly = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:19:20 ------------------------------------------------------ +19 | val _: NameOnly = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: Test.NameOnly | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:21:18 ------------------------------------------------------ -21 | val _: Person = nameOnly // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:20:18 ------------------------------------------------------ +20 | val _: Person = nameOnly // error | ^^^^^^^^ | Found: (Test.nameOnly : (name : String)) | Required: Test.Person | | longer explanation available when compiling with `-explain` --- [E172] Type Error: tests/neg/named-tuples.scala:22:41 --------------------------------------------------------------- -22 | val _: Person = (name = "") ++ nameOnly // error +-- [E172] Type Error: tests/neg/named-tuples.scala:21:41 --------------------------------------------------------------- +21 | val _: Person = (name = "") ++ nameOnly // error | ^ | Cannot prove that Tuple.Disjoint[Tuple1[("name" : String)], Tuple1[("name" : String)]] =:= (true : Boolean). --- [E008] Not Found Error: tests/neg/named-tuples.scala:23:9 ----------------------------------------------------------- -23 | person._1 // error +-- [E008] Not Found Error: tests/neg/named-tuples.scala:22:9 ----------------------------------------------------------- +22 | person._1 // error | ^^^^^^^^^ | value _1 is not a member of (name : String, age : Int) --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:25:36 ------------------------------------------------------ -25 | val _: (age: Int, name: String) = person // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:24:36 ------------------------------------------------------ +24 | val _: (age: Int, name: String) = person // error | ^^^^^^ | Found: (Test.person : (name : String, age : Int)) | Required: (age : Int, name : String) | | longer explanation available when compiling with `-explain` --- Error: tests/neg/named-tuples.scala:27:17 --------------------------------------------------------------------------- -27 | val (name = x, agee = y) = person // error +-- Error: tests/neg/named-tuples.scala:26:17 --------------------------------------------------------------------------- +26 | val (name = x, agee = y) = person // error | ^^^^^^^^ | No element named `agee` is defined in selector type (name : String, age : Int) --- Error: tests/neg/named-tuples.scala:30:10 --------------------------------------------------------------------------- -30 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:29:10 --------------------------------------------------------------------------- +29 | case (name = n, age = a) => () // error // error | ^^^^^^^^ | No element named `name` is defined in selector type (String, Int) --- Error: tests/neg/named-tuples.scala:30:20 --------------------------------------------------------------------------- -30 | case (name = n, age = a) => () // error // error +-- Error: tests/neg/named-tuples.scala:29:20 --------------------------------------------------------------------------- +29 | case (name = n, age = a) => () // error // error | ^^^^^^^ | No element named `age` is defined in selector type (String, Int) --- [E172] Type Error: tests/neg/named-tuples.scala:32:27 --------------------------------------------------------------- -32 | val pp = person ++ (1, 2) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:31:27 --------------------------------------------------------------- +31 | val pp = person ++ (1, 2) // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:35:18 --------------------------------------------------------------- -35 | person ++ (1, 2) match // error +-- [E172] Type Error: tests/neg/named-tuples.scala:34:18 --------------------------------------------------------------- +34 | person ++ (1, 2) match // error | ^ | Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), Tuple] =:= (true : Boolean). --- Error: tests/neg/named-tuples.scala:38:17 --------------------------------------------------------------------------- -38 | val bad = ("", age = 10) // error +-- Error: tests/neg/named-tuples.scala:37:17 --------------------------------------------------------------------------- +37 | val bad = ("", age = 10) // error | ^^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:41:20 --------------------------------------------------------------------------- -41 | case (name = n, age) => () // error +-- Error: tests/neg/named-tuples.scala:40:20 --------------------------------------------------------------------------- +40 | case (name = n, age) => () // error | ^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:42:16 --------------------------------------------------------------------------- -42 | case (name, age = a) => () // error +-- Error: tests/neg/named-tuples.scala:41:16 --------------------------------------------------------------------------- +41 | case (name, age = a) => () // error | ^^^^^^^ | Illegal combination of named and unnamed tuple elements --- Error: tests/neg/named-tuples.scala:45:10 --------------------------------------------------------------------------- -45 | case (age = x) => // error +-- Error: tests/neg/named-tuples.scala:44:10 --------------------------------------------------------------------------- +44 | case (age = x) => // error | ^^^^^^^ | No element named `age` is defined in selector type Tuple --- [E172] Type Error: tests/neg/named-tuples.scala:47:27 --------------------------------------------------------------- -47 | val p2 = person ++ person // error +-- [E172] Type Error: tests/neg/named-tuples.scala:46:27 --------------------------------------------------------------- +46 | val p2 = person ++ person // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("name" : String), ("age" : String))] =:= (true : Boolean). --- [E172] Type Error: tests/neg/named-tuples.scala:48:43 --------------------------------------------------------------- -48 | val p3 = person ++ (first = 11, age = 33) // error +-- [E172] Type Error: tests/neg/named-tuples.scala:47:43 --------------------------------------------------------------- +47 | val p3 = person ++ (first = 11, age = 33) // error | ^ |Cannot prove that Tuple.Disjoint[(("name" : String), ("age" : String)), (("first" : String), ("age" : String))] =:= (true : Boolean). --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:50:22 ------------------------------------------------------ -50 | val p5 = person.zip((first = 11, age = 33)) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:49:22 ------------------------------------------------------ +49 | val p5 = person.zip((first = 11, age = 33)) // error | ^^^^^^^^^^^^^^^^^^^^^^ | Found: (first : Int, age : Int) | Required: NamedTuple.NamedTuple[(("name" : String), ("age" : String)), Tuple] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:61:32 ------------------------------------------------------ -61 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error +-- [E007] Type Mismatch Error: tests/neg/named-tuples.scala:60:32 ------------------------------------------------------ +60 | val typo: (name: ?, age: ?) = (name = "he", ag = 1) // error | ^^^^^^^^^^^^^^^^^^^^^ | Found: (name : String, ag : Int) | Required: (name : ?, age : ?) diff --git a/tests/neg/named-tuples.scala b/tests/neg/named-tuples.scala index 8f78f7915206..daae6e26bac2 100644 --- a/tests/neg/named-tuples.scala +++ b/tests/neg/named-tuples.scala @@ -1,7 +1,6 @@ import annotation.experimental -import language.experimental.namedTuples -@experimental object Test: +object Test: type Person = (name: String, age: Int) val person = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/new/test.scala b/tests/new/test.scala index 18644422ab06..dc1891f3525c 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - type Person = (name: String, age: Int) trait A: diff --git a/tests/pos/fieldsOf.scala b/tests/pos/fieldsOf.scala index 2594dae2cbf7..08f20a1f7e8e 100644 --- a/tests/pos/fieldsOf.scala +++ b/tests/pos/fieldsOf.scala @@ -1,5 +1,3 @@ -import language.experimental.namedTuples - case class Person(name: String, age: Int) type PF = NamedTuple.From[Person] diff --git a/tests/pos/i20377.scala b/tests/pos/i20377.scala index 7a4c0fccfd7e..27b546402467 100644 --- a/tests/pos/i20377.scala +++ b/tests/pos/i20377.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} // Repros for bugs or questions diff --git a/tests/pos/i21300.scala b/tests/pos/i21300.scala index 22859482ef98..e7c7965b0e9a 100644 --- a/tests/pos/i21300.scala +++ b/tests/pos/i21300.scala @@ -1,17 +1,15 @@ -import scala.language.experimental.namedTuples - class Test[S <: String & Singleton](name: S): type NT = NamedTuple.NamedTuple[(S, "foo"), (Int, Long)] def nt: NT = ??? type Name = S - + type NT2 = NamedTuple.NamedTuple[(Name, "foo"), (Int, Long)] def nt2: NT2 = ??? def test = val foo = new Test("bar") - + foo.nt.bar foo.nt2.bar diff --git a/tests/pos/named-tuple-combinators.scala b/tests/pos/named-tuple-combinators.scala index a5134b2e7d26..c027ba688d02 100644 --- a/tests/pos/named-tuple-combinators.scala +++ b/tests/pos/named-tuple-combinators.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples object Test: // original code from issue https://github.com/scala/scala3/issues/20427 diff --git a/tests/pos/named-tuple-selectable.scala b/tests/pos/named-tuple-selectable.scala index be5f0400e58c..0e1324f70ae6 100644 --- a/tests/pos/named-tuple-selectable.scala +++ b/tests/pos/named-tuple-selectable.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples class FromFields extends Selectable: type Fields = (xs: List[Int], poly: [T] => (x: List[T]) => Option[T]) diff --git a/tests/pos/named-tuple-selections.scala b/tests/pos/named-tuple-selections.scala index c3569f21b323..7b73daad2e72 100644 --- a/tests/pos/named-tuple-selections.scala +++ b/tests/pos/named-tuple-selections.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples object Test1: // original code from issue https://github.com/scala/scala3/issues/20439 diff --git a/tests/pos/named-tuple-unstable.scala b/tests/pos/named-tuple-unstable.scala index 6a6a36732a14..d15bdc578a3a 100644 --- a/tests/pos/named-tuple-unstable.scala +++ b/tests/pos/named-tuple-unstable.scala @@ -1,4 +1,3 @@ -import scala.language.experimental.namedTuples import NamedTuple.{AnyNamedTuple, NamedTuple} trait Foo extends Selectable: diff --git a/tests/pos/named-tuple-widen.scala b/tests/pos/named-tuple-widen.scala index 410832e04c17..cc12a5f09b16 100644 --- a/tests/pos/named-tuple-widen.scala +++ b/tests/pos/named-tuple-widen.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples class A class B diff --git a/tests/pos/named-tuples-ops-mirror.scala b/tests/pos/named-tuples-ops-mirror.scala index f66eb89534fb..b8745cf785d5 100644 --- a/tests/pos/named-tuples-ops-mirror.scala +++ b/tests/pos/named-tuples-ops-mirror.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.* @FailsWith[HttpError] diff --git a/tests/pos/named-tuples1.scala b/tests/pos/named-tuples1.scala index 58e3fc065e61..532f1df7efd4 100644 --- a/tests/pos/named-tuples1.scala +++ b/tests/pos/named-tuples1.scala @@ -1,5 +1,4 @@ import annotation.experimental -import language.experimental.namedTuples @main def Test = val bob = (name = "Bob", age = 33): (name: String, age: Int) diff --git a/tests/pos/namedtuple-src-incompat.scala b/tests/pos/namedtuple-src-incompat.scala index 57451a4321b7..76eb5e4aa850 100644 --- a/tests/pos/namedtuple-src-incompat.scala +++ b/tests/pos/namedtuple-src-incompat.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples var age = 22 val x = (age = 1) val _: (age: Int) = x diff --git a/tests/pos/tuple-ops.scala b/tests/pos/tuple-ops.scala index 739b1ebeeb02..e89c0e8e51aa 100644 --- a/tests/pos/tuple-ops.scala +++ b/tests/pos/tuple-ops.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import Tuple.* def test = diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 15ccd38f860c..7df4a05e6973 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -74,12 +74,6 @@ val experimentalDefinitionInLibrary = Set( // New feature: fromNullable for explicit nulls "scala.Predef$.fromNullable", - // New feature: named tuples - "scala.NamedTuple", - "scala.NamedTuple$", - "scala.NamedTupleDecomposition", - "scala.NamedTupleDecomposition$", - // New feature: modularity "scala.Precise", "scala.annotation.internal.WitnessNames", diff --git a/tests/run/named-patmatch.scala b/tests/run/named-patmatch.scala index e62497e4aa8f..6fe1934f008e 100644 --- a/tests/run/named-patmatch.scala +++ b/tests/run/named-patmatch.scala @@ -1,5 +1,4 @@ import annotation.experimental -import language.experimental.namedTuples @main def Test = locally: diff --git a/tests/run/named-patterns.scala b/tests/run/named-patterns.scala index 7c24dc8d683a..e92bbf751c22 100644 --- a/tests/run/named-patterns.scala +++ b/tests/run/named-patterns.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples object Test1: class Person(val name: String, val age: Int) diff --git a/tests/run/named-tuple-ops.scala b/tests/run/named-tuple-ops.scala index 076ab5028c6c..8c6db6f2fa1c 100644 --- a/tests/run/named-tuple-ops.scala +++ b/tests/run/named-tuple-ops.scala @@ -1,5 +1,4 @@ //> using options -source future -import language.experimental.namedTuples import scala.compiletime.asMatchable type City = (name: String, zip: Int, pop: Int) diff --git a/tests/run/named-tuples-xxl.scala b/tests/run/named-tuples-xxl.scala index 3a0a1e5e1294..8c831fb1d223 100644 --- a/tests/run/named-tuples-xxl.scala +++ b/tests/run/named-tuples-xxl.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.toTuple type Person = ( diff --git a/tests/run/named-tuples.scala b/tests/run/named-tuples.scala index 406c6195cf0f..c99393a403b3 100644 --- a/tests/run/named-tuples.scala +++ b/tests/run/named-tuples.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.* type Person = (name: String, age: Int) diff --git a/tests/run/tyql.scala b/tests/run/tyql.scala index 35777e9a4c13..b81e0c5bf6dd 100644 --- a/tests/run/tyql.scala +++ b/tests/run/tyql.scala @@ -1,4 +1,3 @@ -import language.experimental.namedTuples import NamedTuple.{NamedTuple, AnyNamedTuple} /* This is a demonstrator that shows how to map regular for expressions to From 6263944205c0c5b784d7e36ac6beb2f4250ef90c Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 1 Oct 2024 12:17:42 +0200 Subject: [PATCH 2/5] Update reference docs --- .../named-tuples.md | 146 +++++++++--------- docs/sidebar.yml | 2 +- 2 files changed, 75 insertions(+), 73 deletions(-) rename docs/_docs/reference/{experimental => other-new-features}/named-tuples.md (88%) diff --git a/docs/_docs/reference/experimental/named-tuples.md b/docs/_docs/reference/other-new-features/named-tuples.md similarity index 88% rename from docs/_docs/reference/experimental/named-tuples.md rename to docs/_docs/reference/other-new-features/named-tuples.md index 3867b4d13f15..bf1ae4ca7046 100644 --- a/docs/_docs/reference/experimental/named-tuples.md +++ b/docs/_docs/reference/other-new-features/named-tuples.md @@ -1,10 +1,10 @@ --- layout: doc-page title: "Named Tuples" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html --- -The elements of a tuple can now be named. Example: +Starting in Scala 3.6, the elements of a tuple can be named. Example: ```scala type Person = (name: String, age: Int) val Bob: Person = (name = "Bob", age = 33) @@ -94,6 +94,24 @@ Bob match case (age = x, name = y) => ... ``` +### Pattern Matching with Named Fields in General + +We allow named patterns not just for named tuples but also for case classes. For instance: +```scala +city match + case c @ City(name = "London") => println(p.population) + case City(name = n, zip = 1026, population = pop) => println(pop) +``` + +Named constructor patterns are analogous to named tuple patterns. In both cases + + - every name must match the name some field of the selector, + - names can come in any order, + - not all fields of the selector need to be matched. + +Named patterns are compatible with extensible pattern matching simply because +`unapply` results can be named tuples. + ### Expansion Named tuples are in essence just a convenient syntax for regular tuples. In the internal representation, a named tuple type is represented at compile time as a pair of two tuples. One tuple contains the names as literal constant string types, the other contains the element types. The runtime representation of a named tuples consists of just the element values, whereas the names are forgotten. This is achieved by declaring `NamedTuple` @@ -119,6 +137,47 @@ The translation of named tuples to instances of `NamedTuple` is fixed by the spe - All tuple operations also work with named tuples "out of the box". - Macro libraries can rely on this expansion. +### Computed Field Names + +The `Selectable` trait now has a `Fields` type member that can be instantiated +to a named tuple. + +```scala +trait Selectable: + type Fields <: NamedTuple.AnyNamedTuple +``` + +If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type, +then the available fields and their types will be defined by that type. Assume `n: T` +is an element of the `Fields` type in some class `C` that implements `Selectable`, +that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`. +Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`. + +It is the task of the implementation of `selectDynamic` in `C` to ensure that its +computed result conforms to the predicted type `T` + +As an example, assume we have a query type `Q[T]` defined as follows: + +```scala +trait Q[T] extends Selectable: + type Fields = NamedTuple.Map[NamedTuple.From[T], Q] + def selectDynamic(fieldName: String) = ... +``` + +Assume in the user domain: +```scala +case class City(zipCode: Int, name: String, population: Int) +val city: Q[City] +``` +Then +```scala +city.zipCode +``` +has type `Q[Int]` and it expands to +```scala +city.selectDynamic("zipCode").asInstanceOf[Q[Int]] +``` + ### The NamedTuple.From Type The `NamedTuple` object contains a type definition @@ -137,33 +196,36 @@ then `NamedTuple.From[City]` is the named tuple (zip: Int, name: String, population: Int) ``` The same works for enum cases expanding to case classes, abstract types with case classes as upper bound, alias types expanding to case classes -and singleton types with case classes as underlying type. +and singleton types with case classes as underlying type (in terms of the implementation, the `classSymbol` of a type must be a case class). `From` is also defined on named tuples. If `NT` is a named tuple type, then `From[NT] = NT`. +### Operations on Named Tuples + +The operations on named tuples are defined in object [scala.NamedTuple](https://www.scala-lang.org/api/3.x/scala/NamedTuple$.html). + ### Restrictions -The following restrictions apply to named tuple elements: +The following restrictions apply to named tuples and named pattern arguments: - 1. Either all elements of a tuple are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error: + 1. Either all elements of a tuple or constructor pattern are named or none are named. It is illegal to mix named and unnamed elements in a tuple. For instance, the following is in error: ```scala val illFormed1 = ("Bob", age = 33) // error ``` - 2. Each element name in a named tuple must be unique. For instance, the following is in error: + 2. Each element name in a named tuple or constructor pattern must be unique. For instance, the following is in error: ```scala val illFormed2 = (name = "", age = 0, name = true) // error ``` - 3. Named tuples can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error: + 3. Named tuples and case classes can be matched with either named or regular patterns. But regular tuples and other selector types can only be matched with regular tuple patterns. For instance, the following is in error: ```scala (tuple: Tuple) match case (age = x) => // error ``` - 4. Regular selector names `_1`, `_2`, ... are not allowed as names in named tuples. +## Syntax Changes -### Syntax - -The syntax of Scala is extended as follows to support named tuples: +The syntax of Scala is extended as follows to support named tuples and +named constructor arguments: ``` SimpleType ::= ... | ‘(’ NameAndType {‘,’ NameAndType} ‘)’ @@ -178,31 +240,11 @@ Patterns ::= Pattern {‘,’ Pattern} NamedPattern ::= id '=' Pattern ``` -### Named Pattern Matching - -We allow named patterns not just for named tuples but also for case classes. -For instance: -```scala -city match - case c @ City(name = "London") => println(p.population) - case City(name = n, zip = 1026, population = pop) => println(pop) -``` - -Named constructor patterns are analogous to named tuple patterns. In both cases - - - either all fields are named or none is, - - every name must match the name some field of the selector, - - names can come in any order, - - not all fields of the selector need to be matched. - -This revives SIP 43, with a much simpler desugaring than originally proposed. -Named patterns are compatible with extensible pattern matching simply because -`unapply` results can be named tuples. - ### Source Incompatibilities There are some source incompatibilities involving named tuples of length one. First, what was previously classified as an assignment could now be interpreted as a named tuple. Example: + ```scala var age: Int (age = 1) @@ -221,43 +263,3 @@ c f (age = 1) ``` will now construct a tuple as second operand instead of passing a named parameter. -### Computed Field Names - -The `Selectable` trait now has a `Fields` type member that can be instantiated -to a named tuple. - -```scala -trait Selectable: - type Fields <: NamedTuple.AnyNamedTuple -``` - -If `Fields` is instantiated in a subclass of `Selectable` to some named tuple type, -then the available fields and their types will be defined by that type. Assume `n: T` -is an element of the `Fields` type in some class `C` that implements `Selectable`, -that `c: C`, and that `n` is not otherwise legal as a name of a selection on `c`. -Then `c.n` is a legal selection, which expands to `c.selectDynamic("n").asInstanceOf[T]`. - -It is the task of the implementation of `selectDynamic` in `C` to ensure that its -computed result conforms to the predicted type `T` - -As an example, assume we have a query type `Q[T]` defined as follows: - -```scala -trait Q[T] extends Selectable: - type Fields = NamedTuple.Map[NamedTuple.From[T], Q] - def selectDynamic(fieldName: String) = ... -``` - -Assume in the user domain: -```scala -case class City(zipCode: Int, name: String, population: Int) -val city: Q[City] -``` -Then -```scala -city.zipCode -``` -has type `Q[Int]` and it expands to -```scala -city.selectDynamic("zipCode").asInstanceOf[Q[Int]] -``` diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 5048669ef664..0e06dbf41029 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -68,6 +68,7 @@ subsection: - page: reference/other-new-features/export.md - page: reference/other-new-features/opaques.md - page: reference/other-new-features/opaques-details.md + - page: reference/other-new-features/named-tuples.md - page: reference/other-new-features/open-classes.md - page: reference/other-new-features/parameter-untupling.md - page: reference/other-new-features/parameter-untupling-spec.md @@ -154,7 +155,6 @@ subsection: - page: reference/experimental/cc.md - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - - page: reference/experimental/named-tuples.md - page: reference/experimental/modularity.md - page: reference/experimental/typeclasses.md - page: reference/experimental/runtimeChecked.md From 2eb52e489c747d80247b5f20534e610027d00dc9 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 1 Oct 2024 14:49:19 +0200 Subject: [PATCH 3/5] Fix ScalaSTM misleading syntax. The previous syntax `before copy (name = ...)` is now interpreted as a binary operation with a named tuple on the RHS. The correct syntax is `before.copy(name = ...)`. --- community-build/community-projects/scala-stm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/community-build/community-projects/scala-stm b/community-build/community-projects/scala-stm index cf204977752a..8d443ab107e7 160000 --- a/community-build/community-projects/scala-stm +++ b/community-build/community-projects/scala-stm @@ -1 +1 @@ -Subproject commit cf204977752af7ec2ca3b50c43f27daa6a628f49 +Subproject commit 8d443ab107e75e809848c2fa3ecd666043171ad5 From 0a416d88b5810b1fc86877c38b17e6c833a4c5e9 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 3 Oct 2024 18:31:35 +0200 Subject: [PATCH 4/5] Add an adaptation step in Inliner We sometimes face a problem that we inline a reference `x: T` which upon further inlining is adapted to an expected type `x`. It only seems to occur in complicated scenarios. I could not completely narrow it down. But in any case it's safe to drop the widening cast in order to avoid a type error here. We do that in a last-effort adaptation step that's only enabled in the Inliner: Faced with an expression `x: T` and a singleton expected type `y.type` where `x.type <: y.type`, rewrite to `x`. --- compiler/src/dotty/tools/dotc/inlines/Inliner.scala | 6 ++++++ compiler/src/dotty/tools/dotc/typer/Typer.scala | 10 +++++++++- tests/pos/i21413.scala | 2 ++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 tests/pos/i21413.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 7c79e972c126..103f3aac7630 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -957,6 +957,12 @@ class Inliner(val call: tpd.Tree)(using Context): case None => tree case _ => tree + + /** For inlining only: Given `(x: T)` with expected type `x.type`, replace the tree with `x`. + */ + override def healAdapt(tree: Tree, pt: Type)(using Context): Tree = (tree, pt) match + case (Typed(tree1, _), pt: SingletonType) if tree1.tpe <:< pt => tree1 + case _ => tree end InlineTyper /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 93ea3f3c3ae0..57bbc3ee98e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4602,7 +4602,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer def recover(failure: SearchFailureType) = if canDefineFurther(wtp) || canDefineFurther(pt) then readapt(tree) - else err.typeMismatch(tree, pt, failure) + else + val tree1 = healAdapt(tree, pt) + if tree1 ne tree then readapt(tree1) + else err.typeMismatch(tree, pt, failure) pt match case _: SelectionProto => @@ -4751,6 +4754,11 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } } + /** Hook for inheriting Typers to do a last-effort adaptation. If a different + * tree is returned, we will readpat that one, ptherwise we issue a type error afterwards. + */ + protected def healAdapt(tree: Tree, pt: Type)(using Context): Tree = tree + /** True if this inline typer has already issued errors */ def hasInliningErrors(using Context): Boolean = false diff --git a/tests/pos/i21413.scala b/tests/pos/i21413.scala new file mode 100644 index 000000000000..d2dc52e34630 --- /dev/null +++ b/tests/pos/i21413.scala @@ -0,0 +1,2 @@ +val x = (aaa = 1).aaa +//val y = x.aaa \ No newline at end of file From 6828fe708c0109e26fddc44072681dba2f95c75c Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 4 Oct 2024 18:49:35 +0200 Subject: [PATCH 5/5] Apply suggestions from code review --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 3 ++- docs/_docs/reference/other-new-features/named-tuples.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 57bbc3ee98e8..0ca4f9486f09 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4755,7 +4755,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } /** Hook for inheriting Typers to do a last-effort adaptation. If a different - * tree is returned, we will readpat that one, ptherwise we issue a type error afterwards. + * tree is returned, we will re-adapt that one, otherwise we issue a type error afterwards. +`` */ protected def healAdapt(tree: Tree, pt: Type)(using Context): Tree = tree diff --git a/docs/_docs/reference/other-new-features/named-tuples.md b/docs/_docs/reference/other-new-features/named-tuples.md index bf1ae4ca7046..3813db4defe1 100644 --- a/docs/_docs/reference/other-new-features/named-tuples.md +++ b/docs/_docs/reference/other-new-features/named-tuples.md @@ -99,7 +99,7 @@ Bob match We allow named patterns not just for named tuples but also for case classes. For instance: ```scala city match - case c @ City(name = "London") => println(p.population) + case c @ City(name = "London") => println(c.population) case City(name = n, zip = 1026, population = pop) => println(pop) ```