Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make named tuples an experimental feature again #22045

Merged
merged 3 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Feature.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ 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")
Expand Down Expand Up @@ -65,6 +66,7 @@ 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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ enum MigrationVersion(val warnFrom: SourceVersion, val errorFrom: SourceVersion)
case WithOperator extends MigrationVersion(`3.4`, future)
case FunctionUnderscore extends MigrationVersion(`3.4`, future)
case NonNamedArgumentInJavaAnnotation extends MigrationVersion(`3.6`, `3.6`)
case AmbiguousNamedTupleInfixApply extends MigrationVersion(`3.6`, never)
case AmbiguousNamedTupleSyntax extends MigrationVersion(`3.6`, future)
case ImportWildcard extends MigrationVersion(future, future)
case ImportRename extends MigrationVersion(future, future)
case ParameterEnclosedByParenthesis extends MigrationVersion(future, future)
Expand Down
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ object Parsers {
else leading :: Nil

def maybeNamed(op: () => Tree): () => Tree = () =>
if isIdent && in.lookahead.token == EQUALS && sourceVersion.isAtLeast(`3.6`) then
if isIdent && in.lookahead.token == EQUALS && in.featureEnabled(Feature.namedTuples) then
atSpan(in.offset):
val name = ident()
in.nextToken()
Expand Down Expand Up @@ -1149,8 +1149,8 @@ object Parsers {
if isType then infixOp
else infixOp.right match
case Tuple(args) if args.exists(_.isInstanceOf[NamedArg]) && !isNamedTupleOperator =>
report.errorOrMigrationWarning(AmbiguousNamedTupleInfixApply(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleInfixApply)
if MigrationVersion.AmbiguousNamedTupleInfixApply.needsPatch then
report.errorOrMigrationWarning(DeprecatedInfixNamedArgumentSyntax(), infixOp.right.srcPos, MigrationVersion.AmbiguousNamedTupleSyntax)
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
val asApply = cpy.Apply(infixOp)(Select(opInfo.operand, opInfo.operator.name), args)
patch(source, infixOp.span, asApply.show(using ctx.withoutColors))
asApply // allow to use pre-3.6 syntax in migration mode
Expand Down Expand Up @@ -2172,7 +2172,7 @@ object Parsers {

if namedOK && isIdent && in.lookahead.token == EQUALS then
commaSeparated(() => namedArgType())
else if tupleOK && isIdent && in.lookahead.isColon && sourceVersion.isAtLeast(`3.6`) then
else if tupleOK && isIdent && in.lookahead.isColon && in.featureEnabled(Feature.namedTuples) then
commaSeparated(() => namedElem())
else
commaSeparated(() => argType())
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case FinalLocalDefID // errorNumber: 200
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
case QuotedTypeMissingID // errorNumber: 202
case AmbiguousNamedTupleAssignmentID // errorNumber: 203
case AmbiguousNamedTupleInfixApplyID // errorNumber: 204
case DeprecatedAssignmentSyntaxID // errorNumber: 203
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204

def errorNumber = ordinal - 1

Expand Down
19 changes: 9 additions & 10 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3344,21 +3344,20 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q

end QuotedTypeMissing

final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(AmbiguousNamedTupleAssignmentID):
final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Context) extends SyntaxMsg(DeprecatedAssignmentSyntaxID):
override protected def msg(using Context): String =
i"""Ambiguous syntax: this is interpreted as a named tuple with one element,
i"""Deprecated syntax: in the future it would be interpreted as a named tuple with one element,
|not as an assignment.
|
|To assign a value, use curly braces: `{${key} = ${value}}`."""

+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

override protected def explain(using Context): String = ""

class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID):
class DeprecatedInfixNamedArgumentSyntax()(using Context) extends SyntaxMsg(DeprecatedInfixNamedArgumentSyntaxID):
def msg(using Context) =
"Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list."
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)
i"""Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection."""
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

def explain(using Context) =
i"""Starting with Scala 3.6 infix named arguments are interpretted as Named Tuple.
|
|To avoid this warning, either remove the argument names or use dotted selection."""
def explain(using Context) = ""
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,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 && sourceVersion.isAtLeast(`3.6`) then
if nameIdx >= 0 && Feature.enabled(Feature.namedTuples) then
typed(
untpd.Apply(
untpd.Select(untpd.TypedSplice(qual), nme.apply),
Expand Down Expand Up @@ -3404,7 +3404,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Translate tuples of all arities */
def typedTuple(tree: untpd.Tuple, pt: Type)(using Context): Tree =
val tree1 = desugar.tuple(tree, pt)
checkAmbiguousNamedTupleAssignment(tree)
checkDeprecatedAssignmentSyntax(tree)
if tree1 ne tree then typed(tree1, pt)
else
val arity = tree.trees.length
Expand Down Expand Up @@ -3433,15 +3433,18 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
/** Checks if `tree` is a named tuple with one element that could be
* interpreted as an assignment, such as `(x = 1)`. If so, issues a warning.
*/
def checkAmbiguousNamedTupleAssignment(tree: untpd.Tuple)(using Context): Unit =
def checkDeprecatedAssignmentSyntax(tree: untpd.Tuple)(using Context): Unit =
tree.trees match
case List(NamedArg(name, value)) =>
val tmpCtx = ctx.fresh.setNewTyperState()
typedAssign(untpd.Assign(untpd.Ident(name), value), WildcardType)(using tmpCtx)
if !tmpCtx.reporter.hasErrors then
// If there are no errors typing the above, then the named tuple is
// ambiguous and we issue a warning.
report.migrationWarning(AmbiguousNamedTupleAssignment(name, value), tree.srcPos)
report.migrationWarning(DeprecatedAssignmentSyntax(name, value), tree.srcPos)
if MigrationVersion.AmbiguousNamedTupleSyntax.needsPatch then
patch(tree.source, Span(tree.span.start, tree.span.start + 1), "{")
patch(tree.source, Span(tree.span.end - 1, tree.span.end), "}")
case _ => ()

/** Retrieve symbol attached to given tree */
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotty/tools/dotc/CompilationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class CompilationTests {
compileDir("tests/rewrites/annotation-named-pararamters", defaultOptions.and("-rewrite", "-source:3.6-migration")),
compileFile("tests/rewrites/i21418.scala", unindentOptions.and("-rewrite", "-source:3.5-migration")),
compileFile("tests/rewrites/infix-named-args.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
compileFile("tests/rewrites/ambigious-named-tuple-assignment.scala", defaultOptions.and("-rewrite", "-source:3.6-migration")),
).checkRewrites()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
layout: doc-page
title: "Named Tuples"
nightlyOf: https://docs.scala-lang.org/scala3/reference/other-new-features/named-tuples.html
nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/named-tuples.html
---

Starting in Scala 3.6, the elements of a tuple can be named. Example:
The elements of a tuple can now be named. Example:
```scala
type Person = (name: String, age: Int)
val Bob: Person = (name = "Bob", age = 33)
Expand Down
2 changes: 1 addition & 1 deletion docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ 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
Expand Down Expand Up @@ -159,6 +158,7 @@ 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
Expand Down
3 changes: 3 additions & 0 deletions library/src/scala/NamedTuple.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package scala
import annotation.experimental
import compiletime.ops.boolean.*

@experimental
object NamedTuple:

/** The type to which named tuples get mapped to. For instance,
Expand Down Expand Up @@ -131,6 +133,7 @@ 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])
Expand Down
1 change: 0 additions & 1 deletion library/src/scala/runtime/stdLibPatches/language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1988,7 +1988,8 @@ class CompletionSuite extends BaseCompletionSuite:

@Test def `namedTuple completions` =
check(
"""|import scala.NamedTuple.*
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|val person = (name = "Jamie", city = "Lausanne")
|
Expand All @@ -1999,7 +2000,8 @@ class CompletionSuite extends BaseCompletionSuite:

@Test def `Selectable with namedTuple Fields member` =
check(
"""|import scala.NamedTuple.*
"""|import scala.language.experimental.namedTuples
|import scala.NamedTuple.*
|
|class NamedTupleSelectable extends Selectable {
| type Fields <: AnyNamedTuple
Expand Down
14 changes: 7 additions & 7 deletions tests/neg/i20517.check
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
-- [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`
-- [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`
1 change: 1 addition & 0 deletions tests/neg/i20517.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import scala.language.experimental.namedTuples
import NamedTuple.From

case class Foo[+T](elem: T)
Expand Down
40 changes: 18 additions & 22 deletions tests/neg/infix-named-args.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- [E134] Type Error: tests/neg/infix-named-args.scala:2:13 ------------------------------------------------------------
2 | def f = 42 + (x = 1) // error // werror
-- [E134] Type Error: tests/neg/infix-named-args.scala:4:13 ------------------------------------------------------------
4 | def f = 42 + (x = 1) // error // werror
| ^^^^
| None of the overloaded alternatives of method + in class Int with types
| (x: Double): Double
Expand All @@ -11,31 +11,27 @@
| (x: Byte): Int
| (x: String): String
| match arguments ((x : Int)) (a named tuple)
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:2:15 --------------------------------------------------------
2 | def f = 42 + (x = 1) // error // werror
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:4:15 --------------------------------------------------------
4 | def f = 42 + (x = 1) // error // werror
| ^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:5:26 --------------------------------------------------------
5 | def g = new C() `multi` (x = 42, y = 27) // werror
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:7:26 --------------------------------------------------------
7 | def g = new C() `multi` (x = 42, y = 27) // werror
| ^^^^^^^^^^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:6:21 --------------------------------------------------------
6 | def h = new C() ** (x = 42, y = 27) // werror
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:8:21 --------------------------------------------------------
8 | def h = new C() ** (x = 42, y = 27) // werror
| ^^^^^^^^^^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:13:18 -------------------------------------------------------
13 | def f = this ** (x = 2) // werror
-- [E204] Syntax Warning: tests/neg/infix-named-args.scala:15:18 -------------------------------------------------------
15 | def f = this ** (x = 2) // werror
| ^^^^^^^
|Ambigious syntax: this infix call argument list is interpreted as single named tuple argument, not as an named arguments list.
|Deprecated syntax: infix named arguments lists are deprecated; in the future it would be interpreted as a single name tuple argument.
|To avoid this warning, either remove the argument names or use dotted selection.
|This can be rewritten automatically under -rewrite -source 3.6-migration.
|
| longer explanation available when compiling with `-explain`
2 changes: 2 additions & 0 deletions tests/neg/infix-named-args.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.language.experimental.namedTuples

class C:
def f = 42 + (x = 1) // error // werror
def multi(x: Int, y: Int): Int = x + y
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/named-tuple-selectable.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import scala.language.experimental.namedTuples

class FromFields extends Selectable:
type Fields = (i: Int)
def selectDynamic(key: String) =
Expand Down
8 changes: 4 additions & 4 deletions tests/neg/named-tuples-2.check
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- Error: tests/neg/named-tuples-2.scala:4:9 ---------------------------------------------------------------------------
4 | case (name, age) => () // error
-- Error: tests/neg/named-tuples-2.scala:5:9 ---------------------------------------------------------------------------
5 | 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:5:9 ---------------------------------------------------------------------------
5 | case (n, a, m, x) => () // error
-- Error: tests/neg/named-tuples-2.scala:6:9 ---------------------------------------------------------------------------
6 | case (n, a, m, x) => () // error
| ^
| this case is unreachable since type (String, Int, Boolean) is not a subclass of class Tuple4
1 change: 1 addition & 0 deletions tests/neg/named-tuples-2.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import language.experimental.namedTuples
def Test =
val person = (name = "Bob", age = 33, married = true)
person match
Expand Down
4 changes: 2 additions & 2 deletions tests/neg/named-tuples-3.check
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:5:16 -----------------------------------------------------
5 |val p: Person = f // error
-- [E007] Type Mismatch Error: tests/neg/named-tuples-3.scala:7:16 -----------------------------------------------------
7 |val p: Person = f // error
| ^
| Found: NamedTuple.NamedTuple[(Int, Any), (Int, String)]
| Required: Person
Expand Down
2 changes: 2 additions & 0 deletions tests/neg/named-tuples-3.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import language.experimental.namedTuples

def f: NamedTuple.NamedTuple[(Int, Any), (Int, String)] = ???

type Person = (name: Int, age: String)
Expand Down
Loading
Loading