From 89735d029f57853df94b4c99d814948c715381a2 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 15 Mar 2023 18:00:07 +0100 Subject: [PATCH] Properly handle SAM types with wildcards When typing a closure with an expected type containing a wildcard, the closure type itself should not contain wildcards, because it might be expanded to an anonymous class extending the closure type (this happens on non-JVM backends as well as on the JVM itself in situations where a SAM trait does not compile down to a SAM interface). We were already approximating wildcards in the method type returned by the SAMType extractor, but to fix this issue we had to change the extractor to perform the approximation on the expected type itself to generate a valid parent type. The SAMType extractor now returns both the approximated parent type and the type of the method itself. The wildcard approximation analysis relies on a new `VarianceMap` opaque type extracted from Inferencing#variances. Fixes #16065. Fixes #18096. --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/Types.scala | 210 +++++++++++------- .../tools/dotc/transform/ExpandSAMs.scala | 4 +- .../dotty/tools/dotc/typer/Applications.scala | 4 +- .../dotty/tools/dotc/typer/Inferencing.scala | 25 +-- .../src/dotty/tools/dotc/typer/Typer.scala | 43 ++-- tests/neg/i8012.scala | 4 +- ...1723f09b9f77c99c52b709965e580a61706e.scala | 2 +- tests/pos/i18096.scala | 4 + tests/run/i16065.scala | 41 ++++ 10 files changed, 208 insertions(+), 130 deletions(-) create mode 100644 tests/pos/i18096.scala create mode 100644 tests/run/i16065.scala 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 6a408cf05c45..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,104 +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 => - 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 } @@ -6450,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 ff0ec49d373a..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 => 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 3009de413b88..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,28 +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 => + 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 `$pt` + em"""Implementation restriction: cannot convert this expression to `$samParent` |because its result type `${mt.resultType}` is a contextual function type.""", tree.srcPos) - - // 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]) 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) @@ -4000,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) @@ -4169,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 = @@ -4210,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/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. + } +}