From 8db838eb7f19662fe453f36bb12b4fcdf6a33a9f Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 4 Oct 2013 10:53:57 -0700 Subject: Simplify partest.task target, fix typo in comment. --- build.xml | 4 ++-- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.xml b/build.xml index b5e77832df..239a4f50a8 100755 --- a/build.xml +++ b/build.xml @@ -1623,7 +1623,7 @@ TODO: - + - + diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index ecd987b031..7d61e701fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2484,7 +2484,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper assert(pt.typeSymbol == PartialFunctionClass, s"PartialFunction synthesis for match in $tree requires PartialFunction expected type, but got $pt.") val targs = pt.dealiasWiden.typeArgs - // if targs.head isn't fully defined, we can translate --> error + // if targs.head isn't fully defined, we can't translate --> error targs match { case argTp :: _ if isFullyDefined(argTp) => // ok case _ => // uh-oh -- cgit v1.2.3 From e864129a7e7f01d76e177d1bba46d54aea39368e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 4 Oct 2013 10:54:50 -0700 Subject: Clarify findMembers, add reverse engineered docs When looking for deferred members, it only makes sense to retry when deferred members aren't excluded. --- src/reflect/scala/reflect/internal/Types.scala | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 4367e51041..be7ff84c65 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -979,6 +979,18 @@ trait Types else (baseClasses.head.newOverloaded(this, alts)) } + /** Find all members meeting the flag requirements. + * + * If you require a DEFERRED member, you will get it if it exists -- even if there's an overriding concrete member. + * If you exclude DEFERRED members, or don't specify any requirements, + * you won't get deferred members (whether they have an overriding concrete member or not) + * + * Thus, findMember requiring DEFERRED flags yields deferred members, + * while `findMember(excludedFlags = 0, requiredFlags = 0).filter(_.isDeferred)` may not (if there's a corresponding concrete member) + * + * Requirements take precedence over exclusions, so requiring and excluding DEFERRED will yield a DEFERRED member (if there is one). + * + */ def findMembers(excludedFlags: Long, requiredFlags: Long): Scope = { def findMembersInternal: Scope = { var members: Scope = null @@ -988,10 +1000,10 @@ trait Types //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG var required = requiredFlags var excluded = excludedFlags | DEFERRED - var continue = true + var retryForDeferred = true var self: Type = null - while (continue) { - continue = false + while (retryForDeferred) { + retryForDeferred = false val bcs0 = baseClasses var bcs = bcs0 while (!bcs.isEmpty) { @@ -1023,7 +1035,7 @@ trait Types } if (others eq null) members enter sym } else if (excl == DEFERRED) { - continue = true + retryForDeferred = (excludedFlags & DEFERRED) == 0 } } entry = entry.next @@ -1033,7 +1045,7 @@ trait Types } // while (!bcs.isEmpty) required |= DEFERRED excluded &= ~(DEFERRED.toLong) - } // while (continue) + } // while (retryForDeferred) if (Statistics.canEnable) Statistics.popTimer(typeOpsStack, start) if (members eq null) EmptyScope else members } -- cgit v1.2.3 From 657e85fe2412cdadc5ee9dc348159b32dcdfcba7 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 3 Oct 2013 22:04:59 -0700 Subject: Single Abstract Method support: synthesis helpers `synthesizeSAMFunction` will be used to expand the following tree: ``` { (p1: T1, ..., pN: TN) => body } : S ``` to: ``` { def apply$body(p1: T1, ..., pN: TN): T = body new S { def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN) } } ``` The expansion assumes `S` (the expected type) defines a single abstract method (let's call that method `apply` for simplicity). 1. If 'T' is not fully defined, it is inferred by type checking `def apply$body` without a result type before type checking the block. The method's inferred result type is used instead of T`. [See test/files/pos/sammy_poly.scala] 2. To more easily enforce S's members are not in scope in `body`, that tree goes to the `apply$body` method that's outside the anonymous subclass of S. (The separate `apply$body` method simplifies the implementation of 1&2.) 3. The following restrictions apply to S: 1. Its primary constructor (if any) must be public, no-args, not overloaded. 2. S must have exactly one abstract member, its SAM 3. SAM must take exactly one argument list 4. SAM must be monomorphic We may later relax these requirements to allow an implicit argument list, both on the constructor and the SAM. Could also let the SAM be polymorphic. --- .../scala/tools/nsc/typechecker/Typers.scala | 154 +++++++++++++++++++++ .../scala/reflect/internal/Definitions.scala | 38 +++++ 2 files changed, 192 insertions(+) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 7d61e701fe..79039e3436 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2693,6 +2693,160 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } + /** Synthesize and type check the implementation of a type with a Single Abstract Method + * + * `{ (p1: T1, ..., pN: TN) => body } : S` + * + * expands to (where `S` is the expected type that defines a single abstract method named `apply`) + * + * `{ + * def apply$body(p1: T1, ..., pN: TN): T = body + * new S { + * def apply(p1: T1, ..., pN: TN): T = apply$body(p1,..., pN) + * } + * }` + * + * If 'T' is not fully defined, it is inferred by type checking + * `apply$body` without a result type before type checking the block. + * The method's inferred result type is used instead of T`. [See test/files/pos/sammy_poly.scala] + * + * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `samClassTp`, + * and `resPt` is derived from `samClassTp` -- it may be fully defined, or not... + * + * The function's body is put in a method outside of the class definition to enforce scoping. + * S's members should not be in scope in `body`. + * + * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), + * is largely to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * + * NOTE: it would be nicer to not have to type check `apply$body` separately when `T` is not fully defined. + * However T must be fully defined before we type the instantiation, as it'll end up as a parent type, + * which must be fully defined. Would be nice to have some kind of mechanism to insert type vars in a block of code, + * and have the instantiation of the first occurrence propagate to the rest of the block. + */ + def synthesizeSAMFunction(sam: Symbol, fun: Function, resPt: Type, samClassTp: Type, mode: Mode): Tree = { + // assert(fun.vparams forall (vp => isFullyDefined(vp.tpt.tpe))) -- by construction, as we take them from sam's info + val sampos = fun.pos + + // if the expected sam type is fully defined, use it for the method's result type + // otherwise, NoType, so that type inference will determine the method's result type + // resPt is syntactically contained in samClassTp, so if the latter is fully defined, so is the former + // ultimately, we want to fully define samClassTp as it is used as the superclass of our anonymous class + val samDefTp = if (isFullyDefined(resPt)) resPt else NoType + val bodyName = newTermName(sam.name + "$body") + + // `def '${sam.name}\$body'($p1: $T1, ..., $pN: $TN): $resPt = $body` + val samBodyDef = + DefDef(NoMods, + bodyName, + Nil, + List(fun.vparams.map(_.duplicate)), // must duplicate as we're also using them for `samDef` + TypeTree(samDefTp) setPos sampos.focus, + fun.body) + + // If we need to enter the sym for the body def before type checking the block, + // we'll create a nested context, as explained below. + var nestedTyper = this + + // Type check body def before classdef to fully determine samClassTp (if necessary). + // As `samClassTp` determines a parent type for the class, + // we can't type check `block` in one go unless `samClassTp` is fully defined. + val samClassTpFullyDefined = + if (isFullyDefined(samClassTp)) samClassTp + else try { + // This creates a symbol for samBodyDef with a type completer that'll be triggered immediately below. + // The symbol is entered in the same scope used for the block below, and won't thus be reentered later. + // It has to be a new scope, though, or we'll "get ambiguous reference to overloaded definition" [pos/sammy_twice.scala] + // makeSilent: [pos/nonlocal-unchecked.scala -- when translation all functions to sams] + val nestedCtx = enterSym(context.makeNewScope(context.tree, context.owner).makeSilent(), samBodyDef) + nestedTyper = newTyper(nestedCtx) + + // NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above + val actualSamType = samBodyDef.symbol.info + + // println(samClassTp) + + // we're trying to fully define the type arguments for this type constructor + val samTyCon = samClassTp.typeSymbol.typeConstructor + + // the unknowns + val tparams = samClassTp.typeSymbol.typeParams + // ... as typevars + val tvars = tparams map freshVar + + // 1. Recover partial information: + // - derive a type from samClassTp that has the corresponding tparams for type arguments that aren't fully defined + // - constrain typevars to be equal to type args that are fully defined + val samClassTpMoreDefined = appliedType(samTyCon, + (samClassTp.typeArgs, tparams, tvars).zipped map { + case (a, _, tv) if isFullyDefined(a) => tv =:= a; a + case (_, p, _) => p.typeConstructor + }) + + // the method type we're expecting the synthesized sam to have, based on the expected sam type, + // where fully defined type args to samClassTp have been preserved, + // with the unknown args replaced by their corresponding type param + val expectedSamType = samClassTpMoreDefined.memberInfo(sam) + + // 2. make sure the body def's actual type (formals and result) conforms to + // sam's expected type (in terms of the typevars that represent the sam's class's type params) + actualSamType <:< expectedSamType.substituteTypes(tparams, tvars) + + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) + + debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by ${actualSamType} <:< ${expectedSamType} --> $targs for $tparams") + + // a fully defined samClassTp + appliedType(samTyCon, targs) + } catch { + case _: NoInstance | _: TypeError => + println("TODO: OOPS") + samClassTp + } + + // `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)` + val samDef = + DefDef(Modifiers(FINAL | OVERRIDE), + sam.name.toTermName, + Nil, + List(fun.vparams), + TypeTree(samBodyDef.tpt.tpe) setPos sampos.focus, + Apply(Ident(bodyName), fun.vparams map (p => Ident(p.name))) + ) + + val serializableParentAddendum = + if (typeIsSubTypeOfSerializable(samClassTp)) Nil + else List(TypeTree(SerializableTpe)) + + val classDef = + ClassDef(Modifiers(FINAL), tpnme.ANON_FUN_NAME, tparams = Nil, + gen.mkTemplate( + parents = TypeTree(samClassTpFullyDefined) :: serializableParentAddendum, + self = emptyValDef, + constrMods = NoMods, + vparamss = ListOfNil, + body = List(samDef), + superPos = sampos.focus + ) + ) + + // type checking the whole block, so that everything is packaged together nicely + // and we don't have to create any symbols by hand + val block = + nestedTyper.typedPos(sampos, mode, samClassTpFullyDefined) { + Block( + samBodyDef, + classDef, + Apply(Select(New(Ident(tpnme.ANON_FUN_NAME)), nme.CONSTRUCTOR), Nil) + ) + } + + classDef.symbol addAnnotation AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()) + block + } + + private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = { val numVparams = fun.vparams.length if (numVparams > definitions.MaxFunctionArity) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 7b2e40b59c..6fb99b9a5b 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -724,6 +724,44 @@ trait Definitions extends api.StandardDefinitions { (sym eq PartialFunctionClass) || (sym eq AbstractPartialFunctionClass) } + /** The single abstract method declared by type `tp` (or `NoSymbol` if it cannot be found). + * + * The method must be monomorphic and have exactly one parameter list. + * The class defining the method is a supertype of `tp` that + * has a public no-arg primary constructor. + */ + def samOf(tp: Type): Symbol = { + // if tp has a constructor, it must be public and must not take any arguments + // (not even an implicit argument list -- to keep it simple for now) + val tpSym = tp.typeSymbol + val ctor = tpSym.primaryConstructor + val ctorOk = !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1) + + if (tpSym.exists && ctorOk) { + // find the single abstract member, if there is one + // don't go out requiring DEFERRED members, as you will get them even if there's a concrete override: + // scala> abstract class X { def m: Int } + // scala> class Y extends X { def m: Int = 1} + // scala> typeOf[Y].deferredMembers + // Scopes(method m, method getClass) + // + // scala> typeOf[Y].members.filter(_.isDeferred) + // Scopes() + // must filter out "universal" members (getClass is deferred for some reason) + val deferredMembers = ( + tp membersBasedOnFlags (excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD) + filter (mem => mem.isDeferred && !isUniversalMember(mem)) + ) + + // if there is only one, it's monomorphic and has a single argument list + if (deferredMembers.size == 1 && + deferredMembers.head.typeParams.isEmpty && + deferredMembers.head.info.paramSectionCount == 1) + deferredMembers.head + else NoSymbol + } else NoSymbol + } + def arrayType(arg: Type) = appliedType(ArrayClass, arg) def byNameType(arg: Type) = appliedType(ByNameParamClass, arg) def iteratorOfType(tp: Type) = appliedType(IteratorClass, tp) -- cgit v1.2.3 From 67062db57c1abef88e0049dac5d82d4f13375a48 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 3 Oct 2013 23:02:27 -0700 Subject: Single Abstract Method support: synthesize SAMs Under `-Xexperimental`, `typedFunction` invokes `synthesizeSAMFunction` when the expected type for the function literal (`pt`) is not the built-in `FunctionN` type of the expected arity, but `pt` does have a SAM with the expected number of arguments. PS: We'll require `import language.sam` instead of `-Xexperimental`, as soon as the SIP is ready and there are more tests. --- .../scala/tools/nsc/typechecker/Typers.scala | 54 ++++++++++++++++++---- test/files/pos/sammy_poly.flags | 1 + test/files/pos/sammy_poly.scala | 6 +++ test/files/pos/sammy_scope.flags | 1 + test/files/pos/sammy_scope.scala | 8 ++++ test/files/pos/sammy_twice.flags | 1 + test/files/pos/sammy_twice.scala | 9 ++++ test/files/pos/t6221.scala | 8 +++- 8 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 test/files/pos/sammy_poly.flags create mode 100644 test/files/pos/sammy_poly.scala create mode 100644 test/files/pos/sammy_scope.flags create mode 100644 test/files/pos/sammy_scope.scala create mode 100644 test/files/pos/sammy_twice.flags create mode 100644 test/files/pos/sammy_twice.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 79039e3436..dfd962e13e 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2846,18 +2846,45 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper block } - + /** Type check a function literal. + * + * Based on the expected type pt, potentially synthesize an instance of + * - PartialFunction, + * - a type with a Single Abstract Method (under -Xexperimental for now). + */ private def typedFunction(fun: Function, mode: Mode, pt: Type): Tree = { val numVparams = fun.vparams.length - if (numVparams > definitions.MaxFunctionArity) - return MaxFunctionArityError(fun) + val FunctionSymbol = + if (numVparams > definitions.MaxFunctionArity) NoSymbol + else FunctionClass(numVparams) - val FunctionSymbol = FunctionClass(numVparams) - val (argpts, respt) = pt baseType FunctionSymbol match { - case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => NoType), WildcardType) - } - if (argpts.lengthCompare(numVparams) != 0) + /* The Single Abstract Member of pt, unless pt is the built-in function type of the expected arity, + * as `(a => a): Int => Int` should not (yet) get the sam treatment. + */ + val sam = + if (!settings.Xexperimental || pt.typeSymbol == FunctionSymbol) NoSymbol + else samOf(pt) + + /* The SAM case comes first so that this works: + * abstract class MyFun extends (Int => Int) + * (a => a): MyFun + * + * Note that the arity of the sam must correspond to the arity of the function. + */ + val (argpts, respt) = + if (sam.exists && sameLength(sam.info.params, fun.vparams)) { + val samInfo = pt memberInfo sam + (samInfo.paramTypes, samInfo.resultType) + } else { + pt baseType FunctionSymbol match { + case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) + case _ => (fun.vparams map (_ => NoType), WildcardType) + } + } + + if (!FunctionSymbol.exists) + MaxFunctionArityError(fun) + else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts) else { foreach2(fun.vparams, argpts) { (vparam, argpt) => @@ -2868,7 +2895,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper fun match { case etaExpansion(vparams, fn, args) => silent(_.typed(fn, mode.forFunMode, pt)) filter (_ => context.undetparams.isEmpty) map { fn1 => - // if context,undetparams is not empty, the function was polymorphic, + // if context.undetparams is not empty, the function was polymorphic, // so we need the missing arguments to infer its type. See #871 //println("typing eta "+fun+":"+fn1.tpe+"/"+context.undetparams) val ftpe = normalize(fn1.tpe) baseType FunctionClass(numVparams) @@ -2896,6 +2923,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe outerTyper.synthesizePartialFunction(p.name, p.pos, fun.body, mode, pt) + + // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` + // to an instance of the corresponding anonymous subclass of `pt`. + case _ if sam.exists => + newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode) + + // regular Function case _ => val vparamSyms = fun.vparams map { vparam => enterSym(context, vparam) diff --git a/test/files/pos/sammy_poly.flags b/test/files/pos/sammy_poly.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/pos/sammy_poly.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala new file mode 100644 index 0000000000..f03be4f8f5 --- /dev/null +++ b/test/files/pos/sammy_poly.scala @@ -0,0 +1,6 @@ +// test synthesizeSAMFunction where the sam type is not fully defined +class T { + trait F[T, U] { def apply(x: T): U } + def app[T, U](x: T)(f: F[T, U]): U = f(x) + app(1)(x => List(x)) +} \ No newline at end of file diff --git a/test/files/pos/sammy_scope.flags b/test/files/pos/sammy_scope.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/pos/sammy_scope.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/pos/sammy_scope.scala b/test/files/pos/sammy_scope.scala new file mode 100644 index 0000000000..8f1fe7058e --- /dev/null +++ b/test/files/pos/sammy_scope.scala @@ -0,0 +1,8 @@ +// test synthesizeSAMFunction: scope hygiene +abstract class SamFun[T1, R] { self => + def apply(v1: T1): R + + // this should type check, as the apply ref is equivalent to self.apply + // it shouldn't resolve to the sam's apply that's synthesized (that wouldn't type check, hence the pos test) + def compose[A](g: SamFun[A, T1]): SamFun[A, R] = { x => apply(g(x)) } +} \ No newline at end of file diff --git a/test/files/pos/sammy_twice.flags b/test/files/pos/sammy_twice.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/pos/sammy_twice.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/pos/sammy_twice.scala b/test/files/pos/sammy_twice.scala new file mode 100644 index 0000000000..c91f5b9fd2 --- /dev/null +++ b/test/files/pos/sammy_twice.scala @@ -0,0 +1,9 @@ +// test repeated synthesizeSAMFunction where the sam type is not fully defined +// the naive implementation would enter the same apply$body in the same scope twice +trait F[T, U] { def apply(x: T): U } + +class C { + def app[T, U](x: T)(f: F[T, U]): U = f(x) + app(1)(x => List(x)) + app(2)(x => List(x)) +} \ No newline at end of file diff --git a/test/files/pos/t6221.scala b/test/files/pos/t6221.scala index dd7776f596..34f02859f3 100644 --- a/test/files/pos/t6221.scala +++ b/test/files/pos/t6221.scala @@ -7,23 +7,27 @@ class MyCollection[A] { class OtherFunc[-A, +B] {} object Test { - implicit def functionToMyFunc[A, B](f: A => B): MyFunc[A, B] = new MyFunc + implicit def functionToMyFunc[A, B](f: A => B): MyFunc[A, B] = new MyFunc // = new MyFunc[A,Nothing](); - implicit def otherFuncToMyFunc[A, B](f: OtherFunc[A, B]): MyFunc[A, B] = new MyFunc + implicit def otherFuncToMyFunc[A, B](f: OtherFunc[A, B]): MyFunc[A, B] = new MyFunc // = new MyFunc[A,Nothing](); def main(args: Array[String]) { val col = new MyCollection[Int] // Doesn't compile: error: missing parameter type for expanded function ((x$1) => x$1.toString) println(col.map(_.toString)) + // scala.this.Predef.println(col.map[String](Test.this.functionToMyFunc[Int, String](((x$1: Int) => x$1.toString())))); // Doesn't compile: error: missing parameter type println(col.map(x => x.toString)) + // scala.this.Predef.println(col.map[String](Test.this.functionToMyFunc[Int, String](((x: Int) => x.toString())))); // Does compile println(col.map((x: Int) => x.toString)) + // scala.this.Predef.println(col.map[String](Test.this.functionToMyFunc[Int, String](((x: Int) => x.toString())))); // Does compile (even though type params of OtherFunc not given) println(col.map(new OtherFunc)) + // scala.this.Predef.println(col.map[Nothing](Test.this.otherFuncToMyFunc[Any, Nothing](new OtherFunc[Any,Nothing]()))) } } \ No newline at end of file -- cgit v1.2.3 From 00c9c164f57223738789bdfd14959367a39c8d32 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Tue, 8 Oct 2013 09:01:49 +0200 Subject: Don't pursue SAM translation after an arity mismatch. Before this change: scala> trait T { def apply(a: Int): Int } defined trait T scala> ((x: Int, y: Int) => 0): T :9: error: object creation impossible, since method apply in trait T of type (a: Int)Int is not defined ((x: Int, y: Int) => 0): T ^ After the change, these cases report the same errors as they do *without* -Xexperimental. --- .../scala/tools/nsc/typechecker/Typers.scala | 5 ++- test/files/neg/sammy_wrong_arity.check | 52 ++++++++++++++++++++++ test/files/neg/sammy_wrong_arity.flags | 1 + test/files/neg/sammy_wrong_arity.scala | 22 +++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 test/files/neg/sammy_wrong_arity.check create mode 100644 test/files/neg/sammy_wrong_arity.flags create mode 100644 test/files/neg/sammy_wrong_arity.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index dfd962e13e..8e74ae3e0b 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2871,8 +2871,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * * Note that the arity of the sam must correspond to the arity of the function. */ + val samViable = sam.exists && sameLength(sam.info.params, fun.vparams) val (argpts, respt) = - if (sam.exists && sameLength(sam.info.params, fun.vparams)) { + if (samViable) { val samInfo = pt memberInfo sam (samInfo.paramTypes, samInfo.resultType) } else { @@ -2926,7 +2927,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` // to an instance of the corresponding anonymous subclass of `pt`. - case _ if sam.exists => + case _ if samViable => newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode) // regular Function diff --git a/test/files/neg/sammy_wrong_arity.check b/test/files/neg/sammy_wrong_arity.check new file mode 100644 index 0000000000..af547a201d --- /dev/null +++ b/test/files/neg/sammy_wrong_arity.check @@ -0,0 +1,52 @@ +sammy_wrong_arity.scala:6: error: type mismatch; + found : () => Int + required: T1 + (() => 0): T1 + ^ +sammy_wrong_arity.scala:7: error: type mismatch; + found : Any => Int + required: T2 + ((x: Any) => 0): T2 + ^ +sammy_wrong_arity.scala:9: error: type mismatch; + found : Any => Int + required: T0 + ((x: Any) => 0): T0 + ^ +sammy_wrong_arity.scala:10: error: type mismatch; + found : Any => Int + required: T2 + ((x: Any) => 0): T2 + ^ +sammy_wrong_arity.scala:12: error: type mismatch; + found : (Any, Any) => Int + required: T0 + ((x: Any, y: Any) => 0): T0 + ^ +sammy_wrong_arity.scala:13: error: type mismatch; + found : (Any, Any) => Int + required: T1 + ((x: Any, y: Any) => 0): T1 + ^ +sammy_wrong_arity.scala:15: error: missing parameter type + ((x) => 0): T2 + ^ +sammy_wrong_arity.scala:17: error: missing parameter type + ((x) => 0): T0 + ^ +sammy_wrong_arity.scala:18: error: missing parameter type + ((x) => 0): T2 + ^ +sammy_wrong_arity.scala:20: error: missing parameter type + ((x, y) => 0): T0 + ^ +sammy_wrong_arity.scala:20: error: missing parameter type + ((x, y) => 0): T0 + ^ +sammy_wrong_arity.scala:21: error: missing parameter type + ((x, y) => 0): T1 + ^ +sammy_wrong_arity.scala:21: error: missing parameter type + ((x, y) => 0): T1 + ^ +13 errors found diff --git a/test/files/neg/sammy_wrong_arity.flags b/test/files/neg/sammy_wrong_arity.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/neg/sammy_wrong_arity.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/neg/sammy_wrong_arity.scala b/test/files/neg/sammy_wrong_arity.scala new file mode 100644 index 0000000000..d03d266a0b --- /dev/null +++ b/test/files/neg/sammy_wrong_arity.scala @@ -0,0 +1,22 @@ +trait T0 { def ap(): Int } +trait T1 { def ap(a: Any): Int } +trait T2 { def ap(a: Any, b: Any): Int } + +class Test { + (() => 0): T1 + ((x: Any) => 0): T2 + + ((x: Any) => 0): T0 + ((x: Any) => 0): T2 + + ((x: Any, y: Any) => 0): T0 + ((x: Any, y: Any) => 0): T1 + + ((x) => 0): T2 + + ((x) => 0): T0 + ((x) => 0): T2 + + ((x, y) => 0): T0 + ((x, y) => 0): T1 +} -- cgit v1.2.3 From 4265ab6345d7e84e36dc1444fdbbba3643b29b35 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 7 Oct 2013 16:58:46 -0700 Subject: Extract SerialVersionUIDAnnotation. Make SAM body synthetic. Addressing review feedback. --- src/compiler/scala/tools/nsc/transform/UnCurry.scala | 5 +---- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 12 +++++------- src/reflect/scala/reflect/internal/Definitions.scala | 3 ++- test/files/pos/sammy_single.flags | 1 + test/files/pos/sammy_single.scala | 9 +++++++++ 5 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 test/files/pos/sammy_single.flags create mode 100644 test/files/pos/sammy_single.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index d97f62d5d6..ccf2266540 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -85,9 +85,6 @@ abstract class UnCurry extends InfoTransform transformFunction(result) } - private lazy val serialVersionUIDAnnotation = - AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()) - // I don't have a clue why I'm catching TypeErrors here, but it's better // than spewing stack traces at end users for internal errors. Examples // which hit at this point should not be hard to come by, but the immediate @@ -220,7 +217,7 @@ abstract class UnCurry extends InfoTransform case fun1 if fun1 ne fun => fun1 case _ => val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation serialVersionUIDAnnotation + val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation anonClass setInfo ClassInfoType(parents, newScope, anonClass) val targs = fun.tpe.typeArgs diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 8e74ae3e0b..2ca858b732 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2498,9 +2498,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // targs must conform to Any for us to synthesize an applyOrElse (fallback to apply otherwise -- typically for @cps annotated targs) val targsValidParams = targs forall (_ <:< AnyTpe) - val anonClass = (context.owner - newAnonymousFunctionClass tree.pos - addAnnotation AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List())) + val anonClass = context.owner newAnonymousFunctionClass tree.pos addAnnotation SerialVersionUIDAnnotation import CODE._ @@ -2795,19 +2793,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // solve constraints tracked by tvars val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) - debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by ${actualSamType} <:< ${expectedSamType} --> $targs for $tparams") + debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams") // a fully defined samClassTp appliedType(samTyCon, targs) } catch { case _: NoInstance | _: TypeError => - println("TODO: OOPS") + devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)") samClassTp } // `final override def ${sam.name}($p1: $T1, ..., $pN: $TN): $resPt = ${sam.name}\$body'($p1, ..., $pN)` val samDef = - DefDef(Modifiers(FINAL | OVERRIDE), + DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), sam.name.toTermName, Nil, List(fun.vparams), @@ -2842,7 +2840,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper ) } - classDef.symbol addAnnotation AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()) + classDef.symbol addAnnotation SerialVersionUIDAnnotation block } diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 6fb99b9a5b..5970514102 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -750,7 +750,7 @@ trait Definitions extends api.StandardDefinitions { // must filter out "universal" members (getClass is deferred for some reason) val deferredMembers = ( tp membersBasedOnFlags (excludedFlags = BridgeAndPrivateFlags, requiredFlags = METHOD) - filter (mem => mem.isDeferred && !isUniversalMember(mem)) + filter (mem => mem.isDeferredNotDefault && !isUniversalMember(mem)) // TODO: test ) // if there is only one, it's monomorphic and has a single argument list @@ -1052,6 +1052,7 @@ trait Definitions extends api.StandardDefinitions { lazy val ScalaInlineClass = requiredClass[scala.inline] lazy val ScalaNoInlineClass = requiredClass[scala.noinline] lazy val SerialVersionUIDAttr = requiredClass[scala.SerialVersionUID] + lazy val SerialVersionUIDAnnotation = AnnotationInfo(SerialVersionUIDAttr.tpe, List(Literal(Constant(0))), List()) lazy val SpecializedClass = requiredClass[scala.specialized] lazy val ThrowsClass = requiredClass[scala.throws[_]] lazy val TransientAttr = requiredClass[scala.transient] diff --git a/test/files/pos/sammy_single.flags b/test/files/pos/sammy_single.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/pos/sammy_single.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/pos/sammy_single.scala b/test/files/pos/sammy_single.scala new file mode 100644 index 0000000000..7a3d272983 --- /dev/null +++ b/test/files/pos/sammy_single.scala @@ -0,0 +1,9 @@ +// test that dependent types work +// TODO: def apply(x: String): x.type does NOT work yet +object Test { + val s: String = "" + + trait T { def apply(x: s.type): s.type } + + val preservedResult: s.type = ((x => x): T)(s) +} \ No newline at end of file -- cgit v1.2.3 From 58ba39ce851460c18f2f81341a8c80e0e51d2764 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 8 Oct 2013 19:00:48 -0700 Subject: Single Abstract Method support: java8 test Inspired by test/files/run/t7398.scala and sammy_poly. Added some notes to original tests. Elaborating on that note: we don't yet desugar `f(a)` to `f.sam(a)`, like we do for regular functions: `f(a)` becomes `f.apply(a)`. It seems pleasingly symmetrical and is easy to implement, but not sure it's a good idea... --- test/files/pos/sammy_poly.scala | 1 + test/files/run/sammy_java8.flags | 1 + test/files/run/sammy_java8.scala | 34 ++++++++++++++++++++++++++++++++++ test/files/run/t7398.scala | 2 +- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 test/files/run/sammy_java8.flags create mode 100644 test/files/run/sammy_java8.scala diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala index f03be4f8f5..c629be7166 100644 --- a/test/files/pos/sammy_poly.scala +++ b/test/files/pos/sammy_poly.scala @@ -1,6 +1,7 @@ // test synthesizeSAMFunction where the sam type is not fully defined class T { trait F[T, U] { def apply(x: T): U } + // NOTE: the f(x) desugaring for now assumes the single abstract method is called 'apply' def app[T, U](x: T)(f: F[T, U]): U = f(x) app(1)(x => List(x)) } \ No newline at end of file diff --git a/test/files/run/sammy_java8.flags b/test/files/run/sammy_java8.flags new file mode 100644 index 0000000000..48fd867160 --- /dev/null +++ b/test/files/run/sammy_java8.flags @@ -0,0 +1 @@ +-Xexperimental diff --git a/test/files/run/sammy_java8.scala b/test/files/run/sammy_java8.scala new file mode 100644 index 0000000000..db9df7f5fe --- /dev/null +++ b/test/files/run/sammy_java8.scala @@ -0,0 +1,34 @@ +import scala.tools.partest._ + +// java8 version of sammy_poly.scala +object Test extends CompilerTest { + import global._ + + override lazy val units: List[CompilationUnit] = { + global.settings.Xexperimental.value = true + + // This test itself does not depend on JDK8. + javaCompilationUnits(global)(samSource) ++ + compilationUnits(global)(useSamSource) + } + + private def samSource = """ +// trait F[T, U] { def apply(x: T): U } +public interface F { + U apply(T t); + default void yadayada() { + throw new UnsupportedOperationException("yadayada"); + } +} + """ + + private def useSamSource = """ +class T { + def app[T, U](x: T)(f: F[T, U]): U = f(x) + app(1)(x => List(x)) +} + """ + + // We're only checking we can compile it. + def check(source: String, unit: global.CompilationUnit): Unit = () +} diff --git a/test/files/run/t7398.scala b/test/files/run/t7398.scala index 493c4dcf40..4b46850768 100644 --- a/test/files/run/t7398.scala +++ b/test/files/run/t7398.scala @@ -21,6 +21,6 @@ public interface Iterator { } """ - // We're only checking we can parse it. + // We're only checking we can compile it. def check(source: String, unit: global.CompilationUnit): Unit = () } -- cgit v1.2.3 From 71d4e285064e9d3475a0badecf283ea95166df6d Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 8 Oct 2013 17:23:47 -0700 Subject: Remove stray debug comment --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2ca858b732..58c658ee8a 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2762,8 +2762,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // NOTE: this `samBodyDef.symbol.info` runs the type completer set up by the enterSym above val actualSamType = samBodyDef.symbol.info - // println(samClassTp) - // we're trying to fully define the type arguments for this type constructor val samTyCon = samClassTp.typeSymbol.typeConstructor -- cgit v1.2.3