diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e2afe906a9c4..8c0dc13f0a2e 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -744,6 +744,7 @@ class Definitions { @tu lazy val StringContextModule_processEscapes: Symbol = StringContextModule.requiredMethod(nme.processEscapes) @tu lazy val PartialFunctionClass: ClassSymbol = requiredClass("scala.PartialFunction") + @tu lazy val PartialFunction_apply: Symbol = PartialFunctionClass.requiredMethod(nme.apply) @tu lazy val PartialFunction_isDefinedAt: Symbol = PartialFunctionClass.requiredMethod(nme.isDefinedAt) @tu lazy val PartialFunction_applyOrElse: Symbol = PartialFunctionClass.requiredMethod(nme.applyOrElse) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 02f1093bbf5d..fd27458362a7 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -21,7 +21,7 @@ import CheckRealizable._ import Variances.{Variance, setStructuralVariances, Invariant} import typer.Nullables import util.Stats._ -import util.SimpleIdentitySet +import util.{SimpleIdentityMap, SimpleIdentitySet} import ast.tpd._ import ast.TreeTypeMap import printing.Texts._ @@ -1751,7 +1751,7 @@ object Types { t case t if defn.isErasedFunctionType(t) => t - case t @ SAMType(_) => + case t @ SAMType(_, _) => t case _ => NoType @@ -5520,105 +5520,119 @@ object Types { * A type is a SAM type if it is a reference to a class or trait, which * * - has a single abstract method with a method type (ExprType - * and PolyType not allowed!) whose result type is not an implicit function type - * and which is not marked inline. + * and PolyType not allowed!) according to `possibleSamMethods`. * - can be instantiated without arguments or with just () as argument. * - * The pattern `SAMType(sam)` matches a SAM type, where `sam` is the - * type of the single abstract method. + * The pattern `SAMType(samMethod, samParent)` matches a SAM type, where `samMethod` is the + * type of the single abstract method and `samParent` is a subtype of the matched + * SAM type which has been stripped of wildcards to turn it into a valid parent + * type. */ object SAMType { - def zeroParamClass(tp: Type)(using Context): Type = tp match { + /** If possible, return a type which is both a subtype of `origTp` and a type + * application of `samClass` where none of the type arguments are + * wildcards (thus making it a valid parent type), otherwise return + * NoType. + * + * A wildcard in the original type will be replaced by its upper or lower bound in a way + * that maximizes the number of possible implementations of `samMeth`. For example, + * java.util.function defines an interface equivalent to: + * + * trait Function[T, R]: + * def apply(t: T): R + * + * and it usually appears with wildcards to compensate for the lack of + * definition-site variance in Java: + * + * (x => x.toInt): Function[? >: String, ? <: Int] + * + * When typechecking this lambda, we need to approximate the wildcards to find + * a valid parent type for our lambda to extend. We can see that in `apply`, + * `T` only appears contravariantly and `R` only appears covariantly, so by + * minimizing the first parameter and maximizing the second, we maximize the + * number of valid implementations of `apply` which lets us implement the lambda + * with a closure equivalent to: + * + * new Function[String, Int] { def apply(x: String): Int = x.toInt } + * + * If a type parameter appears invariantly or does not appear at all in `samMeth`, then + * we arbitrarily pick the upper-bound. + */ + def samParent(origTp: Type, samClass: Symbol, samMeth: Symbol)(using Context): Type = + val tp = origTp.baseType(samClass) + if !(tp <:< origTp) then NoType + else tp match + case tp @ AppliedType(tycon, args) if tp.hasWildcardArg => + val accu = new TypeAccumulator[VarianceMap[Symbol]]: + def apply(vmap: VarianceMap[Symbol], t: Type): VarianceMap[Symbol] = t match + case tp: TypeRef if tp.symbol.isAllOf(ClassTypeParam) => + vmap.recordLocalVariance(tp.symbol, variance) + case _ => + foldOver(vmap, t) + val vmap = accu(VarianceMap.empty, samMeth.info) + val tparams = tycon.typeParamSymbols + val args1 = args.zipWithConserve(tparams): + case (arg @ TypeBounds(lo, hi), tparam) => + val v = vmap.computedVariance(tparam) + if v.uncheckedNN < 0 then lo + else hi + case (arg, _) => arg + tp.derivedAppliedType(tycon, args1) + case _ => + tp + + def samClass(tp: Type)(using Context): Symbol = tp match case tp: ClassInfo => - def zeroParams(tp: Type): Boolean = tp.stripPoly match { + def zeroParams(tp: Type): Boolean = tp.stripPoly match case mt: MethodType => mt.paramInfos.isEmpty && !mt.resultType.isInstanceOf[MethodType] case et: ExprType => true case _ => false - } - // `ContextFunctionN` does not have constructors - val ctor = tp.cls.primaryConstructor - if (!ctor.exists || zeroParams(ctor.info)) tp - else NoType + val cls = tp.cls + val validCtor = + val ctor = cls.primaryConstructor + // `ContextFunctionN` does not have constructors + !ctor.exists || zeroParams(ctor.info) + val isInstantiable = !cls.isOneOf(FinalOrSealed) && (tp.appliedRef <:< tp.selfType) + if validCtor && isInstantiable then tp.cls + else NoSymbol case tp: AppliedType => - zeroParamClass(tp.superType) + samClass(tp.superType) case tp: TypeRef => - zeroParamClass(tp.underlying) + samClass(tp.underlying) case tp: RefinedType => - zeroParamClass(tp.underlying) + samClass(tp.underlying) case tp: TypeBounds => - zeroParamClass(tp.underlying) + samClass(tp.underlying) case tp: TypeVar => - zeroParamClass(tp.underlying) + samClass(tp.underlying) case tp: AnnotatedType => - zeroParamClass(tp.underlying) - case _ => - NoType - } - def isInstantiatable(tp: Type)(using Context): Boolean = zeroParamClass(tp) match { - case cinfo: ClassInfo if !cinfo.cls.isOneOf(FinalOrSealed) => - val selfType = cinfo.selfType.asSeenFrom(tp, cinfo.cls) - tp <:< selfType + samClass(tp.underlying) case _ => - false - } - def unapply(tp: Type)(using Context): Option[MethodType] = - if (isInstantiatable(tp)) { - val absMems = tp.possibleSamMethods - if (absMems.size == 1) - absMems.head.info match { - case mt: MethodType if !mt.isParamDependent && - mt.resultType.isValueTypeOrWildcard && - !defn.isContextFunctionType(mt.resultType) => - val cls = tp.classSymbol - - // Given a SAM type such as: - // - // import java.util.function.Function - // Function[? >: String, ? <: Int] - // - // the single abstract method will have type: - // - // (x: Function[? >: String, ? <: Int]#T): Function[? >: String, ? <: Int]#R - // - // which is not implementable outside of the scope of Function. - // - // To avoid this kind of issue, we approximate references to - // parameters of the SAM type by their bounds, this way in the - // above example we get: - // - // (x: String): Int - val approxParams = new ApproximatingTypeMap { - def apply(tp: Type): Type = tp match { - case tp: TypeRef if tp.symbol.isAllOf(ClassTypeParam) && tp.symbol.owner == cls => - tp.info match { - case info: AliasingBounds => - mapOver(info.alias) - case TypeBounds(lo, hi) => - range(atVariance(-variance)(apply(lo)), apply(hi)) - case _ => - range(defn.NothingType, defn.AnyType) // should happen only in error cases - } - case _ => - mapOver(tp) - } - } - val approx = - if ctx.owner.isContainedIn(cls) then mt - else approxParams(mt).asInstanceOf[MethodType] - Some(approx) + NoSymbol + + def unapply(tp: Type)(using Context): Option[(MethodType, Type)] = + val cls = samClass(tp) + if cls.exists then + val absMems = + if tp.isRef(defn.PartialFunctionClass) then + // To maintain compatibility with 2.x, we treat PartialFunction specially, + // pretending it is a SAM type. In the future it would be better to merge + // Function and PartialFunction, have Function1 contain a isDefinedAt method + // def isDefinedAt(x: T) = true + // and overwrite that method whenever the function body is a sequence of + // case clauses. + List(defn.PartialFunction_apply) + else + tp.possibleSamMethods.map(_.symbol) + if absMems.lengthCompare(1) == 0 then + val samMethSym = absMems.head + val parent = samParent(tp, cls, samMethSym) + samMethSym.asSeenFrom(parent).info match + case mt: MethodType if !mt.isParamDependent && mt.resultType.isValueTypeOrWildcard => + Some(mt, parent) case _ => None - } - else if (tp isRef defn.PartialFunctionClass) - // To maintain compatibility with 2.x, we treat PartialFunction specially, - // pretending it is a SAM type. In the future it would be better to merge - // Function and PartialFunction, have Function1 contain a isDefinedAt method - // def isDefinedAt(x: T) = true - // and overwrite that method whenever the function body is a sequence of - // case clauses. - absMems.find(_.symbol.name == nme.apply).map(_.info.asInstanceOf[MethodType]) else None - } else None } @@ -6451,6 +6465,37 @@ object Types { } } + object VarianceMap: + /** An immutable map representing the variance of keys of type `K` */ + opaque type VarianceMap[K <: AnyRef] <: AnyRef = SimpleIdentityMap[K, Integer] + def empty[K <: AnyRef]: VarianceMap[K] = SimpleIdentityMap.empty[K] + extension [K <: AnyRef](vmap: VarianceMap[K]) + /** The backing map used to implement this VarianceMap. */ + inline def underlying: SimpleIdentityMap[K, Integer] = vmap + + /** Return a new map taking into account that K appears in a + * {co,contra,in}-variant position if `localVariance` is {positive,negative,zero}. + */ + def recordLocalVariance(k: K, localVariance: Int): VarianceMap[K] = + val previousVariance = vmap(k) + if previousVariance == null then + vmap.updated(k, localVariance) + else if previousVariance == localVariance || previousVariance == 0 then + vmap + else + vmap.updated(k, 0) + + /** Return the variance of `k`: + * - A positive value means that `k` appears only covariantly. + * - A negative value means that `k` appears only contravariantly. + * - A zero value means that `k` appears both covariantly and + * contravariantly, or appears invariantly. + * - A null value means that `k` does not appear at all. + */ + def computedVariance(k: K): Integer | Null = + vmap(k) + export VarianceMap.VarianceMap + // ----- Name Filters -------------------------------------------------- /** A name filter selects or discards a member name of a type `pre`. diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 0bfc444e0997..a933b247a85f 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -50,10 +50,10 @@ class ExpandSAMs extends MiniPhase: tree // it's a plain function case tpe if defn.isContextFunctionType(tpe) => tree - case tpe @ SAMType(_) if tpe.isRef(defn.PartialFunctionClass) => + case SAMType(_, tpe) if tpe.isRef(defn.PartialFunctionClass) => val tpe1 = checkRefinements(tpe, fn) toPartialFunction(tree, tpe1) - case tpe @ SAMType(_) if ExpandSAMs.isPlatformSam(tpe.classSymbol.asClass) => + case SAMType(_, tpe) if ExpandSAMs.isPlatformSam(tpe.classSymbol.asClass) => checkRefinements(tpe, fn) tree case tpe => @@ -66,13 +66,6 @@ class ExpandSAMs extends MiniPhase: tree } - private def checkNoContextFunction(tpt: Tree)(using Context): Unit = - if defn.isContextFunctionType(tpt.tpe) then - report.error( - em"""Implementation restriction: cannot convert this expression to - |partial function with context function result type $tpt""", - tpt.srcPos) - /** A partial function literal: * * ``` @@ -115,8 +108,6 @@ class ExpandSAMs extends MiniPhase: private def toPartialFunction(tree: Block, tpe: Type)(using Context): Tree = { val closureDef(anon @ DefDef(_, List(List(param)), _, _)) = tree: @unchecked - checkNoContextFunction(anon.tpt) - // The right hand side from which to construct the partial function. This is always a Match. // If the original rhs is already a Match (possibly in braces), return that. // Otherwise construct a match `x match case _ => rhs` where `x` is the parameter of the closure. diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index e5707106e325..d2d36ce9e242 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -696,7 +696,7 @@ trait Applications extends Compatibility { def SAMargOK = defn.isFunctionNType(argtpe1) && formal.match - case SAMType(sam) => argtpe <:< sam.toFunctionType(isJava = formal.classSymbol.is(JavaDefined)) + case SAMType(samMeth, samParent) => argtpe <:< samMeth.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) case _ => false isCompatible(argtpe, formal) @@ -2074,7 +2074,7 @@ trait Applications extends Compatibility { * new java.io.ObjectOutputStream(f) */ pt match { - case SAMType(mtp) => + case SAMType(mtp, _) => narrowByTypes(alts, mtp.paramInfos, mtp.resultType) case _ => // pick any alternatives that are not methods since these might be convertible diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 2a1f8bbef6b6..ab8f2320e486 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -407,7 +407,7 @@ object Inferencing { val vs = variances(tp) val patternBindings = new mutable.ListBuffer[(Symbol, TypeParamRef)] val gadtBounds = ctx.gadt.symbols.map(ctx.gadt.bounds(_).nn) - vs foreachBinding { (tvar, v) => + vs.underlying foreachBinding { (tvar, v) => if !tvar.isInstantiated then // if the tvar is covariant/contravariant (v == 1/-1, respectively) in the input type tp // then it is safe to instantiate if it doesn't occur in any of the GADT bounds. @@ -440,8 +440,6 @@ object Inferencing { res } - type VarianceMap = SimpleIdentityMap[TypeVar, Integer] - /** All occurrences of type vars in `tp` that satisfy predicate * `include` mapped to their variances (-1/0/1) in both `tp` and * `pt.finalResultType`, where @@ -465,23 +463,18 @@ object Inferencing { * * we want to instantiate U to x.type right away. No need to wait further. */ - private def variances(tp: Type, pt: Type = WildcardType)(using Context): VarianceMap = { + private def variances(tp: Type, pt: Type = WildcardType)(using Context): VarianceMap[TypeVar] = { Stats.record("variances") val constraint = ctx.typerState.constraint - object accu extends TypeAccumulator[VarianceMap] { + object accu extends TypeAccumulator[VarianceMap[TypeVar]]: def setVariance(v: Int) = variance = v - def apply(vmap: VarianceMap, t: Type): VarianceMap = t match { + def apply(vmap: VarianceMap[TypeVar], t: Type): VarianceMap[TypeVar] = t match case t: TypeVar if !t.isInstantiated && accCtx.typerState.constraint.contains(t) => - val v = vmap(t) - if (v == null) vmap.updated(t, variance) - else if (v == variance || v == 0) vmap - else vmap.updated(t, 0) + vmap.recordLocalVariance(t, variance) case _ => foldOver(vmap, t) - } - } /** Include in `vmap` type variables occurring in the constraints of type variables * already in `vmap`. Specifically: @@ -493,10 +486,10 @@ object Inferencing { * bounds as non-variant. * Do this in a fixpoint iteration until `vmap` stabilizes. */ - def propagate(vmap: VarianceMap): VarianceMap = { + def propagate(vmap: VarianceMap[TypeVar]): VarianceMap[TypeVar] = { var vmap1 = vmap def traverse(tp: Type) = { vmap1 = accu(vmap1, tp) } - vmap.foreachBinding { (tvar, v) => + vmap.underlying.foreachBinding { (tvar, v) => val param = tvar.origin constraint.entry(param) match case TypeBounds(lo, hi) => @@ -512,7 +505,7 @@ object Inferencing { if (vmap1 eq vmap) vmap else propagate(vmap1) } - propagate(accu(accu(SimpleIdentityMap.empty, tp), pt.finalResultType)) + propagate(accu(accu(VarianceMap.empty, tp), pt.finalResultType)) } /** Run the transformation after dealiasing but return the original type if it was a no-op. */ @@ -638,7 +631,7 @@ trait Inferencing { this: Typer => if !tvar.isInstantiated then // isInstantiated needs to be checked again, since previous interpolations could already have // instantiated `tvar` through unification. - val v = vs(tvar) + val v = vs.computedVariance(tvar) if v == null then buf += ((tvar, 0)) else if v.intValue != 0 then buf += ((tvar, v.intValue)) else comparing(cmp => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index cb5051ea34ad..fae044542cbb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1049,7 +1049,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case _ => tree } - def typedNamedArg(tree: untpd.NamedArg, pt: Type)(using Context): NamedArg = { /* Special case for resolving types for arguments of an annotation defined in Java. * It allows that value of any type T can appear in positions where Array[T] is expected. @@ -1330,9 +1329,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case RefinedType(parent, nme.apply, mt @ MethodTpe(_, formals, restpe)) if (defn.isNonRefinedFunction(parent) || defn.isErasedFunctionType(parent)) && formals.length == defaultArity => (formals, untpd.InLambdaTypeTree(isResult = true, (_, syms) => restpe.substParams(mt, syms.map(_.termRef)))) - case pt1 @ SAMType(mt @ MethodTpe(_, formals, _)) => + case SAMType(mt @ MethodTpe(_, formals, _), samParent) => val restpe = mt.resultType match - case mt: MethodType => mt.toFunctionType(isJava = pt1.classSymbol.is(JavaDefined)) + case mt: MethodType => mt.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) case tp => tp (formals, if (mt.isResultDependent) @@ -1686,22 +1685,22 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer meth1.tpe.widen match { case mt: MethodType => pt.findFunctionType match { - case pt @ SAMType(sam) - if !defn.isFunctionNType(pt) && mt <:< sam => - // SAMs of the form C[?] where C is a class cannot be conversion targets. - // The resulting class `class $anon extends C[?] {...}` would be illegal, - // since type arguments to `C`'s super constructor cannot be constructed. - def isWildcardClassSAM = - !pt.classSymbol.is(Trait) && pt.argInfos.exists(_.isInstanceOf[TypeBounds]) + case SAMType(samMeth, samParent) + if !defn.isFunctionNType(samParent) && mt <:< samMeth => + if defn.isContextFunctionType(mt.resultType) then + report.error( + em"""Implementation restriction: cannot convert this expression to `$samParent` + |because its result type `${mt.resultType}` is a contextual function type.""", + tree.srcPos) val targetTpe = - if isFullyDefined(pt, ForceDegree.all) && !isWildcardClassSAM then - pt - else if pt.isRef(defn.PartialFunctionClass) then + if isFullyDefined(samParent, ForceDegree.all) then + samParent + else if samParent.isRef(defn.PartialFunctionClass) then // Replace the underspecified expected type by one based on the closure method type defn.PartialFunctionOf(mt.firstParamTypes.head, mt.resultType) else - report.error(em"result type of lambda is an underspecified SAM type $pt", tree.srcPos) - pt + report.error(em"result type of lambda is an underspecified SAM type $samParent", tree.srcPos) + samParent TypeTree(targetTpe) case _ => if (mt.isParamDependent) @@ -3994,8 +3993,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer else if (!defn.isFunctionNType(pt)) pt match { - case SAMType(_) if !pt.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) => - report.warning(em"${tree.symbol} is eta-expanded even though $pt does not have the @FunctionalInterface annotation.", tree.srcPos) + case SAMType(_, samParent) if !pt1.classSymbol.hasAnnotation(defn.FunctionalInterfaceAnnot) => + report.warning(em"${tree.symbol} is eta-expanded even though $samParent does not have the @FunctionalInterface annotation.", tree.srcPos) case _ => } simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) @@ -4163,9 +4162,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } } - def toSAM(tree: Tree): Tree = tree match { - case tree: Block => tpd.cpy.Block(tree)(tree.stats, toSAM(tree.expr)) - case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(pt)).withType(pt) + def toSAM(tree: Tree, samParent: Type): Tree = tree match { + case tree: Block => tpd.cpy.Block(tree)(tree.stats, toSAM(tree.expr, samParent)) + case tree: Closure => cpy.Closure(tree)(tpt = TypeTree(samParent)).withType(samParent) } def adaptToSubType(wtp: Type): Tree = @@ -4204,13 +4203,13 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer case closure(Nil, id @ Ident(nme.ANON_FUN), _) if defn.isFunctionNType(wtp) && !defn.isFunctionNType(pt) => pt match { - case SAMType(sam) - if wtp <:< sam.toFunctionType(isJava = pt.classSymbol.is(JavaDefined)) => + case SAMType(samMeth, samParent) + if wtp <:< samMeth.toFunctionType(isJava = samParent.classSymbol.is(JavaDefined)) => // was ... && isFullyDefined(pt, ForceDegree.flipBottom) // but this prevents case blocks from implementing polymorphic partial functions, // since we do not know the result parameter a priori. Have to wait until the // body is typechecked. - return toSAM(tree) + return toSAM(tree, samParent) case _ => } case _ => diff --git a/tests/neg/i15741.scala b/tests/neg/i15741.scala index b5304f83d8b3..2d536c515f76 100644 --- a/tests/neg/i15741.scala +++ b/tests/neg/i15741.scala @@ -1,15 +1,15 @@ def get(using Int): String = summon[Int].toString def pf2: PartialFunction[String, Int ?=> String] = { - case "hoge" => get // error + case "hoge" => get case "huga" => get - } + } // error type IS = Int ?=> String def pf3: PartialFunction[String, IS] = { - case "hoge" => get // error + case "hoge" => get case "huga" => get - } + } // error diff --git a/tests/neg/i8012.scala b/tests/neg/i8012.scala index 01171fd3f80c..c5f3df050e2c 100644 --- a/tests/neg/i8012.scala +++ b/tests/neg/i8012.scala @@ -9,5 +9,5 @@ class C extends Q[?] // error: Type argument must be fully defined object O { def m(i: Int): Int = i - val x: Q[_] = m // error: result type of lambda is an underspecified SAM type Q[?] -} \ No newline at end of file + val x: Q[_] = m +} diff --git a/tests/pos/AE-9a131723f09b9f77c99c52b709965e580a61706e.scala b/tests/pos/AE-9a131723f09b9f77c99c52b709965e580a61706e.scala index 6a7de4da0d2f..76c0c3d731e9 100755 --- a/tests/pos/AE-9a131723f09b9f77c99c52b709965e580a61706e.scala +++ b/tests/pos/AE-9a131723f09b9f77c99c52b709965e580a61706e.scala @@ -1 +1 @@ -object I0 { val i1: PartialFunction[_, Int] = { case i2 => i2 } } +object I0 { val i1: PartialFunction[_, Any] = { case i2 => i2 } } diff --git a/tests/pos/i18096.scala b/tests/pos/i18096.scala new file mode 100644 index 000000000000..c2ef9ededdb3 --- /dev/null +++ b/tests/pos/i18096.scala @@ -0,0 +1,4 @@ +trait F1[-T1, +R] extends AnyRef { def apply(v1: T1): R } +class R { def l: List[Any] = Nil } +class S { def m[T](f: F1[R, ? <: List[T]]): S = this } +class T1 { def t1(s: S) = s.m((r: R) => r.l) } diff --git a/tests/run/i16065.scala b/tests/run/i16065.scala new file mode 100644 index 000000000000..59b4f83bc05c --- /dev/null +++ b/tests/run/i16065.scala @@ -0,0 +1,41 @@ +trait Consumer1[T]: + var x: Int = 1 // To force anonymous class generation + def accept(x: T): Unit + +trait Consumer2[T]: + def accept(x: T): Unit + +trait Producer1[T]: + var x: Int = 1 // To force anonymous class generation + def produce(x: Any): T + +trait Producer2[T]: + def produce(x: Any): T + +trait ProdCons1[T]: + var x: Int = 1 // To force anonymous class generation + def apply(x: T): T + +trait ProdCons2[T]: + var x: Int = 1 // To force anonymous class generation + def apply(x: T): T + +object Test { + def main(args: Array[String]): Unit = { + val a1: Consumer1[? >: String] = x => () + a1.accept("foo") + + val a2: Consumer2[? >: String] = x => () + a2.accept("foo") + + val b1: Producer1[? <: String] = x => "" + val bo1: String = b1.produce(1) + + val b2: Producer2[? <: String] = x => "" + val bo2: String = b2.produce(1) + + val c1: ProdCons1[? <: String] = x => x + val c2: ProdCons2[? <: String] = x => x + // Can't do much with `c1` or `c2` but we should still pass Ycheck. + } +}