diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a81f4be9068a..8aacbee09bc0 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -958,6 +958,7 @@ class Definitions { def TupleClass(using Context): ClassSymbol = TupleTypeRef.symbol.asClass @tu lazy val Tuple_cons: Symbol = TupleClass.requiredMethod("*:") @tu lazy val TupleModule: Symbol = requiredModule("scala.Tuple") + @tu lazy val Tuple_Elem: Symbol = TupleModule.requiredType("Elem") @tu lazy val EmptyTupleClass: Symbol = requiredClass("scala.EmptyTuple") @tu lazy val EmptyTupleModule: Symbol = requiredModule("scala.EmptyTuple") @tu lazy val NonEmptyTupleTypeRef: TypeRef = requiredClassRef("scala.NonEmptyTuple") @@ -1270,6 +1271,32 @@ class Definitions { else None } + // Pattern matcher of tuple selectors at the type level + // Matches Tuple.Elem[x, constant] and x._1.type + object TupleSelectorOf: + def unapply(tp: Type)(using Context): Option[(Type, Int)] = + if tp.isRef(Tuple_Elem) then + // get the arg infos for tuple elem + tp.argInfos match + case tupleType :: ConstantType(c) :: Nil + if c.isIntRange && (isTupleNType(tupleType) || tupleType.isRef(PairClass)) => + Some((tupleType, c.intValue)) + // we should probably report an error here, but its probably handled elsewhere + case _ => None + else + tp.dealias match + // very explicitly ONLY check for isTupleNType + // pair class doesn't have selector fields + case TermRef(tupleType, field) if isTupleNType(tupleType) => + field match + case name: SimpleName => + name.toString match + case s"_$id" => + id.toIntOption.map(it => (tupleType, it - 1)) + case _ => None + case _ => None + case _ => None + object ArrayOf { def apply(elem: Type)(using Context): Type = if (ctx.erasedTypes) JavaArrayType(elem) @@ -1473,7 +1500,7 @@ class Definitions { def patchStdLibClass(denot: ClassDenotation)(using Context): Unit = // Do not patch the stdlib files if we explicitly disable it // This is only to be used during the migration of the stdlib - if ctx.settings.YnoStdlibPatches.value then + if ctx.settings.YnoStdlibPatches.value then return def patch2(denot: ClassDenotation, patchCls: Symbol): Unit = diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 8df2fc1ed4b7..36fe7c0c3a78 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -20,6 +20,7 @@ import ast.tpd.* import Synthesizer.* import sbt.ExtractDependencies.* import xsbti.api.DependencyContext.* +import dotty.tools.dotc.core.Definitions.MaxTupleArity /** Synthesize terms for special classes */ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): @@ -105,29 +106,89 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val synthesizedTupleFunction: SpecialHandler = (formal, span) => formal match case AppliedType(_, funArgs @ fun :: tupled :: Nil) => + def doesFunctionTupleInto(baseFun: Type, actualArgs: List[Type], + actualRet: Type, tupled: Type) = + tupled =:= constructDependentTupleType(actualArgs, actualRet, defn.isContextFunctionType(baseFun)) + def doesFunctionUntupleTo(baseFun: Type, actualArgs: List[Type], + actualRet: Type, untupled: Type) = + untupled =:= untupleDependentTupleType(actualArgs, actualRet, defn.isContextFunctionType(baseFun)) + def functionTypeEqual(baseFun: Type, actualArgs: List[Type], actualRet: Type, expected: Type) = - expected =:= defn.FunctionNOf(actualArgs, actualRet, - defn.isContextFunctionType(baseFun)) + expected =:= defn.FunctionNOf(actualArgs, actualRet, defn.isContextFunctionType(baseFun)) + def untupleDependentTupleType(args: List[Type], ret: Type, contextual: Boolean): Type = + val methodKind = if contextual then ContextualMethodType else MethodType + + val arity = args.length + methodKind(args.indices.map(nme.syntheticParamName).toList)( + mt => args, + mt => + val tpeMap = new TypeMap: + def apply(tp: Type): Type = + tp match + case defn.TupleSelectorOf(TermParamRef(_, paramNum), fieldNum) => + if fieldNum >= arity then + NoType + else + mt.paramRefs(fieldNum) + case _ => mapOver(tp) + tpeMap(ret) + ) + + def constructDependentTupleType(args: List[Type], ret: Type, contextual: Boolean): Type = + val methodKind = if contextual then ContextualMethodType else MethodType + + methodKind(List(nme.syntheticParamName(0)))( + mt => List(defn.tupleType(args)), + mt => + val tpeMap = new TypeMap: + def apply(tp: Type): Type = + tp match + case TermParamRef(binder, paramNum) => + mt.paramRefs(0).select(nme.selectorName(paramNum)) + case _ => + mapOver(tp) + tpeMap(ret) + ).toFunctionType() + val arity: Int = if defn.isFunctionNType(fun) then // TupledFunction[(...) => R, ?] - fun.functionArgInfos match - case funArgs :+ funRet - if functionTypeEqual(fun, defn.tupleType(funArgs) :: Nil, funRet, tupled) => - // TupledFunction[(...funArgs...) => funRet, ?] - funArgs.size - case _ => -1 + // dont use functionArgInfos it dealiases and drops dependents + + fun.dealias match + case defn.RefinedFunctionOf(method: MethodType) if doesFunctionTupleInto(fun, method.paramInfos, method.resType, tupled) => + method.paramInfos.size + // poly types are unsupported + case defn.RefinedFunctionOf(_) => -1 + case _ => + fun.functionArgInfos match + case funArgs :+ funRet + if functionTypeEqual(fun, defn.tupleType(funArgs) :: Nil, funRet, tupled) => + // TupledFunction[(...funArgs...) => funRet, ?] + funArgs.size + case _ => -1 else if defn.isFunctionNType(tupled) then // TupledFunction[?, (...) => R] - tupled.functionArgInfos match - case tupledArgs :: funRet :: Nil => - tupledArgs.tupleElementTypes match - case Some(funArgs) if functionTypeEqual(tupled, funArgs, funRet, fun) => - // TupledFunction[?, ((...funArgs...)) => funRet] - funArgs.size + tupled.dealias match + case defn.RefinedFunctionOf(method: MethodType) => + method.argInfos match + case tupledArgs :: funRet :: Nil => + // TupledFunction[?, ((...)) => R] + tupledArgs.tupleElementTypes match + case Some(args) if doesFunctionUntupleTo(tupled, args, funRet, fun) => + args.size + case _ => -1 + case _ => -1 + case _ => + tupled.functionArgInfos match + case tupledArgs :: funRet :: Nil => + // TupledFunction[?, ((...)) => R] + tupledArgs.tupleElementTypes match + case Some(args) if functionTypeEqual(tupled, args, funRet, fun) => + args.size + case _ => -1 case _ => -1 - case _ => -1 else // TupledFunction[?, ?] -1 diff --git a/tests/neg/i21808a.check b/tests/neg/i21808a.check new file mode 100644 index 000000000000..89dd43c92752 --- /dev/null +++ b/tests/neg/i21808a.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/i21808a.scala:9:63 --------------------------------------------------------------------- +9 | summon[TupledFunction[(x: T, y: T) => x.type, ((T, T)) => T]] // error + | ^ + | (x: Test.T, y: Test.T) => x.type cannot be tupled as ((Test.T, Test.T)) => Test.T diff --git a/tests/neg/i21808a.scala b/tests/neg/i21808a.scala new file mode 100644 index 000000000000..df4cf3d5cb90 --- /dev/null +++ b/tests/neg/i21808a.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +import scala.util.TupledFunction +import scala.util.NotGiven + +object Test { + type T + + summon[TupledFunction[(x: T, y: T) => x.type, ((T, T)) => T]] // error +} \ No newline at end of file diff --git a/tests/neg/i21808b.check b/tests/neg/i21808b.check new file mode 100644 index 000000000000..02d04b43b977 --- /dev/null +++ b/tests/neg/i21808b.check @@ -0,0 +1,4 @@ +-- [E172] Type Error: tests/neg/i21808b.scala:9:63 --------------------------------------------------------------------- +9 | summon[TupledFunction[(T, T) => T, (x: (T, T)) => x._1.type]] // error + | ^ + | (Test.T, Test.T) => Test.T cannot be tupled as (x: (Test.T, Test.T)) => (x._1 : Test.T) diff --git a/tests/neg/i21808b.scala b/tests/neg/i21808b.scala new file mode 100644 index 000000000000..3042ab16abf0 --- /dev/null +++ b/tests/neg/i21808b.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +import scala.util.TupledFunction +import scala.util.NotGiven + +object Test { + type T + + summon[TupledFunction[(T, T) => T, (x: (T, T)) => x._1.type]] // error +} \ No newline at end of file diff --git a/tests/pos/i21808.scala b/tests/pos/i21808.scala new file mode 100644 index 000000000000..fde33c858b82 --- /dev/null +++ b/tests/pos/i21808.scala @@ -0,0 +1,10 @@ +//> using options -experimental + +import scala.util.TupledFunction +import scala.util.NotGiven + +object Test { + type T + + summon[TupledFunction[(x: T, y: T) => x.type, (x: (T, T)) => x._1.type]] +} \ No newline at end of file