diff options
101 files changed, 1994 insertions, 1514 deletions
@@ -4,8 +4,8 @@ * What you see below is very much work-in-progress. The following features are implemented: * - Compiling all classses for the compiler and library ("compile" in the respective subprojects) * - Running JUnit tests ("test") and partest ("test/it:test") - * - Creating build-sbt/quick with all compiled classes and launcher scripts ("dist/mkQuick") - * - Creating build-sbt/pack with all JARs and launcher scripts ("dist/mkPack") + * - Creating build/quick with all compiled classes and launcher scripts ("dist/mkQuick") + * - Creating build/pack with all JARs and launcher scripts ("dist/mkPack") * - Building all scaladoc sets ("doc") * - Publishing ("publishDists" and standard sbt tasks like "publish" and "publishLocal") * @@ -722,8 +722,8 @@ def configureAsSubproject(project: Project): Project = { lazy val buildDirectory = settingKey[File]("The directory where all build products go. By default ./build") lazy val mkBin = taskKey[Seq[File]]("Generate shell script (bash or Windows batch).") -lazy val mkQuick = taskKey[Unit]("Generate a full build, including scripts, in build-sbt/quick") -lazy val mkPack = taskKey[Unit]("Generate a full build, including scripts, in build-sbt/pack") +lazy val mkQuick = taskKey[Unit]("Generate a full build, including scripts, in build/quick") +lazy val mkPack = taskKey[Unit]("Generate a full build, including scripts, in build/pack") // Defining these settings is somewhat redundant as we also redefine settings that depend on them. // However, IntelliJ's project import works better when these are set correctly. @@ -782,7 +782,7 @@ def generateServiceProviderResources(services: (String, String)*): Setting[_] = } }.taskValue -buildDirectory in ThisBuild := (baseDirectory in ThisBuild).value / "build-sbt" +buildDirectory in ThisBuild := (baseDirectory in ThisBuild).value / "build" // Add tab completion to partest commands += Command("partest")(_ => PartestUtil.partestParser((baseDirectory in ThisBuild).value, (baseDirectory in ThisBuild).value / "test")) { (state, parsed) => diff --git a/spec/03-types.md b/spec/03-types.md index 94b7916634..e2a6523dff 100644 --- a/spec/03-types.md +++ b/spec/03-types.md @@ -778,25 +778,22 @@ These notions are defined mutually recursively as follows. ## Relations between types -We define two relations between types. +We define the following relations between types. -|Name | Symbolically |Interpretation | -|-----------------|----------------|-------------------------------------------------| -|Equivalence |$T \equiv U$ |$T$ and $U$ are interchangeable in all contexts. | -|Conformance |$T <: U$ |Type $T$ conforms to type $U$. | +| Name | Symbolically | Interpretation | +|------------------|----------------|----------------------------------------------------| +| Equivalence | $T \equiv U$ | $T$ and $U$ are interchangeable in all contexts. | +| Conformance | $T <: U$ | Type $T$ conforms to ("is a subtype of") type $U$. | +| Weak Conformance | $T <:_w U$ | Augments conformance for primitive numeric types. | +| Compatibility | | Type $T$ conforms to type $U$ after conversions. | ### Equivalence -Equivalence $(\equiv)$ between types is the smallest congruence [^congruence] such that -the following holds: +Equivalence $(\equiv)$ between types is the smallest congruence [^congruence] such that the following holds: -- If $t$ is defined by a type alias `type $t$ = $T$`, then $t$ is - equivalent to $T$. -- If a path $p$ has a singleton type `$q$.type`, then - `$p$.type $\equiv q$.type`. -- If $O$ is defined by an object definition, and $p$ is a path - consisting only of package or object selectors and ending in $O$, then - `$O$.this.type $\equiv p$.type`. +- If $t$ is defined by a type alias `type $t$ = $T$`, then $t$ is equivalent to $T$. +- If a path $p$ has a singleton type `$q$.type`, then `$p$.type $\equiv q$.type`. +- If $O$ is defined by an object definition, and $p$ is a path consisting only of package or object selectors and ending in $O$, then `$O$.this.type $\equiv p$.type`. - Two [compound types](#compound-types) are equivalent if the sequences of their component are pairwise equivalent, and occur in the same order, and their refinements are equivalent. Two refinements are equivalent if they @@ -827,14 +824,11 @@ the following holds: ### Conformance -The conformance relation $(<:)$ is the smallest -transitive relation that satisfies the following conditions. +The conformance relation $(<:)$ is the smallest transitive relation that satisfies the following conditions. - Conformance includes equivalence. If $T \equiv U$ then $T <: U$. - For every value type $T$, `scala.Nothing <: $T$ <: scala.Any`. -- For every type constructor $T$ (with any number of type parameters), - `scala.Nothing <: $T$ <: scala.Any`. - +- For every type constructor $T$ (with any number of type parameters), `scala.Nothing <: $T$ <: scala.Any`. - For every class type $T$ such that `$T$ <: scala.AnyRef` one has `scala.Null <: $T$`. - A type variable or abstract type $t$ conforms to its upper bound and its lower bound conforms to $t$. @@ -912,15 +906,12 @@ type $C'$, if one of the following holds. type declaration `type t[$T_1$ , … , $T_n$] >: L <: U` if $L <: t <: U$. -The $(<:)$ relation forms pre-order between types, -i.e. it is transitive and reflexive. _least upper bounds_ and -_greatest lower bounds_ of a set of types -are understood to be relative to that order. -###### Note -The least upper bound or greatest lower bound -of a set of types does not always exist. For instance, consider -the class definitions +#### Least upper bounds and greatest lower bounds +The $(<:)$ relation forms pre-order between types, i.e. it is transitive and reflexive. +This allows us to define _least upper bounds_ and _greatest lower bounds_ of a set of types in terms of that order. +The least upper bound or greatest lower bound of a set of types does not always exist. +For instance, consider the class definitions: ```scala class A[+T] {} @@ -949,11 +940,9 @@ free to pick any one of them. ### Weak Conformance -In some situations Scala uses a more general conformance relation. A -type $S$ _weakly conforms_ -to a type $T$, written $S <:_w -T$, if $S <: T$ or both $S$ and $T$ are primitive number types -and $S$ precedes $T$ in the following ordering. +In some situations Scala uses a more general conformance relation. +A type $S$ _weakly conforms_ to a type $T$, written $S <:_w T$, +if $S <: T$ or both $S$ and $T$ are primitive number types and $S$ precedes $T$ in the following ordering. ```scala Byte $<:_w$ Short @@ -964,15 +953,49 @@ Long $<:_w$ Float Float $<:_w$ Double ``` -A _weak least upper bound_ is a least upper bound with respect to -weak conformance. +A _weak least upper bound_ is a least upper bound with respect to weak conformance. + +### Compatibility +A type $T$ is _compatible_ to a type $U$ if $T$ (or its corresponding function type) [weakly conforms](#weak-conformance) to $U$ +after applying [eta-expansion](06-expressions.html#eta-expansion). If $T$ is a method type, it's converted to the corresponding function type. If the types do not weakly conform, the following alternatives are checked in order: + - [view application](07-implicits.html#views): there's an implicit view from $T$ to $U$; + - dropping by-name modifiers: if $U$ is of the shape `$=> U'$` (and $T$ is not), `$T <:_w U'$`; + - SAM conversion: if $T$ corresponds to a function type, and $U$ declares a single abstract method whose type [corresponds](06-expressions.html#sam-conversion) to the function type $U'$, `$T <:_w U'$`. + +<!--- TODO: include other implicit conversions in addition to view application? + + trait Proc { def go(x: Any): Unit } + + def foo(x: Any => Unit): Unit = ??? + def foo(x: Proc): Unit = ??? + + foo((x: Any) => 1) // works when you drop either foo overload since value discarding is applied + +--> + +#### Examples + +##### Function compatibility via SAM conversion + +Given the definitions + +``` +def foo(x: Int => String): Unit +def foo(x: ToString): Unit + +trait ToString { def convert(x: Int): String } +``` + +The application `foo((x: Int) => x.toString)` [resolves](06-expressions.html#overloading-resolution) to the first overload, +as it's more specific: + - `Int => String` is compatible to `ToString` -- when expecting a value of type `ToString`, you may pass a function literal from `Int` to `String`, as it will be SAM-converted to said function; + - `ToString` is not compatible to `Int => String` -- when expecting a function from `Int` to `String`, you may not pass a `ToString`. ## Volatile Types -Type volatility approximates the possibility that a type parameter or abstract -type instance -of a type does not have any non-null values. A value member of a volatile type -cannot appear in a [path](#paths). +Type volatility approximates the possibility that a type parameter or +abstract type instance of a type does not have any non-null values. +A value member of a volatile type cannot appear in a [path](#paths). A type is _volatile_ if it falls into one of four categories: diff --git a/spec/06-expressions.md b/spec/06-expressions.md index c24ca01c3b..30ad73a3cd 100644 --- a/spec/06-expressions.md +++ b/spec/06-expressions.md @@ -81,10 +81,9 @@ evaluation is immediate. ## The _Null_ Value -The `null` value is of type `scala.Null`, and is thus -compatible with every reference type. It denotes a reference value -which refers to a special “`null`” object. This object -implements methods in class `scala.AnyRef` as follows: +The `null` value is of type `scala.Null`, and thus conforms to every reference type. +It denotes a reference value which refers to a special `null` object. +This object implements methods in class `scala.AnyRef` as follows: - `eq($x\,$)` and `==($x\,$)` return `true` iff the argument $x$ is also the "null" object. @@ -239,38 +238,21 @@ ArgumentExprs ::= `(' [Exprs] `)' Exprs ::= Expr {`,' Expr} ``` -An application `$f$($e_1 , \ldots , e_m$)` applies the -function $f$ to the argument expressions $e_1 , \ldots , e_m$. If $f$ -has a method type `($p_1$:$T_1 , \ldots , p_n$:$T_n$)$U$`, the type of -each argument expression $e_i$ is typed with the -corresponding parameter type $T_i$ as expected type. Let $S_i$ be type -type of argument $e_i$ $(i = 1 , \ldots , m)$. If $f$ is a polymorphic method, -[local type inference](#local-type-inference) is used to determine -type arguments for $f$. If $f$ has some value type, the application is taken to -be equivalent to `$f$.apply($e_1 , \ldots , e_m$)`, -i.e. the application of an `apply` method defined by $f$. - -The function $f$ must be _applicable_ to its arguments $e_1 -, \ldots , e_n$ of types $S_1 , \ldots , S_n$. - -If $f$ has a method type $(p_1:T_1 , \ldots , p_n:T_n)U$ -we say that an argument expression $e_i$ is a _named_ argument if -it has the form $x_i=e'_i$ and $x_i$ is one of the parameter names -$p_1 , \ldots , p_n$. The function $f$ is applicable if all of the following conditions -hold: - -- For every named argument $x_i=e_i'$ the type $S_i$ - is compatible with the parameter type $T_j$ whose name $p_j$ matches $x_i$. -- For every positional argument $e_i$ the type $S_i$ -is compatible with $T_i$. -- If the expected type is defined, the result type $U$ is - compatible to it. - -If $f$ is a polymorphic method it is applicable if -[local type inference](#local-type-inference) can -determine type arguments so that the instantiated method is applicable. If -$f$ has some value type it is applicable if it has a method member named -`apply` which is applicable. +An application `$f(e_1 , \ldots , e_m)$` applies the function `$f$` to the argument expressions `$e_1, \ldots , e_m$`. For this expression to be well-typed, the function must be *applicable* to its arguments, which is defined next by case analysis on $f$'s type. + +If $f$ has a method type `($p_1$:$T_1 , \ldots , p_n$:$T_n$)$U$`, each argument expression $e_i$ is typed with the corresponding parameter type $T_i$ as expected type. Let $S_i$ be the type of argument $e_i$ $(i = 1 , \ldots , m)$. The function $f$ must be _applicable_ to its arguments $e_1, \ldots , e_n$ of types $S_1 , \ldots , S_n$. We say that an argument expression $e_i$ is a _named_ argument if it has the form `$x_i=e'_i$` and `$x_i$` is one of the parameter names `$p_1, \ldots, p_n$`. + +Once the types $S_i$ have been determined, the function $f$ of the above method type is said to be applicable if all of the following conditions hold: + - for every named argument $p_j=e_i'$ the type $S_i$ is [compatible](03-types.html#compatibility) with the parameter type $T_j$; + - for every positional argument $e_i$ the type $S_i$ is [compatible](03-types.html#compatibility) with $T_i$; + - if the expected type is defined, the result type $U$ is [compatible](03-types.html#compatibility) to it. + +If $f$ is a polymorphic method, [local type inference](#local-type-inference) is used to instantiate $f$'s type parameters. +The polymorphic method is applicable if type inference can determine type arguments so that the instantiated method is applicable. + +If $f$ has some value type, the application is taken to be equivalent to `$f$.apply($e_1 , \ldots , e_m$)`, +i.e. the application of an `apply` method defined by $f$. The value `$f$` is applicable to the given arguments if `$f$.apply` is applicable. + Evaluation of `$f$($e_1 , \ldots , e_n$)` usually entails evaluation of $f$ and $e_1 , \ldots , e_n$ in that order. Each argument expression @@ -1141,11 +1123,9 @@ re-thrown. Let $\mathit{pt}$ be the expected type of the try expression. The block $b$ is expected to conform to $\mathit{pt}$. The handler $h$ -is expected conform to type -`scala.PartialFunction[scala.Throwable, $\mathit{pt}\,$]`. The -type of the try expression is the [weak least upper bound](03-types.html#weak-conformance) -of the type of $b$ -and the result type of $h$. +is expected conform to type `scala.PartialFunction[scala.Throwable, $\mathit{pt}\,$]`. +The type of the try expression is the [weak least upper bound](03-types.html#weak-conformance) +of the type of $b$ and the result type of $h$. A try expression `try { $b$ } finally $e$` evaluates the block $b$. If evaluation of $b$ does not cause an exception to be @@ -1178,26 +1158,26 @@ Bindings ::= `(' Binding {`,' Binding} `)' Binding ::= (id | `_') [`:' Type] ``` -The anonymous function `($x_1$: $T_1 , \ldots , x_n$: $T_n$) => e` -maps parameters $x_i$ of types $T_i$ to a result given -by expression $e$. The scope of each formal parameter -$x_i$ is $e$. Formal parameters must have pairwise distinct names. +The anonymous function of arity $n$, `($x_1$: $T_1 , \ldots , x_n$: $T_n$) => e` maps parameters $x_i$ of types $T_i$ to a result given by expression $e$. The scope of each formal parameter $x_i$ is $e$. Formal parameters must have pairwise distinct names. + +In the case of a single untyped formal parameter, `($x\,$) => $e$` can be abbreviated to `$x$ => $e$`. If an anonymous function `($x$: $T\,$) => $e$` with a single typed parameter appears as the result expression of a block, it can be abbreviated to `$x$: $T$ => e`. + +A formal parameter may also be a wildcard represented by an underscore `_`. In that case, a fresh name for the parameter is chosen arbitrarily. + +A named parameter of an anonymous function may be optionally preceded by an `implicit` modifier. In that case the parameter is labeled [`implicit`](07-implicits.html#implicit-parameters-and-views); however the parameter section itself does not count as an [implicit parameter section](07-implicits.html#implicit-parameters). Hence, arguments to anonymous functions always have to be given explicitly. -If the expected type of the anonymous function is of the form -`scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]`, the -expected type of $e$ is $R$ and the type $T_i$ of any of the -parameters $x_i$ can be omitted, in which -case`$T_i$ = $S_i$` is assumed. -If the expected type of the anonymous function is -some other type, all formal parameter types must be explicitly given, -and the expected type of $e$ is undefined. The type of the anonymous -function -is`scala.Function$n$[$S_1 , \ldots , S_n$, $T\,$]`, -where $T$ is the [packed type](#expression-typing) -of $e$. $T$ must be equivalent to a -type which does not refer to any of the formal parameters $x_i$. +### Translation +If the expected type of the anonymous function is of the shape `scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]`, or can be [SAM-converted](#sam-conversion) to such a function type, the type `$T_i$` of a parameter `$x_i$` can be omitted, as far as `$S_i$` is defined in the expected type, and `$T_i$ = $S_i$` is assumed. Furthermore, the expected type when type checking $e$ is $R$. -The anonymous function is evaluated as the instance creation expression +If there is no expected type for the function literal, all formal parameter types `$T_i$` must be specified explicitly, and the expected type of $e$ is undefined. The type of the anonymous function is `scala.Function$n$[$T_1 , \ldots , T_n$, $R\,$]`, where $R$ is the [packed type](#expression-typing) of $e$. $R$ must be equivalent to a type which does not refer to any of the formal parameters $x_i$. + +The eventual run-time value of an anonymous function is determined by the expected type: + - a subclass of one of the builtin function types, `scala.Function$n$[$S_1 , \ldots , S_n$, $R\,$]` (with $S_i$ and $R$ fully defined), + - a [single-abstract-method (SAM) type](#sam-conversion); + - `PartialFunction[$T$, $U$]`, if the function literal is of the shape `x => x match { $\ldots$ }` + - some other type. + +The standard anonymous function evaluates in the same way as the following instance creation expression: ```scala new scala.Function$n$[$T_1 , \ldots , T_n$, $T$] { @@ -1205,22 +1185,11 @@ new scala.Function$n$[$T_1 , \ldots , T_n$, $T$] { } ``` -In the case of a single untyped formal parameter, -`($x\,$) => $e$` -can be abbreviated to `$x$ => $e$`. If an -anonymous function `($x$: $T\,$) => $e$` with a single -typed parameter appears as the result expression of a block, it can be -abbreviated to `$x$: $T$ => e`. +The same evaluation holds for a SAM type, except that the instantiated type is given by the SAM type, and the implemented method is the single abstract method member of this type. -A formal parameter may also be a wildcard represented by an underscore `_`. -In that case, a fresh name for the parameter is chosen arbitrarily. +The underlying platform may provide more efficient ways of constructing these instances, such as Java 8's `invokedynamic` bytecode and `LambdaMetaFactory` class. -A named parameter of an anonymous function may be optionally preceded -by an `implicit` modifier. In that case the parameter is -labeled [`implicit`](07-implicits.html#implicit-parameters-and-views); however the -parameter section itself does not count as an implicit parameter -section in the sense defined [here](07-implicits.html#implicit-parameters). Hence, arguments to -anonymous functions always have to be given explicitly. +A `PartialFunction`'s value receives an additional `isDefinedAt` member, which is derived from the pattern match in the function literal, with each case's body being replaced by `true`, and an added default (if none was given) that evaluates to `false`. ###### Example Examples of anonymous functions: @@ -1290,11 +1259,9 @@ include at least the expressions of the following forms: - A string literal - A class constructed with [`Predef.classOf`](12-the-scala-standard-library.html#the-predef-object) - An element of an enumeration from the underlying platform -- A literal array, of the form - `Array$(c_1 , \ldots , c_n)$`, +- A literal array, of the form `Array$(c_1 , \ldots , c_n)$`, where all of the $c_i$'s are themselves constant expressions -- An identifier defined by a - [constant value definition](04-basic-declarations-and-definitions.html#value-declarations-and-definitions). +- An identifier defined by a [constant value definition](04-basic-declarations-and-definitions.html#value-declarations-and-definitions). ## Statements @@ -1335,10 +1302,6 @@ Implicit conversions can be applied to expressions whose type does not match their expected type, to qualifiers in selections, and to unapplied methods. The available implicit conversions are given in the next two sub-sections. -We say, a type $T$ is _compatible_ to a type $U$ if $T$ weakly conforms -to $U$ after applying [eta-expansion](#eta-expansion) and -[view applications](07-implicits.html#views). - ### Value Conversions The following seven implicit conversions can be applied to an @@ -1382,12 +1345,35 @@ If $e$ has some value type and the expected type is `Unit`, $e$ is converted to the expected type by embedding it in the term `{ $e$; () }`. +###### SAM conversion +An expression `(p1, ..., pN) => body` of function type `(T1, ..., TN) => T` is sam-convertible to the expected type `S` if the following holds: + - the class `C` of `S` declares an abstract method `m` with signature `(p1: A1, ..., pN: AN): R`; + - besides `m`, `C` must not declare or inherit any other deferred value members; + - the method `m` must have a single argument list; + - there must be a type `U` that is a subtype of `S`, so that the expression + `new U { final def m(p1: A1, ..., pN: AN): R = body }` is well-typed (conforming to the expected type `S`); + - for the purpose of scoping, `m` should be considered a static member (`U`'s members are not in scope in `body`); + - `(A1, ..., AN) => R` is a subtype of `(T1, ..., TN) => T` (satisfying this condition drives type inference of unknown type parameters in `S`); + +Note that a function literal that targets a SAM is not necessarily compiled to the above instance creation expression. This is platform-dependent. + +It follows that: + - if class `C` defines a constructor, it must be accessible and must define exactly one, empty, argument list; + - `m` cannot be polymorphic; + - it must be possible to derive a fully-defined type `U` from `S` by inferring any unknown type parameters of `C`. + +Finally, we impose some implementation restrictions (these may be lifted in future releases): + - `C` must not be nested or local (it must not capture its environment, as that results in a zero-argument constructor) + - `C`'s constructor must not have an implicit argument list (this simplifies type inference); + - `C` must not declare a self type (this simplifies type inference); + - `C` must not be `@specialized`. + ###### View Application If none of the previous conversions applies, and $e$'s type does not conform to the expected type $\mathit{pt}$, it is attempted to convert $e$ to the expected type with a [view](07-implicits.html#views). -###### Dynamic Member Selection +###### Selection on `Dynamic` If none of the previous conversions applies, and $e$ is a prefix of a selection $e.x$, and $e$'s type conforms to class `scala.Dynamic`, then the selection is rewritten according to the rules for @@ -1426,34 +1412,31 @@ a function. Let $\mathscr{A}$ be the set of members referenced by $e$. Assume first that $e$ appears as a function in an application, as in `$e$($e_1 , \ldots , e_m$)`. -One first determines the set of functions that is potentially -applicable based on the _shape_ of the arguments. +One first determines the set of functions that is potentially [applicable](#function-applications) +based on the _shape_ of the arguments. -The shape of an argument expression $e$, written $\mathit{shape}(e)$, is +The *shape* of an argument expression $e$, written $\mathit{shape}(e)$, is a type that is defined as follows: + - For a function expression `($p_1$: $T_1 , \ldots , p_n$: $T_n$) => $b$: (Any $, \ldots ,$ Any) => $\mathit{shape}(b)$`, + where `Any` occurs $n$ times in the argument type. + - For a named argument `$n$ = $e$`: $\mathit{shape}(e)$. + - For all other expressions: `Nothing`. -- For a function expression `($p_1$: $T_1 , \ldots , p_n$: $T_n$) => $b$`: - `(Any $, \ldots ,$ Any) => $\mathit{shape}(b)$`, where `Any` occurs $n$ times - in the argument type. -- For a named argument `$n$ = $e$`: $\mathit{shape}(e)$. -- For all other expressions: `Nothing`. - -Let $\mathscr{B}$ be the set of alternatives in $\mathscr{A}$ that are -[_applicable_](#function-applications) -to expressions $(e_1 , \ldots , e_n)$ of types -$(\mathit{shape}(e_1) , \ldots , \mathit{shape}(e_n))$. -If there is precisely one -alternative in $\mathscr{B}$, that alternative is chosen. +Let $\mathscr{B}$ be the set of alternatives in $\mathscr{A}$ that are [_applicable_](#function-applications) +to expressions $(e_1 , \ldots , e_n)$ of types $(\mathit{shape}(e_1) , \ldots , \mathit{shape}(e_n))$. +If there is precisely one alternative in $\mathscr{B}$, that alternative is chosen. Otherwise, let $S_1 , \ldots , S_m$ be the vector of types obtained by typing each argument with an undefined expected type. For every -member $m$ in $\mathscr{B}$ one determines whether it is -applicable to expressions ($e_1 , \ldots , e_m$) of types $S_1 -, \ldots , S_m$. +member $m$ in $\mathscr{B}$ one determines whether it is applicable +to expressions ($e_1 , \ldots , e_m$) of types $S_1, \ldots , S_m$. + It is an error if none of the members in $\mathscr{B}$ is applicable. If there is one single applicable alternative, that alternative is chosen. Otherwise, let $\mathscr{CC}$ be the set of applicable alternatives which don't employ any default argument -in the application to $e_1 , \ldots , e_m$. It is again an error if $\mathscr{CC}$ is empty. +in the application to $e_1 , \ldots , e_m$. + +It is again an error if $\mathscr{CC}$ is empty. Otherwise, one chooses the _most specific_ alternative among the alternatives in $\mathscr{CC}$, according to the following definition of being "as specific as", and "more specific than": @@ -1469,21 +1452,17 @@ question: given so the method is not more specific than the value. --> -- A parameterized method $m$ of type `($p_1:T_1, \ldots , p_n:T_n$)$U$` is _as specific as_ some other - member $m'$ of type $S$ if $m'$ is applicable to arguments - `($p_1 , \ldots , p_n\,$)` of - types $T_1 , \ldots , T_n$. -- A polymorphic method of type - `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is - as specific as some other member of type $S$ if $T$ is as - specific as $S$ under the assumption that for - $i = 1 , \ldots , n$ each $a_i$ is an abstract type name +- A parameterized method $m$ of type `($p_1:T_1, \ldots , p_n:T_n$)$U$` is + _as specific as_ some other member $m'$ of type $S$ if $m'$ is [applicable](#function-applications) + to arguments `($p_1 , \ldots , p_n$)` of types $T_1 , \ldots , T_n$. +- A polymorphic method of type `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is + as specific as some other member of type $S$ if $T$ is as specific as $S$ + under the assumption that for $i = 1 , \ldots , n$ each $a_i$ is an abstract type name bounded from below by $L_i$ and from above by $U_i$. -- A member of any other type is always as specific as a parameterized method - or a polymorphic method. -- Given two members of types $T$ and $U$ which are - neither parameterized nor polymorphic method types, the member of type $T$ is as specific as - the member of type $U$ if the existential dual of $T$ conforms to the existential dual of $U$. +- A member of any other type is always as specific as a parameterized method or a polymorphic method. +- Given two members of types $T$ and $U$ which are neither parameterized nor polymorphic method types, + the member of type $T$ is as specific as the member of type $U$ if + the existential dual of $T$ conforms to the existential dual of $U$. Here, the existential dual of a polymorphic type `[$a_1$ >: $L_1$ <: $U_1 , \ldots , a_n$ >: $L_n$ <: $U_n$]$T$` is `$T$ forSome { type $a_1$ >: $L_1$ <: $U_1$ $, \ldots ,$ type $a_n$ >: $L_n$ <: $U_n$}`. @@ -1493,8 +1472,7 @@ The _relative weight_ of an alternative $A$ over an alternative $B$ is a number from 0 to 2, defined as the sum of - 1 if $A$ is as specific as $B$, 0 otherwise, and -- 1 if $A$ is defined in a class or object which is derived - from the class or object defining $B$, 0 otherwise. +- 1 if $A$ is defined in a class or object which is derived from the class or object defining $B$, 0 otherwise. A class or object $C$ is _derived_ from a class or object $D$ if one of the following holds: @@ -1517,15 +1495,13 @@ arguments in $\mathit{targs}$ are chosen. It is an error if no such alternative If there are several such alternatives, overloading resolution is applied again to the whole expression `$e$[$\mathit{targs}\,$]`. -Assume finally that $e$ does not appear as a function in either -an application or a type application. If an expected type is given, -let $\mathscr{B}$ be the set of those alternatives in $\mathscr{A}$ which are -[compatible](#implicit-conversions) to it. Otherwise, let $\mathscr{B}$ be the same -as $\mathscr{A}$. -We choose in this case the most specific alternative among all -alternatives in $\mathscr{B}$. It is an error if there is no -alternative in $\mathscr{B}$ which is more specific than all other -alternatives in $\mathscr{B}$. +Assume finally that $e$ does not appear as a function in either an application or a type application. +If an expected type is given, let $\mathscr{B}$ be the set of those alternatives +in $\mathscr{A}$ which are [compatible](03-types.html#compatibility) to it. +Otherwise, let $\mathscr{B}$ be the same as $\mathscr{A}$. +In this last case we choose the most specific alternative among all alternatives in $\mathscr{B}$. +It is an error if there is no alternative in $\mathscr{B}$ which is +more specific than all other alternatives in $\mathscr{B}$. ###### Example Consider the following definitions: @@ -1552,9 +1528,8 @@ no most specific applicable signature exists. ### Local Type Inference Local type inference infers type arguments to be passed to expressions -of polymorphic type. Say $e$ is of type [$a_1$ >: $L_1$ <: $U_1 -, \ldots , a_n$ >: $L_n$ <: $U_n$]$T$ and no explicit type parameters -are given. +of polymorphic type. Say $e$ is of type [$a_1$ >: $L_1$ <: $U_1, \ldots , a_n$ >: $L_n$ <: $U_n$]$T$ +and no explicit type parameters are given. Local type inference converts this expression to a type application `$e$[$T_1 , \ldots , T_n$]`. The choice of the diff --git a/spec/08-pattern-matching.md b/spec/08-pattern-matching.md index 7e48947639..3b481eea86 100644 --- a/spec/08-pattern-matching.md +++ b/spec/08-pattern-matching.md @@ -654,7 +654,8 @@ or `scala.PartialFunction[$S_1$, $R$]`, where the argument type(s) $S_1 , \ldots , S_k$ must be fully determined, but the result type $R$ may be undetermined. -If the expected type is `scala.Function$k$[$S_1 , \ldots , S_k$, $R$]`, +If the expected type is [SAM-convertible](06-expressions.html#sam-conversion) +to `scala.Function$k$[$S_1 , \ldots , S_k$, $R$]`, the expression is taken to be equivalent to the anonymous function: ```scala diff --git a/spec/_layouts/default.yml b/spec/_layouts/default.yml index 69791d26ad..7e205f8835 100644 --- a/spec/_layouts/default.yml +++ b/spec/_layouts/default.yml @@ -15,7 +15,7 @@ } }); </script> - <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.3-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> + <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> <script src="//code.jquery.com/jquery-2.1.3.min.js"></script> <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.2/styles/default.min.css"> <!-- need to use include to see value of page.chapter variable --> diff --git a/src/compiler/scala/reflect/macros/contexts/Typers.scala b/src/compiler/scala/reflect/macros/contexts/Typers.scala index 28c1e3ddb3..baf066c7d9 100644 --- a/src/compiler/scala/reflect/macros/contexts/Typers.scala +++ b/src/compiler/scala/reflect/macros/contexts/Typers.scala @@ -18,22 +18,25 @@ trait Typers { * @see [[scala.tools.reflect.ToolBox.typeCheck]] */ def typecheck(tree: Tree, mode: TypecheckMode = TERMmode, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree = { - macroLogVerbose("typechecking %s with expected type %s, implicit views = %s, macros = %s".format(tree, pt, !withImplicitViewsDisabled, !withMacrosDisabled)) - val context = callsiteTyper.context - val withImplicitFlag = if (!withImplicitViewsDisabled) (context.withImplicitsEnabled[Tree] _) else (context.withImplicitsDisabled[Tree] _) - val withMacroFlag = if (!withMacrosDisabled) (context.withMacrosEnabled[Tree] _) else (context.withMacrosDisabled[Tree] _) - def withContext(tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) - def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) universe.wrappingIntoTerm(tree)(op) else op(tree) - def typecheckInternal(tree: Tree) = callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(tree), mode, pt), reportAmbiguousErrors = false) - withWrapping(tree)(wrappedTree => withContext(typecheckInternal(wrappedTree) match { - case universe.analyzer.SilentResultValue(result) => - macroLogVerbose(result) - result - case error @ universe.analyzer.SilentTypeError(_) => - macroLogVerbose(error.err.errMsg) - if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) - universe.EmptyTree - })) + macroLogVerbose(s"typechecking $tree with expected type $pt, implicit views = ${!withImplicitViewsDisabled}, macros = ${!withMacrosDisabled}") + import callsiteTyper.context + def doTypecheck(wrapped: Tree): Tree = + context.withImplicits(enabled = !withImplicitViewsDisabled) { + context.withMacros(enabled = !withMacrosDisabled) { + callsiteTyper.silent(_.typed(universe.duplicateAndKeepPositions(wrapped), mode, pt), reportAmbiguousErrors = false) match { + case universe.analyzer.SilentResultValue(result) => + macroLogVerbose(result) + result + case error@universe.analyzer.SilentTypeError(_) => + macroLogVerbose(error.err.errMsg) + if (!silent) throw new TypecheckException(error.err.errPos, error.err.errMsg) + universe.EmptyTree + } + } + } + + if (mode == TERMmode) universe.wrappingIntoTerm(tree)(doTypecheck) + else doTypecheck(tree) } def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = { diff --git a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala index d007df75e3..b2948f8161 100644 --- a/src/compiler/scala/reflect/reify/codegen/GenTypes.scala +++ b/src/compiler/scala/reflect/reify/codegen/GenTypes.scala @@ -106,14 +106,10 @@ trait GenTypes { private def spliceAsManifest(tpe: Type): Tree = { def isSynthetic(manifest: Tree) = manifest exists (sub => sub.symbol != null && (sub.symbol == FullManifestModule || sub.symbol.owner == FullManifestModule)) def searchForManifest(typer: analyzer.Typer): Tree = - analyzer.inferImplicit( - EmptyTree, + analyzer.inferImplicitByTypeSilent( appliedType(FullManifestClass.toTypeConstructor, List(tpe)), - reportAmbiguous = false, - isView = false, - context = typer.context, - saveAmbiguousDivergent = false, - pos = defaultErrorPosition) match { + typer.context, + defaultErrorPosition) match { case success if !success.tree.isEmpty && !isSynthetic(success.tree) => val manifestInScope = success.tree // todo. write a test for this diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 7edac76b91..0786ceb7c2 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -261,43 +261,77 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { mkNew(Nil, noSelfType, stats1, NoPosition, NoPosition) } - /** - * Create a method based on a Function - * - * Used both to under `-Ydelambdafy:method` create a lifted function and - * under `-Ydelambdafy:inline` to create the apply method on the anonymous - * class. - * - * It creates a method definition with value params cloned from the - * original lambda. Then it calls a supplied function to create - * the body and types the result. Finally - * everything is wrapped up in a DefDef - * - * @param owner The owner for the new method - * @param name name for the new method - * @param additionalFlags flags to be put on the method in addition to FINAL - */ - def mkMethodFromFunction(localTyper: analyzer.Typer) - (fun: Function, owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags) = { - val funParams = fun.vparams map (_.symbol) - val formals :+ restpe = fun.tpe.typeArgs + // Construct a method to implement `fun`'s single abstract method (`apply`, when `fun.tpe` is a built-in function type) + def mkMethodFromFunction(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = { + // TODO: treat FunctionN like any other SAM -- drop `&& !isFunctionType(fun.tpe)` + val sam = if (!isFunctionType(fun.tpe)) samOf(fun.tpe) else NoSymbol + if (!sam.exists) mkMethodForFunctionBody(localTyper)(owner, fun, nme.apply)() + else { + val samMethType = fun.tpe memberInfo sam + mkMethodForFunctionBody(localTyper)(owner, fun, sam.name.toTermName)(methParamProtos = samMethType.params, resTp = samMethType.resultType) + } + } + + // used to create the lifted method that holds a function's body + def mkLiftedFunctionBodyMethod(localTyper: analyzer.Typer)(owner: Symbol, fun: Function) = + mkMethodForFunctionBody(localTyper)(owner, fun, nme.ANON_FUN_NAME)(additionalFlags = ARTIFACT) + + + /** + * Lift a Function's body to a method. For use during Uncurry, where Function nodes have type FunctionN[T1, ..., Tn, R] + * + * It creates a method definition with value params derived from the original lambda + * or `methParamProtos` (used to create the correct override for sam methods). + * + * Replace the `fun.vparams` symbols by the newly created method params, + * changes owner of `fun.body` from `fun.symbol` to resulting method's symbol. + * + * @param owner The owner for the new method + * @param fun the function to take the body from + * @param name name for the new method + * @param additionalFlags flags to be put on the method in addition to FINAL + */ + private def mkMethodForFunctionBody(localTyper: analyzer.Typer) + (owner: Symbol, fun: Function, name: TermName) + (methParamProtos: List[Symbol] = fun.vparams.map(_.symbol), + resTp: Type = functionResultType(fun.tpe), + additionalFlags: FlagSet = NoFlags): DefDef = { val methSym = owner.newMethod(name, fun.pos, FINAL | additionalFlags) + // for sams, methParamProtos is the parameter symbols for the sam's method, so that we generate the correct override (based on parmeter types) + val methParamSyms = methParamProtos.map { param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName) } + methSym setInfo MethodType(methParamSyms, resTp) - val paramSyms = map2(formals, fun.vparams) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } + // we must rewire reference to the function's param symbols -- and not methParamProtos -- to methParamSyms + val useMethodParams = new TreeSymSubstituter(fun.vparams.map(_.symbol), methParamSyms) + // we're now owned by the method that holds the body, and not the function + val moveToMethod = new ChangeOwnerTraverser(fun.symbol, methSym) - methSym setInfo MethodType(paramSyms, restpe.deconst) + newDefDef(methSym, moveToMethod(useMethodParams(fun.body)))(tpt = TypeTree(resTp)) + } + + // TODO: the rewrite to AbstractFunction is superfluous once we compile FunctionN to a SAM type (aka functional interface) + def functionClassType(fun: Function): Type = + if (isFunctionType(fun.tpe)) abstractFunctionType(fun.vparams.map(_.symbol.tpe), fun.body.tpe.deconst) + else fun.tpe - fun.body.substituteSymbols(funParams, paramSyms) - fun.body changeOwner (fun.symbol -> methSym) + def expandFunction(localTyper: analyzer.Typer)(fun: Function, inConstructorFlag: Long): Tree = { + val parents = addSerializable(functionClassType(fun)) + val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - val methDef = DefDef(methSym, fun.body) + // The original owner is used in the backend for the EnclosingMethod attribute. If fun is + // nested in a value-class method, its owner was already changed to the extension method. + // Saving the original owner allows getting the source structure from the class symbol. + defineOriginalOwner(anonClass, fun.symbol.originalOwner) + anonClass setInfo ClassInfoType(parents, newScope, anonClass) - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - methDef.tpt setType localTyper.packedType(fun.body, methSym).deconst - methDef + val samDef = mkMethodFromFunction(localTyper)(anonClass, fun) + anonClass.info.decls enter samDef.symbol + + localTyper.typedPos(fun.pos) { + Block( + ClassDef(anonClass, NoMods, ListOfNil, List(samDef), fun.pos), + Typed(New(anonClass.tpe), TypeTree(fun.tpe))) + } } } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index d4715471f6..9c0174d89b 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1713,9 +1713,7 @@ self => } simpleExprRest(app, canApply = true) case USCORE => - atPos(t.pos.start, in.skipToken()) { - Typed(stripParens(t), Function(Nil, EmptyTree)) - } + atPos(t.pos.start, in.skipToken()) { makeMethodValue(stripParens(t)) } case _ => t } diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 473a40f42a..1e9a1762eb 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -35,6 +35,9 @@ abstract class TreeBuilder { def repeatedApplication(tpe: Tree): Tree = AppliedTypeTree(rootScalaDot(tpnme.REPEATED_PARAM_CLASS_NAME), List(tpe)) + // represents `expr _`, as specified in Method Values of spec/06-expressions.md + def makeMethodValue(expr: Tree): Tree = Typed(expr, Function(Nil, EmptyTree)) + def makeImportSelector(name: Name, nameOffset: Int): ImportSelector = ImportSelector(name, nameOffset, name, nameOffset) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index b4d645d4bb..a4d08cb123 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -469,13 +469,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case NullTag => emit(asm.Opcodes.ACONST_NULL) case ClazzTag => - val toPush: BType = { - typeToBType(const.typeValue) match { - case kind: PrimitiveBType => boxedClassOfPrimitive(kind) - case kind => kind - } - } - mnode.visitLdcInsn(toPush.toASMType) + val tp = typeToBType(const.typeValue) + // classOf[Int] is transformed to Integer.TYPE by CleanUp + assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp") + mnode.visitLdcInsn(tp.toASMType) case EnumTag => val sym = const.symbolValue @@ -659,7 +656,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case Apply(fun, args) if app.hasAttachment[delambdafy.LambdaMetaFactoryCapable] => val attachment = app.attachments.get[delambdafy.LambdaMetaFactoryCapable].get genLoadArguments(args, paramTKs(app)) - genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface) + genInvokeDynamicLambda(attachment.target, attachment.arity, attachment.functionalInterface, attachment.sam) generatedType = methodBTypeFromSymbol(fun.symbol).returnType case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) => @@ -1360,7 +1357,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType def genLoadTry(tree: Try): BType - def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol) { + def genInvokeDynamicLambda(lambdaTarget: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) { val isStaticMethod = lambdaTarget.hasFlag(Flags.STATIC) def asmType(sym: Symbol) = classBTypeFromSymbol(sym).toASMType @@ -1375,7 +1372,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val invokedType = asm.Type.getMethodDescriptor(asmType(functionalInterface), (receiver ::: capturedParams).map(sym => typeToBType(sym.info).toASMType): _*) val constrainedType = new MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType - val sam = functionalInterface.info.decls.find(_.isDeferred).getOrElse(functionalInterface.info.member(nme.apply)) val samName = sam.name.toString val samMethodType = methodBTypeFromSymbol(sam).toASMType diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 324fc10eae..a2ccce9d21 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -61,6 +61,8 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { assert(classSym.isClass, s"not a class: $classSym") val r = exitingPickler(classSym.isAnonymousClass) || !classSym.originalOwner.isClass if (r) { + // lambda lift renames symbols and may accidentally introduce `$lambda` into a class name, making `isDelambdafyFunction` true. + // we prevent this, see `nonAnon` in LambdaLift. // phase travel necessary: after flatten, the name includes the name of outer classes. // if some outer name contains $lambda, a non-lambda class is considered lambda. assert(exitingPickler(!classSym.isDelambdafyFunction), classSym.name) @@ -260,7 +262,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { else { // Phase travel necessary. For example, nullary methods (getter of an abstract val) get an // empty parameter list in later phases and would therefore be picked as SAM. - val samSym = exitingPickler(definitions.findSam(classSym.tpe)) + val samSym = exitingPickler(definitions.samOf(classSym.tpe)) if (samSym == NoSymbol) None else Some(samSym.javaSimpleName.toString + methodSymToDescriptor(samSym)) } @@ -595,17 +597,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym classBTypeFromSymbol(classSym).internalName } - - /** - * The jvm descriptor of a type. - */ - final def descriptor(t: Type): String = typeToBType(t).descriptor - - /** - * The jvm descriptor for a symbol. - */ - final def descriptor(sym: Symbol): String = classBTypeFromSymbol(sym).descriptor - } // end of trait BCInnerClassGen trait BCAnnotGen extends BCInnerClassGen { @@ -614,6 +605,35 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) + /** + * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the + * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding + * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure). + * + * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend. + * Java annoations on the other hand are additionally emitted to the classfile in Java's format. + * + * This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples: + * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0 + * Here, annotationInfo.atp is an AnnotatedType. + * - @SomeAnnotation[T] val x = 0 + * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot + * actually happen because Java annotations cannot be generic. + * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0 + * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the + * non-erased existential type. + */ + def erasedType(tp: Type): Type = enteringErasure { + // make sure we don't erase value class references to the type that the value class boxes + // this is basically the same logic as in erasure's preTransform, case Literal(classTag). + tp.dealiasWiden match { + case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr) + case tpe => erasure.erasure(tpe.typeSymbol)(tpe) + } + } + + def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor + /** Whether an annotation should be emitted as a Java annotation * .initialize: if 'annot' is read from pickle, atp might be uninitialized */ @@ -650,7 +670,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { ca(idx) = b.asInstanceOf[Char] idx += 1 } - ca } @@ -713,9 +732,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case StringTag => assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, typeToBType(const.typeValue).toASMType) + case ClazzTag => + av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType) case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class. val evalue = const.symbolValue.name.toString // value the actual enumeration value. av.visitEnum(name, edesc, evalue) } @@ -740,7 +760,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case NestedAnnotArg(annInfo) => val AnnotationInfo(typ, args, assocs) = annInfo assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class + val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) emitAssocs(nestedVisitor, assocs) } @@ -765,7 +785,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -777,7 +797,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -789,7 +809,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -804,7 +824,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } @@ -988,7 +1008,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mirrorMethod.visitCode() - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor) var index = 0 for(jparamType <- paramJavaTypes) { @@ -1049,7 +1069,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def getExceptions(excs: List[AnnotationInfo]): List[String] = { for (ThrownException(tp) <- excs.distinct) yield { - val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) + val erased = erasedType(tp) internalName(erased.typeSymbol) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 85563be428..759b0a615a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -177,7 +177,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { /** * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + * signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef. */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && isCompilingArray, sym) @@ -190,39 +190,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info) - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for - * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. - * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. - */ - case a @ AnnotatedType(_, t) => - debuglog(s"typeKind of annotated type $a") - typeToBType(t) - - /* ExistentialType should (probably) be eliminated by erasure. We know they get here for - * classOf constants: - * class C[T] - * class T { final val k = classOf[C[_]] } - */ - case e @ ExistentialType(_, t) => - debuglog(s"typeKind of existential type $e") - typeToBType(t) - /* The cases below should probably never occur. They are kept for now to avoid introducing * new compiler crashes, but we added a warning. The compiler / library bootstrap and the * test suite don't produce any warning. */ case tp => - currentUnit.warning(tp.typeSymbol.pos, + warning(tp.typeSymbol.pos, s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + "If possible, please file a bug on issues.scala-lang.org.") tp match { - case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test - case ThisType(sym) => classBTypeFromSymbol(sym) - case SingleType(_, sym) => primitiveOrClassToBType(sym) - case ConstantType(_) => typeToBType(t.underlying) - case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case ThisType(sym) => classBTypeFromSymbol(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => typeToBType(t.underlying) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + case AnnotatedType(_, t) => typeToBType(t) + case ExistentialType(_, t) => typeToBType(t) } } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala index 696a164c56..ab9fd94a93 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala @@ -219,14 +219,12 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { // enumeration of specialized classes is temporary, while we still use the java-defined JFunctionN. // once we switch to ordinary FunctionN, we can use specializedSubclasses just like for tuples. - private def functionClasses(base: String): Set[Symbol] = { - def primitives = Iterator("B", "S", "I", "J", "C", "F", "D", "Z", "V") + private def specializedJFunctionSymbols(base: String): Seq[Symbol] = { + def primitives = Seq("B", "S", "I", "J", "C", "F", "D", "Z", "V") def ijfd = Iterator("I", "J", "F", "D") def ijfdzv = Iterator("I", "J", "F", "D", "Z", "V") def ijd = Iterator("I", "J", "D") - val classNames = Set.empty[String] ++ { - (0 to 22).map(base + _) - } ++ { + val classNames = { primitives.map(base + "0$mc" + _ + "$sp") // Function0 } ++ { // return type specializations appear first in the name string (alphabetical sorting) @@ -237,7 +235,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) { classNames map getRequiredClass } - lazy val srJFunctionRefs: Set[InternalName] = functionClasses("scala.runtime.java8.JFunction").map(classBTypeFromSymbol(_).internalName) + lazy val functionRefs: Set[InternalName] = (FunctionClass.seq ++ specializedJFunctionSymbols("scala.runtime.java8.JFunction")).map(classBTypeFromSymbol(_).internalName).toSet lazy val typeOfArrayOp: Map[Int, BType] = { import scalaPrimitives._ @@ -343,7 +341,7 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def srRefConstructors : Map[InternalName, MethodNameAndType] def tupleClassConstructors : Map[InternalName, MethodNameAndType] - def srJFunctionRefs: Set[InternalName] + def functionRefs: Set[InternalName] def lambdaMetaFactoryBootstrapHandle : asm.Handle def lambdaDeserializeBootstrapHandle : asm.Handle @@ -410,7 +408,7 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: def srRefConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.srRefConstructors def tupleClassConstructors : Map[InternalName, MethodNameAndType] = _coreBTypes.tupleClassConstructors - def srJFunctionRefs: Set[InternalName] = _coreBTypes.srJFunctionRefs + def functionRefs: Set[InternalName] = _coreBTypes.functionRefs def srSymbolLiteral : ClassBType = _coreBTypes.srSymbolLiteral def srStructuralCallSite : ClassBType = _coreBTypes.srStructuralCallSite diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala index bd7d5d2608..a6b9faa933 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala @@ -125,7 +125,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { private val anonfunAdaptedName = """.*\$anonfun\$\d+\$adapted""".r def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = { - isrJFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && + isBuiltinFunctionType(Type.getReturnType(closureInit.lambdaMetaFactoryCall.indy.desc).getInternalName) && anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches } @@ -250,7 +250,7 @@ class BackendUtils[BT <: BTypes](val btypes: BT) { } } - def isrJFunctionType(internalName: InternalName): Boolean = srJFunctionRefs(internalName) + def isBuiltinFunctionType(internalName: InternalName): Boolean = functionRefs(internalName) /** * Visit the class node and collect all referenced nested classes. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala index f1eaebd27c..d28565b9bc 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CopyProp.scala @@ -296,11 +296,11 @@ class CopyProp[BT <: BTypes](val btypes: BT) { /** * Eliminate the closure value produced by `indy`. If the SAM type is known to construct - * without side-effects (e.g. scala/runtime/java8/JFunctionN), the `indy` and its inputs + * without side-effects (e.g. scala/FunctionN), the `indy` and its inputs * are eliminated, otherwise a POP is inserted. */ def handleClosureInst(indy: InvokeDynamicInsnNode): Unit = { - if (isrJFunctionType(Type.getReturnType(indy.desc).getInternalName)) { + if (isBuiltinFunctionType(Type.getReturnType(indy.desc).getInternalName)) { toRemove += indy callGraph.removeClosureInstantiation(indy, method) handleInputs(indy, Type.getArgumentTypes(indy.desc).length) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index 982a6da41a..e924dc856a 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -109,7 +109,7 @@ trait ScalaSettings extends AbsScalaSettings val Xmigration = ScalaVersionSetting ("-Xmigration", "version", "Warn about constructs whose behavior may have changed since version.", initial = NoScalaVersion, default = Some(AnyScalaVersion)) val nouescape = BooleanSetting ("-Xno-uescape", "Disable handling of \\u unicode escapes.") val Xnojline = BooleanSetting ("-Xnojline", "Do not use JLine for editing.") - val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode (asm backend only.)") + val Xverify = BooleanSetting ("-Xverify", "Verify generic signatures in generated bytecode.") val plugin = MultiStringSetting ("-Xplugin", "paths", "Load a plugin from each classpath.") val disable = MultiStringSetting ("-Xplugin-disable", "plugin", "Disable plugins by name.") val showPlugins = BooleanSetting ("-Xplugin-list", "Print a synopsis of loaded plugins.") diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 1e479d3f63..636fb08b89 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -501,8 +501,6 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { !sym.isSetter ) - private def possiblySpecialized(s: Symbol) = specializeTypes.specializedTypeVars(s).nonEmpty - /* * whether `sym` denotes a param-accessor (ie a field) that fulfills all of: * (a) has stationary value, ie the same value provided via the corresponding ctor-arg; and @@ -511,7 +509,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { * (b.2) the constructor in the specialized (sub-)class. * (c) isn't part of a DelayedInit subclass. */ - private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !possiblySpecialized(sym) + private def canBeSupplanted(sym: Symbol) = !isDelayedInitSubclass && isStationaryParamRef(sym) && !specializeTypes.possiblySpecialized(sym) override def transform(tree: Tree): Tree = tree match { case Apply(Select(This(_), _), List()) => @@ -531,7 +529,7 @@ abstract class Constructors extends Statics with Transform with ast.TreeDSL { gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos case Select(_, _) if guardSpecializedFieldInit => // reasoning behind this guard in the docu of `usesSpecializedField` - if (possiblySpecialized(tree.symbol)) { + if (specializeTypes.possiblySpecialized(tree.symbol)) { usesSpecializedField = true } super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala index 67e3f67f2f..76c84bd428 100644 --- a/src/compiler/scala/tools/nsc/transform/Delambdafy.scala +++ b/src/compiler/scala/tools/nsc/transform/Delambdafy.scala @@ -7,30 +7,19 @@ import scala.collection._ import scala.collection.mutable.LinkedHashMap /** - * This transformer is responsible for preparing lambdas for runtime, by either translating to anonymous classes - * or to a tree that will be converted to invokedynamic by the JVM 1.8+ backend. - * - * The main assumption it makes is that a lambda {args => body} has been turned into - * {args => liftedBody()} where lifted body is a top level method that implements the body of the lambda. - * Currently Uncurry is responsible for that transformation. - * - * From a lambda, Delambdafy will create: - * - * Under GenASM - * - * 1) a new top level class that - a) has fields and a constructor taking the captured environment (including possibly the "this" - * reference) - * b) an apply method that calls the target method - * c) if needed a bridge method for the apply method - * 2) an instantiation of the newly created class which replaces the lambda - * - * Under GenBCode: - * - * 1) An application of the captured arguments to a fictional symbol representing the lambda factory. - * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. - * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. - */ + * This transformer is responsible for preparing Function nodes for runtime, + * by translating to a tree that will be converted to an invokedynamic by the backend. + * + * The main assumption it makes is that a Function {args => body} has been turned into + * {args => liftedBody()} where lifted body is a top level method that implements the body of the function. + * Currently Uncurry is responsible for that transformation. + * + * From this shape of Function, Delambdafy will create: + * + * An application of the captured arguments to a fictional symbol representing the lambda factory. + * This will be translated by the backed into an invokedynamic using a bootstrap method in JDK8's `LambdaMetaFactory`. + * The captured arguments include `this` if `liftedBody` is unable to be made STATIC. + */ abstract class Delambdafy extends Transform with TypingTransformers with ast.TreeDSL with TypeAdaptingTransformer { import global._ import definitions._ @@ -40,6 +29,19 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre /** the following two members override abstract members in Transform */ val phaseName: String = "delambdafy" + final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol, sam: Symbol) + + /** + * Get the symbol of the target lifted lambda body method from a function. I.e. if + * the function is {args => anonfun(args)} then this method returns anonfun's symbol + */ + private def targetMethod(fun: Function): Symbol = fun match { + case Function(_, Apply(target, _)) => target.symbol + case _ => + // any other shape of Function is unexpected at this point + abort(s"could not understand function with tree $fun") + } + override def newPhase(prev: scala.tools.nsc.Phase): StdPhase = { if (settings.Ydelambdafy.value == "method") new Phase(prev) else new SkipPhase(prev) @@ -52,432 +54,217 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre protected def newTransformer(unit: CompilationUnit): Transformer = new DelambdafyTransformer(unit) - class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with TypeAdapter { - private val lambdaClassDefs = new mutable.LinkedHashMap[Symbol, List[Tree]] withDefaultValue Nil + class DelambdafyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { + // we need to know which methods refer to the 'this' reference so that we can determine which lambdas need access to it + // TODO: this looks expensive, so I made it a lazy val. Can we make it more pay-as-you-go / optimize for common shapes? + private[this] lazy val methodReferencesThis: Set[Symbol] = + (new ThisReferringMethodsTraverser).methodReferencesThisIn(unit.body) + + private def mkLambdaMetaFactoryCall(fun: Function, target: Symbol, functionalInterface: Symbol, samUserDefined: Symbol, isSpecialized: Boolean): Tree = { + val pos = fun.pos + val allCapturedArgRefs = { + // find which variables are free in the lambda because those are captures that need to be + // passed into the constructor of the anonymous function class + val captureArgs = FreeVarTraverser.freeVarsOf(fun).iterator.map(capture => + gen.mkAttributedRef(capture) setPos pos + ).toList + + if (target hasFlag STATIC) captureArgs // no `this` reference needed + else (gen.mkAttributedThis(fun.symbol.enclClass) setPos pos) :: captureArgs + } + // Create a symbol representing a fictional lambda factory method that accepts the captured + // arguments and returns the SAM type. + val msym = { + val meth = currentOwner.newMethod(nme.ANON_FUN_NAME, pos, ARTIFACT) + val capturedParams = meth.newSyntheticValueParams(allCapturedArgRefs.map(_.tpe)) + meth.setInfo(MethodType(capturedParams, fun.tpe)) + } - val typer = localTyper + // We then apply this symbol to the captures. + val apply = localTyper.typedPos(pos)(Apply(Ident(msym), allCapturedArgRefs)) - // we need to know which methods refer to the 'this' reference so that we can determine - // which lambdas need access to it - val thisReferringMethods: Set[Symbol] = { - val thisReferringMethodsTraverser = new ThisReferringMethodsTraverser() - thisReferringMethodsTraverser traverse unit.body - val methodReferringMap = thisReferringMethodsTraverser.liftedMethodReferences - val referrers = thisReferringMethodsTraverser.thisReferringMethods - // recursively find methods that refer to 'this' directly or indirectly via references to other methods - // for each method found add it to the referrers set - def refersToThis(symbol: Symbol): Boolean = { - if (referrers contains symbol) true - else if (methodReferringMap(symbol) exists refersToThis) { - // add it early to memoize - debuglog(s"$symbol indirectly refers to 'this'") - referrers += symbol - true - } else false + // TODO: this is a bit gross + val sam = samUserDefined orElse { + if (isSpecialized) functionalInterface.info.decls.find(_.isDeferred).get + else functionalInterface.info.member(nme.apply) } - methodReferringMap.keys foreach refersToThis - referrers + + // no need for adaptation when the implemented sam is of a specialized built-in function type + val lambdaTarget = if (isSpecialized) target else createBoxingBridgeMethodIfNeeded(fun, target, functionalInterface, sam) + + // The backend needs to know the target of the lambda and the functional interface in order + // to emit the invokedynamic instruction. We pass this information as tree attachment. + // + // see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + // instantiatedMethodType is derived from lambdaTarget's signature + // samMethodType is derived from samOf(functionalInterface)'s signature + apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, fun.vparams.length, functionalInterface, sam)) + + apply } - // the result of the transformFunction method. - sealed abstract class TransformedFunction - // A class definition for the lambda, an expression instantiating the lambda class - case class DelambdafyAnonClass(lambdaClassDef: ClassDef, newExpr: Tree) extends TransformedFunction - case class InvokeDynamicLambda(tree: Apply) extends TransformedFunction private val boxingBridgeMethods = mutable.ArrayBuffer[Tree]() - // here's the main entry point of the transform - override def transform(tree: Tree): Tree = tree match { - // the main thing we care about is lambdas - case fun @ Function(_, _) => - transformFunction(fun) match { - case DelambdafyAnonClass(lambdaClassDef, newExpr) => - // a lambda becomes a new class, an instantiation expression - val pkg = lambdaClassDef.symbol.owner - - // we'll add the lambda class to the package later - lambdaClassDefs(pkg) = lambdaClassDef :: lambdaClassDefs(pkg) - - super.transform(newExpr) - case InvokeDynamicLambda(apply) => - // ... or an invokedynamic call - super.transform(apply) - } - case Template(_, _, _) => - try { - // during this call boxingBridgeMethods will be populated from the Function case - val Template(parents, self, body) = super.transform(tree) - Template(parents, self, body ++ boxingBridgeMethods) - } finally boxingBridgeMethods.clear() - case _ => super.transform(tree) + private def reboxValueClass(tp: Type) = tp match { + case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) + case _ => tp } - // this entry point is aimed at the statements in the compilation unit. - // after working on the entire compilation until we'll have a set of - // new class definitions to add to the top level - override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { - // Need to remove from the lambdaClassDefs map: there may be multiple PackageDef for the same - // package when defining a package object. We only add the lambda class to one. See SI-9097. - super.transformStats(stats, exprOwner) ++ lambdaClassDefs.remove(exprOwner).getOrElse(Nil) + // exclude primitives and value classes, which need special boxing + private def isReferenceType(tp: Type) = !tp.isInstanceOf[ErasedValueType] && { + val sym = tp.typeSymbol + !(isPrimitiveValueClass(sym) || sym.isDerivedValueClass) } - private def optionSymbol(sym: Symbol): Option[Symbol] = if (sym.exists) Some(sym) else None + // determine which lambda target to use with java's LMF -- create a new one if scala-specific boxing is required + def createBoxingBridgeMethodIfNeeded(fun: Function, target: Symbol, functionalInterface: Symbol, sam: Symbol): Symbol = { + val oldClass = fun.symbol.enclClass + val pos = fun.pos + + // At erasure, there won't be any captured arguments (they are added in constructors) + val functionParamTypes = exitingErasure(target.info.paramTypes) + val functionResultType = exitingErasure(target.info.resultType) + + val samParamTypes = exitingErasure(sam.info.paramTypes) + val samResultType = exitingErasure(sam.info.resultType) + + /** How to satisfy the linking invariants of https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html + * + * Given samMethodType: (U1..Un)Ru and function type T1,..., Tn => Rt (the target method created by uncurry) + * + * Do we need a bridge, or can we use the original lambda target for implMethod: (<captured args> A1..An)Ra + * (We can ignore capture here.) + * + * If, for i=1..N: + * Ai =:= Ui || (Ai <:< Ui <:< AnyRef) + * Ru =:= void || (Ra =:= Ru || (Ra <:< AnyRef, Ru <:< AnyRef)) + * + * We can use the target method as-is -- if not, we create a bridging one that uses the types closest + * to the target method that still meet the above requirements. + */ + val resTpOk = ( + samResultType =:= UnitTpe + || functionResultType =:= samResultType + || (isReferenceType(samResultType) && isReferenceType(functionResultType))) // yes, this is what the spec says -- no further correspondance required + if (resTpOk && (samParamTypes corresponds functionParamTypes){ (samParamTp, funParamTp) => + funParamTp =:= samParamTp || (isReferenceType(funParamTp) && isReferenceType(samParamTp) && funParamTp <:< samParamTp) }) target + else { + // We have to construct a new lambda target that bridges to the one created by uncurry. + // The bridge must satisfy the above invariants, while also minimizing adaptation on our end. + // LMF will insert runtime casts according to the spec at the above link. + + // we use the more precise type between samParamTp and funParamTp to minimize boxing in the bridge method + // we are constructing a method whose signature matches the sam's signature (because the original target did not) + // whenever a type in the sam's signature is (erases to) a primitive type, we must pick the sam's version, + // as we don't implement the logic regarding widening that's performed by LMF -- we require =:= for primitives + // + // We use the sam's type for the check whether we're dealin with a reference type, as it could be a generic type, + // which means the function's parameter -- even if it expects a value class -- will need to be + // boxed on the generic call to the sam method. - // turns a lambda into a new class def, a New expression instantiating that class - private def transformFunction(originalFunction: Function): TransformedFunction = { - val formals = originalFunction.vparams.map(_.tpe) - val restpe = originalFunction.body.tpe.deconst - val oldClass = originalFunction.symbol.enclClass + val bridgeParamTypes = map2(samParamTypes, functionParamTypes){ (samParamTp, funParamTp) => + if (isReferenceType(samParamTp) && funParamTp <:< samParamTp) funParamTp + else samParamTp + } - // find which variables are free in the lambda because those are captures that need to be - // passed into the constructor of the anonymous function class - val captures = FreeVarTraverser.freeVarsOf(originalFunction) + val bridgeResultType = + if (resTpOk && isReferenceType(samResultType) && functionResultType <:< samResultType) functionResultType + else samResultType - val target = targetMethod(originalFunction) - target.makeNotPrivate(target.owner) - if (!thisReferringMethods.contains(target)) - target setFlag STATIC + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) } + import typeAdapter.{adaptToType, unboxValueClass} - val isStatic = target.hasFlag(STATIC) + val targetParams = target.paramss.head + val numCaptures = targetParams.length - functionParamTypes.length + val (targetCapturedParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - def createBoxingBridgeMethod(functionParamTypes: List[Type], functionResultType: Type): Tree = { - // Note: we bail out of this method and return EmptyTree if we find there is no adaptation required. - // If we need to improve performance, we could check the types first before creating the - // method and parameter symbols. val methSym = oldClass.newMethod(target.name.append("$adapted").toTermName, target.pos, target.flags | FINAL | ARTIFACT) - var neededAdaptation = false - def boxedType(tpe: Type): Type = { - if (isPrimitiveValueClass(tpe.typeSymbol)) {neededAdaptation = true; ObjectTpe} - else if (enteringErasure(tpe.typeSymbol.isDerivedValueClass)) {neededAdaptation = true; ObjectTpe} - else tpe - } - val targetParams: List[Symbol] = target.paramss.head - val numCaptures = targetParams.length - functionParamTypes.length - val (targetCaptureParams, targetFunctionParams) = targetParams.splitAt(numCaptures) - val bridgeParams: List[Symbol] = - targetCaptureParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) ::: - map2(targetFunctionParams, functionParamTypes)((param, tp) => methSym.newSyntheticValueParam(boxedType(tp), param.name.toTermName)) - - val bridgeResultType: Type = { - if (target.info.resultType == UnitTpe && functionResultType != UnitTpe) { - neededAdaptation = true - ObjectTpe - } else - boxedType(functionResultType) - } - val methodType = MethodType(bridgeParams, bridgeResultType) - methSym setInfo methodType - if (!neededAdaptation) - EmptyTree - else { - val bridgeParamTrees = bridgeParams.map(ValDef(_)) - - oldClass.info.decls enter methSym - - val body = localTyper.typedPos(originalFunction.pos) { - val newTarget = Select(gen.mkAttributedThis(oldClass), target) - val args: List[Tree] = mapWithIndex(bridgeParams) { (param, i) => - if (i < numCaptures) { - gen.mkAttributedRef(param) - } else { - val functionParam = functionParamTypes(i - numCaptures) - val targetParam = targetParams(i) - if (enteringErasure(functionParam.typeSymbol.isDerivedValueClass)) { - val casted = cast(gen.mkAttributedRef(param), functionParam) - val unboxed = unbox(casted, ErasedValueType(functionParam.typeSymbol, targetParam.tpe)).modifyType(postErasure.elimErasedValueType) - unboxed - } else adaptToType(gen.mkAttributedRef(param), targetParam.tpe) - } + val bridgeCapturedParams = targetCapturedParams.map(param => methSym.newSyntheticValueParam(param.tpe, param.name.toTermName)) + val bridgeFunctionParams = + map2(targetFunctionParams, bridgeParamTypes)((param, tp) => methSym.newSyntheticValueParam(tp, param.name.toTermName)) + + val bridgeParams = bridgeCapturedParams ::: bridgeFunctionParams + + methSym setInfo MethodType(bridgeParams, bridgeResultType) + oldClass.info.decls enter methSym + + val forwarderCall = localTyper.typedPos(pos) { + val capturedArgRefs = bridgeCapturedParams map gen.mkAttributedRef + val functionArgRefs = + map3(bridgeFunctionParams, functionParamTypes, targetParams.drop(numCaptures)) { (bridgeParam, functionParamTp, targetParam) => + val bridgeParamRef = gen.mkAttributedRef(bridgeParam) + val targetParamTp = targetParam.tpe + + // TODO: can we simplify this to something like `adaptToType(adaptToType(bridgeParamRef, functionParamTp), targetParamTp)`? + val unboxed = + functionParamTp match { + case ErasedValueType(clazz, underlying) => + // when the original function expected an argument of value class type, + // the original target will expect the unboxed underlying value, + // whereas the bridge will receive the boxed value (since the sam's argument type did not match and we had to adapt) + localTyper.typed(unboxValueClass(bridgeParamRef, clazz, underlying), targetParamTp) + case _ => bridgeParamRef + } + + adaptToType(unboxed, targetParamTp) } - gen.mkMethodCall(newTarget, args) - } - val body1 = if (enteringErasure(functionResultType.typeSymbol.isDerivedValueClass)) - adaptToType(box(body.setType(ErasedValueType(functionResultType.typeSymbol, body.tpe)), "boxing lambda target"), bridgeResultType) - else adaptToType(body, bridgeResultType) - val methDef0 = DefDef(methSym, List(bridgeParamTrees), body1) - postErasure.newTransformer(unit).transform(methDef0).asInstanceOf[DefDef] - } - } - /** - * Creates the apply method for the anonymous subclass of FunctionN - */ - def createApplyMethod(newClass: Symbol, fun: Function, thisProxy: Symbol): DefDef = { - val methSym = newClass.newMethod(nme.apply, fun.pos, FINAL | SYNTHETIC) - val params = fun.vparams map (_.duplicate) - - val paramSyms = map2(formals, params) { - (tp, vparam) => methSym.newSyntheticValueParam(tp, vparam.name) - } - params zip paramSyms foreach { case (valdef, sym) => valdef.symbol = sym } - params foreach (_.symbol.owner = methSym) - - val methodType = MethodType(paramSyms, restpe) - methSym setInfo methodType - - newClass.info.decls enter methSym - val Apply(_, oldParams) = fun.body - val qual = if (thisProxy.exists) - Select(gen.mkAttributedThis(newClass), thisProxy) - else - gen.mkAttributedThis(oldClass) // sort of a lie, EmptyTree.<static method> would be more honest, but the backend chokes on that. - - val body = localTyper typed Apply(Select(qual, target), oldParams) - body.substituteSymbols(fun.vparams map (_.symbol), params map (_.symbol)) - body changeOwner (fun.symbol -> methSym) - - val methDef = DefDef(methSym, List(params), body) + gen.mkMethodCall(Select(gen.mkAttributedThis(oldClass), target), capturedArgRefs ::: functionArgRefs) + } - // Have to repack the type to avoid mismatches when existentials - // appear in the result - see SI-4869. - // TODO probably don't need packedType - methDef.tpt setType localTyper.packedType(body, methSym) - methDef - } + val bridge = postErasure.newTransformer(unit).transform(DefDef(methSym, List(bridgeParams.map(ValDef(_))), + adaptToType(forwarderCall setType functionResultType, bridgeResultType))).asInstanceOf[DefDef] - /** - * Creates the constructor on the newly created class. It will handle - * initialization of members that represent the captured environment - */ - def createConstructor(newClass: Symbol, members: List[ValDef]): DefDef = { - val constrSym = newClass.newConstructor(originalFunction.pos, SYNTHETIC) - - val (paramSymbols, params, assigns) = (members map {member => - val paramSymbol = newClass.newVariable(member.symbol.name.toTermName, newClass.pos, 0) - paramSymbol.setInfo(member.symbol.info) - val paramVal = ValDef(paramSymbol) - val paramIdent = Ident(paramSymbol) - val assign = Assign(Select(gen.mkAttributedThis(newClass), member.symbol), paramIdent) - - (paramSymbol, paramVal, assign) - }).unzip3 - - val constrType = MethodType(paramSymbols, newClass.thisType) - constrSym setInfoAndEnter constrType - - val body = - Block( - List( - atPos(newClass.pos)(Apply(gen.mkSuperInitCall, Nil)) - ) ++ assigns, - Literal(Constant(())): Tree - ) setPos newClass.pos - - (localTyper typed DefDef(constrSym, List(params), body) setPos newClass.pos).asInstanceOf[DefDef] + boxingBridgeMethods += bridge + bridge.symbol } + } - val pkg = oldClass.owner - - // Parent for anonymous class def - val abstractFunctionErasedType = AbstractFunctionClass(formals.length).tpe - - // anonymous subclass of FunctionN with an apply method - def makeAnonymousClass: ClassDef = { - val parents = addSerializable(abstractFunctionErasedType) - val funOwner = originalFunction.symbol.owner - - // TODO harmonize the naming of delambdafy anon-fun classes with those spun up by Uncurry - // - make `anonClass.isAnonymousClass` true. - // - use `newAnonymousClassSymbol` or push the required variations into a similar factory method - // - reinstate the assertion in `Erasure.resolveAnonymousBridgeClash` - val suffix = nme.DELAMBDAFY_LAMBDA_CLASS_NAME + "$" + ( - if (funOwner.isPrimaryConstructor) "" - else "$" + funOwner.name + "$" - ) - val oldClassPart = oldClass.name.decode - // make sure the class name doesn't contain $anon, otherwise isAnonymousClass/Function may be true - val name = unit.freshTypeName(s"$oldClassPart$suffix".replace("$anon", "$nestedInAnon")) - - val lambdaClass = pkg newClassSymbol(name, originalFunction.pos, FINAL | SYNTHETIC) addAnnotation SerialVersionUIDAnnotation - lambdaClass.associatedFile = unit.source.file - // make sure currentRun.compiles(lambdaClass) is true (AddInterfaces does the same for trait impl classes) - currentRun.symSource(lambdaClass) = funOwner.sourceFile - lambdaClass setInfo ClassInfoType(parents, newScope, lambdaClass) - assert(!lambdaClass.isAnonymousClass && !lambdaClass.isAnonymousFunction, "anonymous class name: "+ lambdaClass.name) - assert(lambdaClass.isDelambdafyFunction, "not lambda class name: " + lambdaClass.name) - - val captureProxies2 = new LinkedHashMap[Symbol, TermSymbol] - captures foreach {capture => - val sym = lambdaClass.newVariable(unit.freshTermName(capture.name.toString + "$"), capture.pos, SYNTHETIC) - sym setInfo capture.info - captureProxies2 += ((capture, sym)) - } + private def transformFunction(originalFunction: Function): Tree = { + val target = targetMethod(originalFunction) + target.makeNotPrivate(target.owner) - // the Optional proxy that will hold a reference to the 'this' - // object used by the lambda, if any. NoSymbol if there is no this proxy - val thisProxy = { - if (isStatic) - NoSymbol - else { - val sym = lambdaClass.newVariable(nme.FAKE_LOCAL_THIS, originalFunction.pos, SYNTHETIC) - sym.setInfo(oldClass.tpe) - } - } + // must be done before calling createBoxingBridgeMethod and mkLambdaMetaFactoryCall + if (!(target hasFlag STATIC) && !methodReferencesThis(target)) target setFlag STATIC - val decapturify = new DeCapturifyTransformer(captureProxies2, unit, oldClass, lambdaClass, originalFunction.symbol.pos, thisProxy) + val funSym = originalFunction.tpe.typeSymbolDirect + // The functional interface that can be used to adapt the lambda target method `target` to the given function type. + val (functionalInterface, isSpecialized) = + if (!isFunctionSymbol(funSym)) (funSym, false) + else { + val specializedName = + specializeTypes.specializedFunctionName(funSym, + exitingErasure(target.info.paramTypes).map(reboxValueClass) :+ reboxValueClass(exitingErasure(target.info.resultType))).toTypeName - val decapturedFunction = decapturify.transform(originalFunction).asInstanceOf[Function] + val isSpecialized = specializedName != funSym.name + val functionalInterface = // TODO: this is no longer needed, right? we can just use the regular function classes + if (isSpecialized) currentRun.runDefinitions.Scala_Java8_CompatPackage.info.decl(specializedName.prepend("J")) + else FunctionClass(originalFunction.vparams.length) - val members = (optionSymbol(thisProxy).toList ++ (captureProxies2 map (_._2))) map {member => - lambdaClass.info.decls enter member - ValDef(member, gen.mkZero(member.tpe)) setPos decapturedFunction.pos + (functionalInterface, isSpecialized) } - // constructor - val constr = createConstructor(lambdaClass, members) - - // apply method with same arguments and return type as original lambda. - val applyMethodDef = createApplyMethod(lambdaClass, decapturedFunction, thisProxy) - - val bridgeMethod = createBridgeMethod(lambdaClass, originalFunction, applyMethodDef) - - def fulldef(sym: Symbol) = - if (sym == NoSymbol) sym.toString - else s"$sym: ${sym.tpe} in ${sym.owner}" - - bridgeMethod foreach (bm => - // TODO SI-6260 maybe just create the apply method with the signature (Object => Object) in all cases - // rather than the method+bridge pair. - if (bm.symbol.tpe =:= applyMethodDef.symbol.tpe) - erasure.resolveAnonymousBridgeClash(applyMethodDef.symbol, bm.symbol) - ) - - val body = members ++ List(constr, applyMethodDef) ++ bridgeMethod - - // TODO if member fields are private this complains that they're not accessible - localTyper.typedPos(decapturedFunction.pos)(ClassDef(lambdaClass, body)).asInstanceOf[ClassDef] - } - - val allCaptureArgs: List[Tree] = { - val thisArg = if (isStatic) Nil else (gen.mkAttributedThis(oldClass) setPos originalFunction.pos) :: Nil - val captureArgs = captures.iterator.map(capture => gen.mkAttributedRef(capture) setPos originalFunction.pos).toList - thisArg ::: captureArgs - } - - val arity = originalFunction.vparams.length - - // Reconstruct the type of the function entering erasure. - // We do this by taking the type after erasure, and re-boxing `ErasedValueType`. - // - // Unfortunately, the more obvious `enteringErasure(target.info)` doesn't work - // as we would like, value classes in parameter position show up as the unboxed types. - val (functionParamTypes, functionResultType) = exitingErasure { - def boxed(tp: Type) = tp match { - case ErasedValueType(valueClazz, _) => TypeRef(NoPrefix, valueClazz, Nil) - case _ => tp - } - // We don't need to deeply map `boxedValueClassType` over the infos as `ErasedValueType` - // will only appear directly as a parameter type in a method signature, as shown - // https://gist.github.com/retronym/ba81dbd462282c504ff8 - val info = target.info - val boxedParamTypes = info.paramTypes.takeRight(arity).map(boxed) - (boxedParamTypes, boxed(info.resultType)) - } - val functionType = definitions.functionType(functionParamTypes, functionResultType) - - val (functionalInterface, isSpecialized) = java8CompatFunctionalInterface(target, functionType) - if (functionalInterface.exists) { - // Create a symbol representing a fictional lambda factory method that accepts the captured - // arguments and returns a Function. - val msym = currentOwner.newMethod(nme.ANON_FUN_NAME, originalFunction.pos, ARTIFACT) - val argTypes: List[Type] = allCaptureArgs.map(_.tpe) - val params = msym.newSyntheticValueParams(argTypes) - msym.setInfo(MethodType(params, functionType)) - val arity = originalFunction.vparams.length - - val lambdaTarget = - if (isSpecialized) - target - else { - createBoxingBridgeMethod(functionParamTypes, functionResultType) match { - case EmptyTree => - target - case bridge => - boxingBridgeMethods += bridge - bridge.symbol - } - } - - // We then apply this symbol to the captures. - val apply = localTyper.typedPos(originalFunction.pos)(Apply(Ident(msym), allCaptureArgs)).asInstanceOf[Apply] - - // The backend needs to know the target of the lambda and the functional interface in order - // to emit the invokedynamic instruction. We pass this information as tree attachment. - apply.updateAttachment(LambdaMetaFactoryCapable(lambdaTarget, arity, functionalInterface)) - InvokeDynamicLambda(apply) - } else { - val anonymousClassDef = makeAnonymousClass - pkg.info.decls enter anonymousClassDef.symbol - val newStat = Typed(New(anonymousClassDef.symbol, allCaptureArgs: _*), TypeTree(abstractFunctionErasedType)) - val typedNewStat = localTyper.typedPos(originalFunction.pos)(newStat) - DelambdafyAnonClass(anonymousClassDef, typedNewStat) - } + val sam = originalFunction.attachments.get[SAMFunction].map(_.sam).getOrElse(NoSymbol) + mkLambdaMetaFactoryCall(originalFunction, target, functionalInterface, sam, isSpecialized) } - /** - * Creates a bridge method if needed. The bridge method forwards from apply(x1: Object, x2: Object...xn: Object): Object to - * apply(x1: T1, x2: T2...xn: Tn): T0 using type adaptation on each input and output. The only time a bridge isn't needed - * is when the original lambda is already erased to type Object, Object, Object... => Object - */ - def createBridgeMethod(newClass:Symbol, originalFunction: Function, applyMethod: DefDef): Option[DefDef] = { - val bridgeMethSym = newClass.newMethod(nme.apply, applyMethod.pos, FINAL | SYNTHETIC | BRIDGE) - val originalParams = applyMethod.vparamss(0) - val bridgeParams = originalParams map { originalParam => - val bridgeSym = bridgeMethSym.newSyntheticValueParam(ObjectTpe, originalParam.name) - ValDef(bridgeSym) - } - - val bridgeSyms = bridgeParams map (_.symbol) - - val methodType = MethodType(bridgeSyms, ObjectTpe) - bridgeMethSym setInfo methodType - - def adapt(tree: Tree, expectedTpe: Type): (Boolean, Tree) = { - if (tree.tpe =:= expectedTpe) (false, tree) - else (true, adaptToType(tree, expectedTpe)) - } - - def adaptAndPostErase(tree: Tree, pt: Type): (Boolean, Tree) = { - val (needsAdapt, adaptedTree) = adapt(tree, pt) - val trans = postErasure.newTransformer(unit) - val postErasedTree = trans.atOwner(currentOwner)(trans.transform(adaptedTree)) // SI-8017 eliminates ErasedValueTypes - (needsAdapt, postErasedTree) - } - - enteringPhase(currentRun.posterasurePhase) { - // e.g, in: - // class C(val a: Int) extends AnyVal; (x: Int) => new C(x) - // - // This type is: - // (x: Int)ErasedValueType(class C, Int) - val liftedBodyDefTpe: MethodType = { - val liftedBodySymbol = { - val Apply(method, _) = originalFunction.body - method.symbol - } - liftedBodySymbol.info.asInstanceOf[MethodType] - } - val (paramNeedsAdaptation, adaptedParams) = (bridgeSyms zip liftedBodyDefTpe.params map {case (bridgeSym, param) => adapt(Ident(bridgeSym) setType bridgeSym.tpe, param.tpe)}).unzip - // SI-8017 Before, this code used `applyMethod.symbol.info.resultType`. - // But that symbol doesn't have a type history that goes back before `delambdafy`, - // so we just see a plain `Int`, rather than `ErasedValueType(C, Int)`. - // This triggered primitive boxing, rather than value class boxing. - val resTp = liftedBodyDefTpe.finalResultType - val body = Apply(gen.mkAttributedSelect(gen.mkAttributedThis(newClass), applyMethod.symbol), adaptedParams) setType resTp - val (needsReturnAdaptation, adaptedBody) = adaptAndPostErase(body, ObjectTpe) - - val needsBridge = (paramNeedsAdaptation contains true) || needsReturnAdaptation - if (needsBridge) { - val methDef = DefDef(bridgeMethSym, List(bridgeParams), adaptedBody) - newClass.info.decls enter bridgeMethSym - Some((localTyper typed methDef).asInstanceOf[DefDef]) - } else None - } + // here's the main entry point of the transform + override def transform(tree: Tree): Tree = tree match { + // the main thing we care about is lambdas + case fun: Function => super.transform(transformFunction(fun)) + case Template(_, _, _) => + try { + // during this call boxingBridgeMethods will be populated from the Function case + val Template(parents, self, body) = super.transform(tree) + Template(parents, self, body ++ boxingBridgeMethods) + } finally boxingBridgeMethods.clear() + case _ => super.transform(tree) } } // DelambdafyTransformer + // A traverser that finds symbols used but not defined in the given Tree // TODO freeVarTraverser in LambdaLift does a very similar task. With some // analysis this could probably be unified with it @@ -510,40 +297,36 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre } } - // A transformer that converts specified captured symbols into other symbols - // TODO this transform could look more like ThisSubstituter and TreeSymSubstituter. It's not clear that it needs that level of sophistication since the types - // at this point are always very simple flattened/erased types, but it would probably be more robust if it tried to take more complicated types into account - class DeCapturifyTransformer(captureProxies: Map[Symbol, TermSymbol], unit: CompilationUnit, oldClass: Symbol, newClass:Symbol, pos: Position, thisProxy: Symbol) extends TypingTransformer(unit) { - override def transform(tree: Tree) = tree match { - case tree@This(encl) if tree.symbol == oldClass && thisProxy.exists => - gen mkAttributedSelect (gen mkAttributedThis newClass, thisProxy) - case Ident(name) if (captureProxies contains tree.symbol) => - gen mkAttributedSelect (gen mkAttributedThis newClass, captureProxies(tree.symbol)) - case _ => super.transform(tree) + // finds all methods that reference 'this' + class ThisReferringMethodsTraverser extends Traverser { + // the set of methods that refer to this + private val thisReferringMethods = mutable.Set[Symbol]() + + // the set of lifted lambda body methods that each method refers to + private val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + + def methodReferencesThisIn(tree: Tree) = { + traverse(tree) + liftedMethodReferences.keys foreach refersToThis + + thisReferringMethods } - } - /** - * Get the symbol of the target lifted lambda body method from a function. I.e. if - * the function is {args => anonfun(args)} then this method returns anonfun's symbol - */ - private def targetMethod(fun: Function): Symbol = fun match { - case Function(_, Apply(target, _)) => - target.symbol - case _ => - // any other shape of Function is unexpected at this point - abort(s"could not understand function with tree $fun") - } + // recursively find methods that refer to 'this' directly or indirectly via references to other methods + // for each method found add it to the referrers set + private def refersToThis(symbol: Symbol): Boolean = + (thisReferringMethods contains symbol) || + (liftedMethodReferences(symbol) exists refersToThis) && { + // add it early to memoize + debuglog(s"$symbol indirectly refers to 'this'") + thisReferringMethods += symbol + true + } - // finds all methods that reference 'this' - class ThisReferringMethodsTraverser() extends Traverser { private var currentMethod: Symbol = NoSymbol - // the set of methods that refer to this - val thisReferringMethods = mutable.Set[Symbol]() - // the set of lifted lambda body methods that each method refers to - val liftedMethodReferences = mutable.Map[Symbol, Set[Symbol]]().withDefault(_ => mutable.Set()) + override def traverse(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => + case DefDef(_, _, _, _, _, _) if tree.symbol.isDelambdafyTarget => // we don't expect defs within defs. At this phase trees should be very flat if (currentMethod.exists) devWarning("Found a def within a def at a phase where defs are expected to be flattened out.") currentMethod = tree.symbol @@ -559,27 +342,10 @@ abstract class Delambdafy extends Transform with TypingTransformers with ast.Tre debuglog(s"$currentMethod directly refers to 'this'") thisReferringMethods add currentMethod } + case _: ClassDef if !tree.symbol.isTopLevel => + case _: DefDef => case _ => super.traverse(tree) } } - - final case class LambdaMetaFactoryCapable(target: Symbol, arity: Int, functionalInterface: Symbol) - - // The functional interface that can be used to adapt the lambda target method `target` to the - // given function type. Returns `NoSymbol` if the compiler settings are unsuitable. - private def java8CompatFunctionalInterface(target: Symbol, functionType: Type): (Symbol, Boolean) = { - val sym = functionType.typeSymbol - val pack = currentRun.runDefinitions.Scala_Java8_CompatPackage - val name1 = specializeTypes.specializedFunctionName(sym, functionType.typeArgs) - val paramTps :+ restpe = functionType.typeArgs - val arity = paramTps.length - val isSpecialized = name1.toTypeName != sym.name - val functionalInterface = if (!isSpecialized) { - currentRun.runDefinitions.Scala_Java8_CompatPackage_JFunction(arity) - } else { - pack.info.decl(name1.toTypeName.prepend("J")) - } - (functionalInterface, isSpecialized) - } } diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 41f22e5669..ac794201a4 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -578,8 +578,9 @@ abstract class Erasure extends AddInterfaces } /** The modifier typer which retypes with erased types. */ - class Eraser(_context: Context) extends Typer(_context) with TypeAdapter { - val typer = this.asInstanceOf[analyzer.Typer] + class Eraser(_context: Context) extends Typer(_context) { + val typeAdapter = new TypeAdapter { def typedPos(pos: Position)(tree: Tree): Tree = Eraser.this.typedPos(pos)(tree) } + import typeAdapter._ override protected def stabilize(tree: Tree, pre: Type, mode: Mode, pt: Type): Tree = tree @@ -647,7 +648,7 @@ abstract class Erasure extends AddInterfaces var qual1 = typedQualifier(qual) if ((isPrimitiveValueType(qual1.tpe) && !isPrimitiveValueMember(tree.symbol)) || isErasedValueType(qual1.tpe)) - qual1 = box(qual1, "owner "+tree.symbol.owner) + qual1 = box(qual1) else if (!isPrimitiveValueType(qual1.tpe) && isPrimitiveValueMember(tree.symbol)) qual1 = unbox(qual1, tree.symbol.owner.tpe) @@ -656,10 +657,9 @@ abstract class Erasure extends AddInterfaces if (isPrimitiveValueMember(tree.symbol) && !isPrimitiveValueType(qual1.tpe)) { tree.symbol = NoSymbol selectFrom(qual1) - } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { + } else if (isMethodTypeWithEmptyParams(qual1.tpe)) { // see also adaptToType in TypeAdapter assert(qual1.symbol.isStable, qual1.symbol) - val applied = Apply(qual1, List()) setPos qual1.pos setType qual1.tpe.resultType - adaptMember(selectFrom(applied)) + adaptMember(selectFrom(applyMethodWithEmptyParams(qual1))) } else if (!(qual1.isInstanceOf[Super] || (qual1.tpe.typeSymbol isSubClass tree.symbol.owner))) { assert(tree.symbol.owner != ArrayClass) selectFrom(cast(qual1, tree.symbol.owner.tpe.resultType)) @@ -721,6 +721,12 @@ abstract class Erasure extends AddInterfaces if (branch == EmptyTree) branch else adaptToType(branch, tree1.tpe) tree1 match { + case fun: Function => + fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(samTp, _)) => fun setType specialScalaErasure(samTp) + case _ => fun + } + case If(cond, thenp, elsep) => treeCopy.If(tree1, cond, adaptBranch(thenp), adaptBranch(elsep)) case Match(selector, cases) => @@ -1112,7 +1118,7 @@ abstract class Erasure extends AddInterfaces case Literal(ct) if ct.tag == ClazzTag && ct.typeValue.typeSymbol != definitions.UnitClass => - val erased = ct.typeValue match { + val erased = ct.typeValue.dealiasWiden match { case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr) case tpe => specialScalaErasure(tpe) } @@ -1181,5 +1187,41 @@ abstract class Erasure extends AddInterfaces bridge.resetFlag(BRIDGE) } + /** Does this symbol compile to the underlying platform's notion of an interface, + * without requiring compiler magic before it can be instantiated? + * + * More specifically, we're interested in whether LambdaMetaFactory can instantiate this type, + * assuming it has a single abstract method. In other words, if we were to mix this + * trait into a class, it should not result in any compiler-generated members having to be + * implemented in ("mixed in to") this class (except for the SAM). + * + * Thus, the type must erase to a java interface, either by virtue of being defined as one, + * or by being a trait that: + * - is static (explicitouter or lambdalift may add disqualifying members) + * - extends only other traits that compile to pure interfaces (except for Any) + * - has no val/var members + * + * TODO: can we speed this up using the INTERFACE flag, or set it correctly by construction? + */ + final def compilesToPureInterface(tpSym: Symbol): Boolean = { + def ok(sym: Symbol) = + sym.isJavaInterface || + sym.isTrait && + // Unless sym.isStatic, even if the constructor is zero-argument now, it may acquire arguments in explicit outer or lambdalift. + // This is an impl restriction to simplify the decision of whether to expand the SAM during uncurry + // (when we don't yet know whether it will receive an outer pointer in explicit outer or whether lambda lift will add proxies for captures). + // When we delay sam expansion until after explicit outer & lambda lift, we could decide there whether + // to expand sam at compile time or use LMF, and this implementation restriction could be lifted. + sym.isStatic && + // HACK: this is to rule out traits with an effectful initializer. + // The constructor only exists if the trait's template has statements. + // Sadly, we can't be more precise without access to the tree that defines the SAM's owner. + !sym.primaryConstructor.exists && + (sym.isInterface || sym.info.decls.forall(mem => mem.isMethod || mem.isType)) // TODO OPT: && {sym setFlag INTERFACE; true}) + + // we still need to check our ancestors even if the INTERFACE flag is set, as it doesn't take inheritance into account + ok(tpSym) && tpSym.ancestors.forall(sym => (sym eq AnyClass) || (sym eq ObjectClass) || ok(sym)) + } + private class TypeRefAttachment(val tpe: TypeRef) } diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 7a5bd747c4..074acc1332 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -223,10 +223,6 @@ abstract class LambdaLift extends InfoTransform { debuglog("renaming in %s: %s => %s".format(sym.owner.fullLocationString, originalName, sym.name)) } - // make sure that the name doesn't make the symbol accidentally `isAnonymousClass` (et.al) by - // introducing `$anon` in its name. - def nonAnon(s: String) = nme.ensureNonAnon(s) - def newName(sym: Symbol): Name = { val originalName = sym.name def freshen(prefix: String): Name = @@ -235,7 +231,7 @@ abstract class LambdaLift extends InfoTransform { val join = nme.NAME_JOIN_STRING if (sym.isAnonymousFunction && sym.owner.isMethod) { - freshen(sym.name + join + nonAnon(sym.owner.name.toString) + join) + freshen(sym.name + join + nme.ensureNonAnon(sym.owner.name.toString) + join) } else { val name = freshen(sym.name + join) // SI-5652 If the lifted symbol is accessed from an inner class, it will be made public. (where?) @@ -243,7 +239,7 @@ abstract class LambdaLift extends InfoTransform { // package - subclass might have the same name), avoids a VerifyError in the case // that a sub-class happens to lifts out a method with the *same* name. if (originalName.isTermName && calledFromInner(sym)) - newTermNameCached(nonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) + newTermNameCached(nme.ensureNonAnon(sym.enclClass.fullName('$')) + nme.EXPAND_SEPARATOR_STRING + name) else name } diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 998f0b22cb..0050d08f1b 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -285,6 +285,19 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { for ((tvar, tpe) <- sym.info.typeParams.zip(args) if !tvar.isSpecialized || !isPrimitiveValueType(tpe)) yield tpe + /** Is `member` potentially affected by specialization? This is a gross overapproximation, + * but it should be okay for use outside of specialization. + */ + def possiblySpecialized(sym: Symbol) = specializedTypeVars(sym).nonEmpty + + /** Refines possiblySpecialized taking into account the instantiation of the specialized type variables at `site` */ + def isSpecializedIn(sym: Symbol, site: Type) = + specializedTypeVars(sym) exists { tvar => + val concretes = concreteTypes(tvar) + (concretes contains AnyRefClass) || (concretes contains site.memberType(tvar)) + } + + val specializedType = new TypeMap { override def apply(tp: Type): Type = tp match { case TypeRef(pre, sym, args) if args.nonEmpty => diff --git a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala index 1ed728247b..afafdedce7 100644 --- a/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala +++ b/src/compiler/scala/tools/nsc/transform/TypeAdaptingTransformer.scala @@ -1,89 +1,64 @@ package scala.tools.nsc package transform +import scala.annotation.tailrec import scala.tools.nsc.ast.TreeDSL /** * A trait usable by transforms that need to adapt trees of one type to another type */ -trait TypeAdaptingTransformer { - self: TreeDSL => - - val analyzer: typechecker.Analyzer { val global: self.global.type } - - trait TypeAdapter { - val typer: analyzer.Typer +trait TypeAdaptingTransformer { self: TreeDSL => + abstract class TypeAdapter { import global._ import definitions._ - import CODE._ - def isMethodTypeWithEmptyParams(tpe: Type) = tpe match { - case MethodType(Nil, _) => true - case _ => false - } + def typedPos(pos: Position)(tree: Tree): Tree private def isSafelyRemovableUnbox(fn: Tree, arg: Tree): Boolean = { currentRun.runDefinitions.isUnbox(fn.symbol) && { val cls = arg.tpe.typeSymbol - (cls == definitions.NullClass) || isBoxedValueClass(cls) + (cls == NullClass) || isBoxedValueClass(cls) } } - private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) - - private def isErasedValueType(tpe: Type) = tpe.isInstanceOf[ErasedValueType] + private def isPrimitiveValueType(tpe: Type) = isPrimitiveValueClass(tpe.typeSymbol) + final def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) + final def isMethodTypeWithEmptyParams(tpe: Type) = tpe.isInstanceOf[MethodType] && tpe.params.isEmpty + final def applyMethodWithEmptyParams(qual: Tree) = Apply(qual, List()) setPos qual.pos setType qual.tpe.resultType - private def isDifferentErasedValueType(tpe: Type, other: Type) = - isErasedValueType(tpe) && (tpe ne other) - - def isPrimitiveValueMember(sym: Symbol) = isPrimitiveValueClass(sym.owner) - - @inline def box(tree: Tree, target: => String): Tree = { - val result = box1(tree) - if (tree.tpe =:= UnitTpe) () - else log(s"boxing ${tree.summaryString}: ${tree.tpe} into $target: ${result.tpe}") - result - } + import CODE._ /** Box `tree` of unboxed type */ - private def box1(tree: Tree): Tree = tree match { + final def box(tree: Tree): Tree = tree match { case LabelDef(_, _, _) => - val ldef = deriveLabelDef(tree)(box1) + val ldef = deriveLabelDef(tree)(box) ldef setType ldef.rhs.tpe case _ => val tree1 = tree.tpe match { - case ErasedValueType(clazz, _) => - New(clazz, cast(tree, underlyingOfValueClass(clazz))) - case _ => - tree.tpe.typeSymbol match { - case UnitClass => - if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) - else BLOCK(tree, REF(BoxedUnit_UNIT)) - case NothingClass => tree // a non-terminating expression doesn't need boxing - case x => - assert(x != ArrayClass) - tree match { - /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x - * may lead to throwing an exception. - * - * This is important for specialization: calls to the super constructor should not box/unbox specialized - * fields (see TupleX). (ID) - */ - case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => - log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") - arg - case _ => - (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe - } - } + case ErasedValueType(clazz, _) => New(clazz, cast(tree, underlyingOfValueClass(clazz))) + case _ => tree.tpe.typeSymbol match { + case UnitClass => + if (treeInfo isExprSafeToInline tree) REF(BoxedUnit_UNIT) + else BLOCK(tree, REF(BoxedUnit_UNIT)) + case NothingClass => tree // a non-terminating expression doesn't need boxing + case x => + assert(x != ArrayClass) + tree match { + /* Can't always remove a Box(Unbox(x)) combination because the process of boxing x + * may lead to throwing an exception. + * + * This is important for specialization: calls to the super constructor should not box/unbox specialized + * fields (see TupleX). (ID) + */ + case Apply(boxFun, List(arg)) if isSafelyRemovableUnbox(tree, arg) => + log(s"boxing an unbox: ${tree.symbol} -> ${arg.tpe}") + arg + case _ => + (REF(currentRun.runDefinitions.boxMethod(x)) APPLY tree) setPos (tree.pos) setType ObjectTpe + } + } } - typer.typedPos(tree.pos)(tree1) - } - - def unbox(tree: Tree, pt: Type): Tree = { - val result = unbox1(tree, pt) - log(s"unboxing ${tree.shortClass}: ${tree.tpe} as a ${result.tpe}") - result + typedPos(tree.pos)(tree1) } /** Unbox `tree` of boxed type to expected type `pt`. @@ -92,27 +67,13 @@ trait TypeAdaptingTransformer { * @param pt the expected type. * @return the unboxed tree */ - private def unbox1(tree: Tree, pt: Type): Tree = tree match { -/* - case Boxed(unboxed) => - println("unbox shorten: "+tree) // this never seems to kick in during build and test; therefore disabled. - adaptToType(unboxed, pt) - */ + final def unbox(tree: Tree, pt: Type): Tree = tree match { case LabelDef(_, _, _) => val ldef = deriveLabelDef(tree)(unbox(_, pt)) ldef setType ldef.rhs.tpe case _ => val tree1 = pt match { - case ErasedValueType(clazz, underlying) => - val tree0 = - if (tree.tpe.typeSymbol == NullClass && - isPrimitiveValueClass(underlying.typeSymbol)) { - // convert `null` directly to underlying type, as going - // via the unboxed type would yield a NPE (see SI-5866) - unbox1(tree, underlying) - } else - Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) - cast(tree0, pt) + case ErasedValueType(clazz, underlying) => cast(unboxValueClass(tree, clazz, underlying), pt) case _ => pt.typeSymbol match { case UnitClass => @@ -124,21 +85,28 @@ trait TypeAdaptingTransformer { Apply(currentRun.runDefinitions.unboxMethod(pt.typeSymbol), tree) } } - typer.typedPos(tree.pos)(tree1) + typedPos(tree.pos)(tree1) } + final def unboxValueClass(tree: Tree, clazz: Symbol, underlying: Type): Tree = + if (tree.tpe.typeSymbol == NullClass && isPrimitiveValueClass(underlying.typeSymbol)) { + // convert `null` directly to underlying type, as going via the unboxed type would yield a NPE (see SI-5866) + unbox(tree, underlying) + } else + Apply(Select(adaptToType(tree, clazz.tpe), clazz.derivedValueClassUnbox), List()) + /** Generate a synthetic cast operation from tree.tpe to pt. - * @pre pt eq pt.normalize + * + * @pre pt eq pt.normalize */ - def cast(tree: Tree, pt: Type): Tree = { - if ((tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { - def word = ( + final def cast(tree: Tree, pt: Type): Tree = { + if (settings.debug && (tree.tpe ne null) && !(tree.tpe =:= ObjectTpe)) { + def word = if (tree.tpe <:< pt) "upcast" else if (pt <:< tree.tpe) "downcast" else if (pt weak_<:< tree.tpe) "coerce" else if (tree.tpe weak_<:< pt) "widen" else "cast" - ) log(s"erasure ${word}s from ${tree.tpe} to $pt") } if (pt =:= UnitTpe) { @@ -159,27 +127,23 @@ trait TypeAdaptingTransformer { * @param pt the expected type * @return the adapted tree */ - def adaptToType(tree: Tree, pt: Type): Tree = { - if (settings.debug && pt != WildcardType) - log("adapting " + tree + ":" + tree.tpe + " : " + tree.tpe.parents + " to " + pt)//debug - if (tree.tpe <:< pt) - tree - else if (isDifferentErasedValueType(tree.tpe, pt)) - adaptToType(box(tree, pt.toString), pt) - else if (isDifferentErasedValueType(pt, tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else if (isPrimitiveValueType(tree.tpe) && !isPrimitiveValueType(pt)) { - adaptToType(box(tree, pt.toString), pt) - } else if (isMethodTypeWithEmptyParams(tree.tpe)) { - // [H] this assert fails when trying to typecheck tree !(SomeClass.this.bitmap) for single lazy val - //assert(tree.symbol.isStable, "adapt "+tree+":"+tree.tpe+" to "+pt) - adaptToType(Apply(tree, List()) setPos tree.pos setType tree.tpe.resultType, pt) -// } else if (pt <:< tree.tpe) -// cast(tree, pt) - } else if (isPrimitiveValueType(pt) && !isPrimitiveValueType(tree.tpe)) - adaptToType(unbox(tree, pt), pt) - else - cast(tree, pt) + @tailrec final def adaptToType(tree: Tree, pt: Type): Tree = { + val tpe = tree.tpe + + if ((tpe eq pt) || tpe <:< pt) tree + else if (tpe.isInstanceOf[ErasedValueType]) adaptToType(box(tree), pt) // what if pt is an erased value type? + else if (pt.isInstanceOf[ErasedValueType]) adaptToType(unbox(tree, pt), pt) + // See corresponding case in `Eraser`'s `adaptMember` + // [H] this does not hold here, however: `assert(tree.symbol.isStable)` (when typechecking !(SomeClass.this.bitmap) for single lazy val) + else if (isMethodTypeWithEmptyParams(tpe)) adaptToType(applyMethodWithEmptyParams(tree), pt) + else { + val gotPrimitiveVC = isPrimitiveValueType(tpe) + val expectedPrimitiveVC = isPrimitiveValueType(pt) + + if (gotPrimitiveVC && !expectedPrimitiveVC) adaptToType(box(tree), pt) + else if (!gotPrimitiveVC && expectedPrimitiveVC) adaptToType(unbox(tree, pt), pt) + else cast(tree, pt) + } } } } diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 40a988ee94..628090dba5 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -7,6 +7,7 @@ package scala package tools.nsc package transform +import scala.annotation.tailrec import scala.language.postfixOps import symtab.Flags._ @@ -65,19 +66,30 @@ abstract class UnCurry extends InfoTransform // uncurry and uncurryType expand type aliases class UnCurryTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - private val inlineFunctionExpansion = settings.Ydelambdafy.value == "inline" + private val forceExpandFunction = settings.Ydelambdafy.value == "inline" private var needTryLift = false private var inConstructorFlag = 0L private val byNameArgs = mutable.HashSet[Tree]() private val noApply = mutable.HashSet[Tree]() private val newMembers = mutable.Map[Symbol, mutable.Buffer[Tree]]() - private lazy val forceSpecializationInfoTransformOfFunctionN: Unit = { - if (currentRun.specializePhase != NoPhase) { // be robust in case of -Ystop-after:uncurry - exitingSpecialize { - FunctionClass.seq.foreach(cls => cls.info) - } - } + // Expand `Function`s in constructors to class instance creation (SI-6666, SI-8363) + // We use Java's LambdaMetaFactory (LMF), which requires an interface for the sam's owner + private def mustExpandFunction(fun: Function) = { + // (TODO: Can't use isInterface, yet, as it hasn't been updated for the new trait encoding) + val canUseLambdaMetaFactory = (fun.attachments.get[SAMFunction] match { + case Some(SAMFunction(userDefinedSamTp, sam)) => + // LambdaMetaFactory cannot mix in trait members for us, or instantiate classes -- only pure interfaces need apply + erasure.compilesToPureInterface(erasure.javaErasure(userDefinedSamTp).typeSymbol) && + // impl restriction -- we currently use the boxed apply, so not really useful to allow specialized sam types (https://github.com/scala/scala/pull/4971#issuecomment-198119167) + // specialization and LMF are at odds, since LMF implements the single abstract method, + // but that's the one that specialization leaves generic, whereas we need to implement the specialized one to avoid boxing + !specializeTypes.isSpecializedIn(sam, userDefinedSamTp) + + case _ => true // our built-in FunctionN's are suitable for LambdaMetaFactory by construction + }) + + !canUseLambdaMetaFactory } /** Add a new synthetic member for `currentOwner` */ @@ -88,25 +100,17 @@ abstract class UnCurry extends InfoTransform @inline private def useNewMembers[T](owner: Symbol)(f: List[Tree] => T): T = f(newMembers.remove(owner).getOrElse(Nil).toList) - private def newFunction0(body: Tree): Tree = { - val result = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] - log("Change owner from %s to %s in %s".format(currentOwner, result.symbol, result.body)) - result.body changeOwner (currentOwner -> result.symbol) - transformFunction(result) - } - // 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 // motivation can be seen in continuations-neg/t3718. - override def transform(tree: Tree): Tree = ( + override def transform(tree: Tree): Tree = try postTransform(mainTransform(tree)) catch { case ex: TypeError => reporter.error(ex.pos, ex.msg) debugStack(ex) EmptyTree } - ) /* Is tree a reference `x` to a call by name parameter that needs to be converted to * x.apply()? Note that this is not the case if `x` is used as an argument to another @@ -115,7 +119,7 @@ abstract class UnCurry extends InfoTransform def isByNameRef(tree: Tree) = ( tree.isTerm && (tree.symbol ne null) - && (isByName(tree.symbol)) + && isByName(tree.symbol) && !byNameArgs(tree) ) @@ -192,16 +196,6 @@ abstract class UnCurry extends InfoTransform // ------ Transforming anonymous functions and by-name-arguments ---------------- - /** Undo eta expansion for parameterless and nullary methods */ - def deEta(fun: Function): Tree = fun match { - case Function(List(), expr) if isByNameRef(expr) => - noApply += expr - expr - case _ => - fun - } - - /** Transform a function node (x_1,...,x_n) => body of type FunctionN[T_1, .., T_N, R] to * * class $anon() extends AbstractFunctionN[T_1, .., T_N, R] with Serializable { @@ -210,63 +204,30 @@ abstract class UnCurry extends InfoTransform * new $anon() * */ - def transformFunction(fun: Function): Tree = { - fun.tpe match { - // can happen when analyzer plugins assign refined types to functions, e.g. - // (() => Int) { def apply(): Int @typeConstraint } - case RefinedType(List(funTp), decls) => - debuglog(s"eliminate refinement from function type ${fun.tpe}") - fun.setType(funTp) - case _ => - () - } - - deEta(fun) match { - // nullary or parameterless - case fun1 if fun1 ne fun => fun1 - case _ => - def typedFunPos(t: Tree) = localTyper.typedPos(fun.pos)(t) - val funParams = fun.vparams map (_.symbol) - def mkMethod(owner: Symbol, name: TermName, additionalFlags: FlagSet = NoFlags): DefDef = - gen.mkMethodFromFunction(localTyper)(fun, owner, name, additionalFlags) - - def isSpecialized = { - forceSpecializationInfoTransformOfFunctionN - val specialized = specializeTypes.specializedType(fun.tpe) - !(specialized =:= fun.tpe) - } - - def canUseDelamdafyMethod = inConstructorFlag == 0 // Avoiding synthesizing code prone to SI-6666, SI-8363 by using old-style lambda translation - if (inlineFunctionExpansion || !canUseDelamdafyMethod) { - val parents = addSerializable(abstractFunctionForFunctionType(fun.tpe)) - val anonClass = fun.symbol.owner newAnonymousFunctionClass(fun.pos, inConstructorFlag) addAnnotation SerialVersionUIDAnnotation - // The original owner is used in the backend for the EnclosingMethod attribute. If fun is - // nested in a value-class method, its owner was already changed to the extension method. - // Saving the original owner allows getting the source structure from the class symbol. - defineOriginalOwner(anonClass, fun.symbol.originalOwner) - anonClass setInfo ClassInfoType(parents, newScope, anonClass) - - val applyMethodDef = mkMethod(anonClass, nme.apply) - anonClass.info.decls enter applyMethodDef.symbol - - typedFunPos { - Block( - ClassDef(anonClass, NoMods, ListOfNil, List(applyMethodDef), fun.pos), - Typed(New(anonClass.tpe), TypeTree(fun.tpe))) - } - } else { - // method definition with the same arguments, return type, and body as the original lambda - val liftedMethod = mkMethod(fun.symbol.owner, nme.ANON_FUN_NAME, additionalFlags = ARTIFACT) - - // new function whose body is just a call to the lifted method - val newFun = deriveFunction(fun)(_ => typedFunPos( - gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), funParams :: Nil) - )) - typedFunPos(Block(liftedMethod, super.transform(newFun))) - } - } - } + def transformFunction(fun: Function): Tree = + // Undo eta expansion for parameterless and nullary methods, EXCEPT if `fun` targets a SAM. + // Normally, we can unwrap `() => cbn` to `cbn` where `cbn` refers to a CBN argument (typically `cbn` is an Ident), + // because we know `cbn` will already be a `Function0` thunk. When we're targeting a SAM, + // the types don't align and we must preserve the function wrapper. + if (fun.vparams.isEmpty && isByNameRef(fun.body) && fun.attachments.get[SAMFunction].isEmpty) { noApply += fun.body ; fun.body } + else if (forceExpandFunction || inConstructorFlag != 0) { + // Expand the function body into an anonymous class + gen.expandFunction(localTyper)(fun, inConstructorFlag) + } else { + // method definition with the same arguments, return type, and body as the original lambda + val liftedMethod = gen.mkLiftedFunctionBodyMethod(localTyper)(fun.symbol.owner, fun) + + // new function whose body is just a call to the lifted method + val newFun = deriveFunction(fun)(_ => localTyper.typedPos(fun.pos)( + gen.mkForwarder(gen.mkAttributedRef(liftedMethod.symbol), (fun.vparams map (_.symbol)) :: Nil) + )) + val typedNewFun = localTyper.typedPos(fun.pos)(Block(liftedMethod, super.transform(newFun))) + if (mustExpandFunction(fun)) { + val Block(stats, expr : Function) = typedNewFun + treeCopy.Block(typedNewFun, stats, gen.expandFunction(localTyper)(expr, inConstructorFlag)) + } else typedNewFun + } def transformArgs(pos: Position, fun: Symbol, args: List[Tree], formals: List[Type]) = { val isJava = fun.isJavaDefined @@ -344,25 +305,22 @@ abstract class UnCurry extends InfoTransform val args1 = if (isVarArgTypes(formals)) transformVarargs(formals.last.typeArgs.head) else args map2(formals, args1) { (formal, arg) => - if (!isByNameParamType(formal)) - arg - else if (isByNameRef(arg)) { + if (!isByNameParamType(formal)) arg + else if (isByNameRef(arg)) { // thunk does not need to be forced because it's a reference to a by-name arg passed to a by-name param byNameArgs += arg arg setType functionType(Nil, arg.tpe) - } - else { + } else { log(s"Argument '$arg' at line ${arg.pos.line} is $formal from ${fun.fullName}") - def canUseDirectly(recv: Tree) = ( - recv.tpe.typeSymbol.isSubClass(FunctionClass(0)) - && treeInfo.isExprSafeToInline(recv) - ) + def canUseDirectly(qual: Tree) = qual.tpe.typeSymbol.isSubClass(FunctionClass(0)) && treeInfo.isExprSafeToInline(qual) arg match { // don't add a thunk for by-name argument if argument already is an application of // a Function0. We can then remove the application and use the existing Function0. - case Apply(Select(recv, nme.apply), Nil) if canUseDirectly(recv) => - recv - case _ => - newFunction0(arg) + case Apply(Select(qual, nme.apply), Nil) if canUseDirectly(qual) => qual + case body => + val thunkFun = localTyper.typedPos(body.pos)(Function(Nil, body)).asInstanceOf[Function] + log(s"Change owner from $currentOwner to ${thunkFun.symbol} in ${thunkFun.body}") + thunkFun.body.changeOwner((currentOwner, thunkFun.symbol)) + transformFunction(thunkFun) } } } @@ -433,9 +391,10 @@ abstract class UnCurry extends InfoTransform val sym = tree.symbol // true if the target is a lambda body that's been lifted into a method - def isLiftedLambdaBody(target: Tree) = target.symbol.isLocalToBlock && target.symbol.isArtifact && target.symbol.name.containsName(nme.ANON_FUN_NAME) + def isLiftedLambdaMethod(funSym: Symbol) = + funSym.isArtifact && funSym.name.containsName(nme.ANON_FUN_NAME) && funSym.isLocalToBlock - val result = ( + val result = if ((sym ne null) && sym.elisionLevel.exists(_ < settings.elidebelow.value)) replaceElidableTree(tree) else translateSynchronized(tree) match { @@ -488,7 +447,7 @@ abstract class UnCurry extends InfoTransform case Assign(lhs, _) if lhs.symbol.owner != currentMethod || lhs.symbol.hasFlag(LAZY | ACCESSOR) => withNeedLift(needLift = true) { super.transform(tree) } - case ret @ Return(_) if (isNonLocalReturn(ret)) => + case ret @ Return(_) if isNonLocalReturn(ret) => withNeedLift(needLift = true) { super.transform(ret) } case Try(_, Nil, _) => @@ -507,7 +466,7 @@ abstract class UnCurry extends InfoTransform treeCopy.CaseDef(tree, pat1, transform(guard), transform(body)) // if a lambda is already the right shape we don't need to transform it again - case fun @ Function(_, Apply(target, _)) if (!inlineFunctionExpansion) && isLiftedLambdaBody(target) => + case fun @ Function(_, Apply(target, _)) if !forceExpandFunction && isLiftedLambdaMethod(target.symbol) => super.transform(fun) case fun @ Function(_, _) => @@ -527,9 +486,8 @@ abstract class UnCurry extends InfoTransform } tree1 } - ) - assert(result.tpe != null, result.shortClass + " tpe is null:\n" + result) - result modifyType uncurry + + result.setType(uncurry(result.tpe)) } def postTransform(tree: Tree): Tree = exitingUncurry { @@ -548,7 +506,7 @@ abstract class UnCurry extends InfoTransform tree } - def isThrowable(pat: Tree): Boolean = pat match { + @tailrec def isThrowable(pat: Tree): Boolean = pat match { case Typed(Ident(nme.WILDCARD), tpt) => tpt.tpe =:= ThrowableTpe case Bind(_, pat) => @@ -620,7 +578,7 @@ abstract class UnCurry extends InfoTransform case Select(_, _) | TypeApply(_, _) => applyUnary() case ret @ Return(expr) if isNonLocalReturn(ret) => - log("non-local return from %s to %s".format(currentOwner.enclMethod, ret.symbol)) + log(s"non-local return from ${currentOwner.enclMethod} to ${ret.symbol}") atPos(ret.pos)(nonLocalReturnThrow(expr, ret.symbol)) case TypeTree() => tree diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index c5a3d605b1..bcc1ed3e64 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -387,8 +387,10 @@ trait Contexts { self: Analyzer => @inline final def withImplicitsEnabled[T](op: => T): T = withMode(enabled = ImplicitsEnabled)(op) @inline final def withImplicitsDisabled[T](op: => T): T = withMode(disabled = ImplicitsEnabled | EnrichmentEnabled)(op) @inline final def withImplicitsDisabledAllowEnrichment[T](op: => T): T = withMode(enabled = EnrichmentEnabled, disabled = ImplicitsEnabled)(op) + @inline final def withImplicits[T](enabled: Boolean)(op: => T): T = if (enabled) withImplicitsEnabled(op) else withImplicitsDisabled(op) @inline final def withMacrosEnabled[T](op: => T): T = withMode(enabled = MacrosEnabled)(op) @inline final def withMacrosDisabled[T](op: => T): T = withMode(disabled = MacrosEnabled)(op) + @inline final def withMacros[T](enabled: Boolean)(op: => T): T = if (enabled) withMacrosEnabled(op) else withMacrosDisabled(op) @inline final def withinStarPatterns[T](op: => T): T = withMode(enabled = StarPatterns)(op) @inline final def withinSuperInit[T](op: => T): T = withMode(enabled = SuperInit)(op) @inline final def withinSecondTry[T](op: => T): T = withMode(enabled = SecondTry)(op) diff --git a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala index 7092f00bff..97de2b6c85 100644 --- a/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala +++ b/src/compiler/scala/tools/nsc/typechecker/EtaExpansion.scala @@ -15,39 +15,29 @@ import symtab.Flags._ * @version 1.0 */ trait EtaExpansion { self: Analyzer => - import global._ - object etaExpansion { - private def isMatch(vparam: ValDef, arg: Tree) = arg match { - case Ident(name) => vparam.name == name - case _ => false - } - - def unapply(tree: Tree): Option[(List[ValDef], Tree, List[Tree])] = tree match { - case Function(vparams, Apply(fn, args)) if (vparams corresponds args)(isMatch) => - Some((vparams, fn, args)) - case _ => - None - } - } - - /** <p> - * Expand partial function applications of type `type`. - * </p><pre> - * p.f(es_1)...(es_n) - * ==> { - * <b>private synthetic val</b> eta$f = p.f // if p is not stable - * ... - * <b>private synthetic val</b> eta$e_i = e_i // if e_i is not stable - * ... - * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) - * }</pre> - * <p> - * tree is already attributed - * </p> - */ - def etaExpand(unit : CompilationUnit, tree: Tree, typer: Typer): Tree = { + /** Expand partial method application `p.f(es_1)...(es_n)`. + * + * We expand this to the following block, which evaluates + * the target of the application and its supplied arguments if needed (they are not stable), + * and then wraps a Function that abstracts over the missing arguments. + * + * ``` + * { + * private synthetic val eta$f = p.f // if p is not stable + * ... + * private synthetic val eta$e_i = e_i // if e_i is not stable + * ... + * (ps_1 => ... => ps_m => eta$f([es_1])...([es_m])(ps_1)...(ps_m)) + * } + * ``` + * + * This is called from instantiateToMethodType after type checking `tree`, + * and we realize we have a method type, where a function type (builtin or SAM) is expected. + * + **/ + def etaExpand(unit: CompilationUnit, tree: Tree, typer: Typer): Tree = { val tpe = tree.tpe var cnt = 0 // for NoPosition def freshName() = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index a34e97b6cb..bee2ae8e99 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -34,14 +34,33 @@ trait Implicits { import typingStack.{ printTyping } import typeDebug._ + // standard usage + def inferImplicitFor(pt: Type, tree: Tree, context: Context, reportAmbiguous: Boolean = true): SearchResult = + inferImplicit(tree, pt, reportAmbiguous, isView = false, context, saveAmbiguousDivergent = true, tree.pos) + + // used by typer to find an implicit coercion + def inferImplicitView(from: Type, to: Type, tree: Tree, context: Context, reportAmbiguous: Boolean, saveAmbiguousDivergent: Boolean) = + inferImplicit(tree, Function1(from, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent, tree.pos) + + // used for manifests, typetags, checking language features, scaladoc + def inferImplicitByType(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = true, isView = false, context, saveAmbiguousDivergent = true, pos) + + def inferImplicitByTypeSilent(pt: Type, context: Context, pos: Position = NoPosition): SearchResult = + inferImplicit(EmptyTree, pt, reportAmbiguous = false, isView = false, context, saveAmbiguousDivergent = false, pos) + + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent = true, tree.pos) + @deprecated("Unused in scalac", "2.12.0-M4") def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean): SearchResult = inferImplicit(tree, pt, reportAmbiguous, isView, context, saveAmbiguousDivergent, tree.pos) - /** Search for an implicit value. See the comment on `result` at the end of class `ImplicitSearch` - * for more info how the search is conducted. + /** Search for an implicit value. Consider using one of the convenience methods above. This one has many boolean levers. + * + * See the comment on `result` at the end of class `ImplicitSearch` for more info how the search is conducted. + * * @param tree The tree for which the implicit needs to be inserted. * (the inference might instantiate some of the undetermined * type parameters of that tree. @@ -92,9 +111,10 @@ trait Implicits { /** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes. */ def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = { - val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _) - def wrapper(inference: => SearchResult) = wrapper1(inference) - val result = wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) + val result = context.withMacros(enabled = !withMacrosDisabled) { + inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context, saveAmbiguousDivergent = !silent, pos) + } + if (result.isFailure && !silent) { val err = context.reporter.firstError val errPos = err.map(_.errPos).getOrElse(pos) @@ -304,6 +324,10 @@ trait Implicits { */ object Function1 { val Sym = FunctionClass(1) + val Pre = Sym.typeConstructor.prefix + + def apply(from: Type, to: Type) = TypeRef(Pre, Sym, List(from, to)) + // It is tempting to think that this should be inspecting "tp baseType Sym" // rather than tp. See test case run/t8280 and the commit message which // accompanies it for explanation why that isn't done. @@ -1207,7 +1231,7 @@ trait Implicits { /* Re-wraps a type in a manifest before calling inferImplicit on the result */ def findManifest(tp: Type, manifestClass: Symbol = if (full) FullManifestClass else PartialManifestClass) = - inferImplicit(tree, appliedType(manifestClass, tp), reportAmbiguous = true, isView = false, context).tree + inferImplicitFor(appliedType(manifestClass, tp), tree, context).tree def findSubManifest(tp: Type) = findManifest(tp, if (full) FullManifestClass else OptManifestClass) def mot(tp0: Type, from: List[Symbol], to: List[Type]): SearchResult = { diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 684cf788a4..dc91d23011 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -164,7 +164,9 @@ trait Infer extends Checkable { | was: $restpe | now""")(normalize(restpe)) case mt @ MethodType(_, restpe) if mt.isImplicit => normalize(restpe) - case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => functionType(mt.paramTypes, normalize(restpe)) + case mt @ MethodType(_, restpe) if !mt.isDependentMethodType => + if (phase.erasedTypes) FunctionClass(mt.params.length).tpe + else functionType(mt.paramTypes, normalize(restpe)) case NullaryMethodType(restpe) => normalize(restpe) case ExistentialType(tparams, qtpe) => newExistentialType(tparams, normalize(qtpe)) case _ => tp // @MAT aliases already handled by subtyping @@ -295,7 +297,7 @@ trait Infer extends Checkable { && !isByNameParamType(tp) && isCompatible(tp, dropByName(pt)) ) - def isCompatibleSam(tp: Type, pt: Type): Boolean = { + def isCompatibleSam(tp: Type, pt: Type): Boolean = (definitions.isFunctionType(tp) || tp.isInstanceOf[MethodType] || tp.isInstanceOf[PolyType]) && { val samFun = typer.samToFunctionType(pt) (samFun ne NoType) && isCompatible(tp, samFun) } @@ -1218,7 +1220,7 @@ trait Infer extends Checkable { } def inferModulePattern(pat: Tree, pt: Type) = - if (!(pat.tpe <:< pt)) { + if ((pat.symbol ne null) && pat.symbol.isModule && !(pat.tpe <:< pt)) { val ptparams = freeTypeParamsOfTerms(pt) debuglog("free type params (2) = " + ptparams) val ptvars = ptparams map freshVar diff --git a/src/compiler/scala/tools/nsc/typechecker/Tags.scala b/src/compiler/scala/tools/nsc/typechecker/Tags.scala index 56127f4026..e29451f379 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Tags.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Tags.scala @@ -13,16 +13,7 @@ trait Tags { private val runDefinitions = currentRun.runDefinitions private def resolveTag(pos: Position, taggedTp: Type, allowMaterialization: Boolean) = enteringTyper { - def wrapper (tree: => Tree): Tree = if (allowMaterialization) (context.withMacrosEnabled[Tree](tree)) else (context.withMacrosDisabled[Tree](tree)) - wrapper(inferImplicit( - EmptyTree, - taggedTp, - reportAmbiguous = true, - isView = false, - context, - saveAmbiguousDivergent = true, - pos - ).tree) + context.withMacros(enabled = allowMaterialization) { inferImplicitByType(taggedTp, context, pos).tree } } /** Finds in scope or materializes a ClassTag. diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 16d3f5134b..fdf7058ab1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -158,7 +158,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper for(ar <- argResultsBuff) paramTp = paramTp.subst(ar.subst.from, ar.subst.to) - val res = if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure else inferImplicit(fun, paramTp, context.reportErrors, isView = false, context) + val res = + if (paramFailed || (paramTp.isErroneous && {paramFailed = true; true})) SearchFailure + else inferImplicitFor(paramTp, fun, context, reportAmbiguous = context.reportErrors) argResultsBuff += res if (res.isSuccess) { @@ -194,14 +196,12 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper !from.isError && !to.isError && context.implicitsEnabled - && (inferView(context.tree, from, to, reportAmbiguous = false, saveErrors = true) != EmptyTree) + && (inferView(context.tree, from, to, reportAmbiguous = false) != EmptyTree) // SI-8230 / SI-8463 We'd like to change this to `saveErrors = false`, but can't. // For now, we can at least pass in `context.tree` rather then `EmptyTree` so as // to avoid unpositioned type errors. ) - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean): Tree = - inferView(tree, from, to, reportAmbiguous, saveErrors = true) /** Infer an implicit conversion (`view`) between two types. * @param tree The tree which needs to be converted. @@ -214,25 +214,23 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * during the inference of a view be put into the original buffer. * False iff we don't care about them. */ - def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { - debuglog("infer view from "+from+" to "+to)//debug - if (isPastTyper) EmptyTree - else from match { - case MethodType(_, _) => EmptyTree - case OverloadedType(_, _) => EmptyTree - case PolyType(_, _) => EmptyTree - case _ => - def wrapImplicit(from: Type): Tree = { - val result = inferImplicit(tree, functionType(from.withoutAnnotations :: Nil, to), reportAmbiguous, isView = true, context, saveAmbiguousDivergent = saveErrors) - if (result.subst != EmptyTreeTypeSubstituter) { - result.subst traverse tree - notifyUndetparamsInferred(result.subst.from, result.subst.to) - } - result.tree - } - wrapImplicit(from) orElse wrapImplicit(byNameType(from)) + def inferView(tree: Tree, from: Type, to: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = + if (isPastTyper || from.isInstanceOf[MethodType] || from.isInstanceOf[OverloadedType] || from.isInstanceOf[PolyType]) EmptyTree + else { + debuglog(s"Inferring view from $from to $to for $tree (reportAmbiguous= $reportAmbiguous, saveErrors=$saveErrors)") + + val fromNoAnnot = from.withoutAnnotations + val result = inferImplicitView(fromNoAnnot, to, tree, context, reportAmbiguous, saveErrors) match { + case fail if fail.isFailure => inferImplicitView(byNameType(fromNoAnnot), to, tree, context, reportAmbiguous, saveErrors) + case ok => ok + } + + if (result.subst != EmptyTreeTypeSubstituter) { + result.subst traverse tree + notifyUndetparamsInferred(result.subst.from, result.subst.to) + } + result.tree } - } import infer._ @@ -246,6 +244,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper var context = context0 def context1 = context + // for use with silent type checking to when we can't have results with undetermined type params + // note that this captures the context var + val isMonoContext = (_: Any) => context.undetparams.isEmpty + def dropExistential(tp: Type): Type = tp match { case ExistentialType(tparams, tpe) => new SubstWildcardMap(tparams).apply(tp) @@ -732,7 +734,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper featureTrait.owner.ownerChain.takeWhile(_ != languageFeatureModule.moduleClass).reverse val featureName = (nestedOwners map (_.name + ".")).mkString + featureTrait.name def action(): Boolean = { - def hasImport = inferImplicit(EmptyTree: Tree, featureTrait.tpe, reportAmbiguous = true, isView = false, context).isSuccess + def hasImport = inferImplicitByType(featureTrait.tpe, context).isSuccess def hasOption = settings.language contains featureName val OK = hasImport || hasOption if (!OK) { @@ -809,7 +811,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (11) Widen numeric literals to their expected type, if necessary * (12) When in mode EXPRmode, convert E to { E; () } if expected type is scala.Unit. * (13) When in mode EXPRmode, apply AnnotationChecker conversion if expected type is annotated. - * (14) When in mode EXPRmode, apply a view + * (14) When in mode EXPRmode, do SAM conversion + * (15) When in mode EXPRmode, apply a view * If all this fails, error */ protected def adapt(tree: Tree, mode: Mode, pt: Type, original: Tree = EmptyTree): Tree = { @@ -1021,72 +1024,70 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } } - def fallbackAfterVanillaAdapt(): Tree = { - def isPopulatedPattern = { - if ((tree.symbol ne null) && tree.symbol.isModule) - inferModulePattern(tree, pt) - - isPopulated(tree.tpe, approximateAbstracts(pt)) + def adaptExprNotFunMode(): Tree = { + def lastTry(err: AbsTypeError = null): Tree = { + debuglog("error tree = " + tree) + if (settings.debug && settings.explaintypes) explainTypes(tree.tpe, pt) + if (err ne null) context.issue(err) + if (tree.tpe.isErroneous || pt.isErroneous) setError(tree) + else adaptMismatchedSkolems() } - if (mode.inPatternMode && isPopulatedPattern) - return tree - val tree1 = constfold(tree, pt) // (10) (11) - if (tree1.tpe <:< pt) - return adapt(tree1, mode, pt, original) + // TODO: should we even get to fallbackAfterVanillaAdapt for an ill-typed tree? + if (mode.typingExprNotFun && !tree.tpe.isErroneous) { + @inline def tpdPos(transformed: Tree) = typedPos(tree.pos, mode, pt)(transformed) + @inline def tpd(transformed: Tree) = typed(transformed, mode, pt) - if (mode.typingExprNotFun) { - // The <: Any requirement inhibits attempts to adapt continuation types - // to non-continuation types. - if (tree.tpe <:< AnyTpe) pt.dealias match { - case TypeRef(_, UnitClass, _) => // (12) - if (!isPastTyper && settings.warnValueDiscard) - context.warning(tree.pos, "discarded non-Unit value") - return typedPos(tree.pos, mode, pt)(Block(List(tree), Literal(Constant(())))) - case TypeRef(_, sym, _) if isNumericValueClass(sym) && isNumericSubType(tree.tpe, pt) => - if (!isPastTyper && settings.warnNumericWiden) - context.warning(tree.pos, "implicit numeric widening") - return typedPos(tree.pos, mode, pt)(Select(tree, "to" + sym.name)) - case _ => - } - if (pt.dealias.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt)) // (13) - return typed(adaptAnnotations(tree, this, mode, pt), mode, pt) + @inline def warnValueDiscard(): Unit = + if (!isPastTyper && settings.warnValueDiscard) context.warning(tree.pos, "discarded non-Unit value") + @inline def warnNumericWiden(): Unit = + if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening") - if (hasUndets) - return instantiate(tree, mode, pt) - - if (context.implicitsEnabled && !pt.isError && !tree.isErrorTyped) { - // (14); the condition prevents chains of views - debuglog("inferring view from " + tree.tpe + " to " + pt) - inferView(tree, tree.tpe, pt, reportAmbiguous = true) match { - case EmptyTree => - case coercion => - def msg = "inferred view from " + tree.tpe + " to " + pt + " = " + coercion + ":" + coercion.tpe - if (settings.logImplicitConv) - context.echo(tree.pos, msg) - - debuglog(msg) - val silentContext = context.makeImplicit(context.ambiguousErrors) - val res = newTyper(silentContext).typed( - new ApplyImplicitView(coercion, List(tree)) setPos tree.pos, mode, pt) - silentContext.reporter.firstError match { - case Some(err) => context.issue(err) - case None => return res + // The <: Any requirement inhibits attempts to adapt continuation types to non-continuation types. + val anyTyped = tree.tpe <:< AnyTpe + + pt.dealias match { + case TypeRef(_, UnitClass, _) if anyTyped => // (12) + warnValueDiscard() ; tpdPos(gen.mkUnitBlock(tree)) + case TypeRef(_, numValueCls, _) if anyTyped && isNumericValueClass(numValueCls) && isNumericSubType(tree.tpe, pt) => // (10) (11) + warnNumericWiden() ; tpdPos(Select(tree, s"to${numValueCls.name}")) + case dealiased if dealiased.annotations.nonEmpty && canAdaptAnnotations(tree, this, mode, pt) => // (13) + tpd(adaptAnnotations(tree, this, mode, pt)) + case _ => + if (hasUndets) instantiate(tree, mode, pt) + else { + // (14) sam conversion + // TODO: figure out how to avoid partially duplicating typedFunction (samMatchingFunction) + // Could we infer the SAM type, assign it to the tree and add the attachment, + // all in one fell swoop at the end of typedFunction? + val samAttach = inferSamType(tree, pt, mode) + + if (samAttach.samTp ne NoType) tree.setType(samAttach.samTp).updateAttachment(samAttach) + else { // (15) implicit view application + val coercion = + if (context.implicitsEnabled) inferView(tree, tree.tpe, pt) + else EmptyTree + if (coercion ne EmptyTree) { + def msg = s"inferred view from ${tree.tpe} to $pt via $coercion: ${coercion.tpe}" + if (settings.logImplicitConv) context.echo(tree.pos, msg) + else debuglog(msg) + + val viewApplied = new ApplyImplicitView(coercion, List(tree)) setPos tree.pos + val silentContext = context.makeImplicit(context.ambiguousErrors) + val typedView = newTyper(silentContext).typed(viewApplied, mode, pt) + + silentContext.reporter.firstError match { + case None => typedView + case Some(err) => lastTry(err) + } + } else lastTry() } - } + } } - } - - debuglog("error tree = " + tree) - if (settings.debug && settings.explaintypes) - explainTypes(tree.tpe, pt) - - if (tree.tpe.isErroneous || pt.isErroneous) - setError(tree) - else - adaptMismatchedSkolems() + } else lastTry() } + def vanillaAdapt(tree: Tree) = { def applyPossible = { def applyMeth = member(adaptToName(tree, nme.apply), nme.apply) @@ -1120,8 +1121,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper } else if (tree.tpe <:< pt) tree - else - fallbackAfterVanillaAdapt() + else if (mode.inPatternMode && { inferModulePattern(tree, pt); isPopulated(tree.tpe, approximateAbstracts(pt)) }) + tree + else { + val constFolded = constfold(tree, pt) + if (constFolded.tpe <:< pt) adapt(constFolded, mode, pt, original) // set stage for (0) + else adaptExprNotFunMode() // (10) -- (15) + } } // begin adapt @@ -1186,7 +1192,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper val savedUndetparams = context.undetparams silent(_.instantiate(tree, mode, UnitTpe)) orElse { _ => context.undetparams = savedUndetparams - val valueDiscard = atPos(tree.pos)(Block(List(instantiate(tree, mode, WildcardType)), Literal(Constant(())))) + val valueDiscard = atPos(tree.pos)(gen.mkUnitBlock(instantiate(tree, mode, WildcardType))) typed(valueDiscard, mode, UnitTpe) } } @@ -1247,7 +1253,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * If no conversion is found, return `qual` unchanged. * */ - def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToArguments(qual: Tree, name: Name, args: List[Tree], pt: Type, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def doAdapt(restpe: Type) = //util.trace("adaptToArgs "+qual+", name = "+name+", argtpes = "+(args map (_.tpe))+", pt = "+pt+" = ") adaptToMember(qual, HasMethodMatching(name, args map (_.tpe), restpe), reportAmbiguous, saveErrors) @@ -1263,7 +1269,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * a method `name`. If that's ambiguous try taking arguments into * account using `adaptToArguments`. */ - def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean, saveErrors: Boolean): Tree = { + def adaptToMemberWithArgs(tree: Tree, qual: Tree, name: Name, mode: Mode, reportAmbiguous: Boolean = true, saveErrors: Boolean = true): Tree = { def onError(reportError: => Tree): Tree = context.tree match { case Apply(tree1, args) if (tree1 eq tree) && args.nonEmpty => ( silent (_.typedArgs(args.map(_.duplicate), mode)) @@ -1745,17 +1751,21 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper classinfo.parents map (_.instantiateTypeParams(List(tparam), List(AnyRefTpe))), classinfo.decls, clazz) - clazz.setInfo { - clazz.info match { - case PolyType(tparams, _) => PolyType(tparams, newinfo) - case _ => newinfo - } - } + updatePolyClassInfo(clazz, newinfo) FinitaryError(tparam) } } } + private def updatePolyClassInfo(clazz: Symbol, newinfo: ClassInfoType): clazz.type = { + clazz.setInfo { + clazz.info match { + case PolyType(tparams, _) => PolyType(tparams, newinfo) + case _ => newinfo + } + } + } + def typedClassDef(cdef: ClassDef): Tree = { val clazz = cdef.symbol val typedMods = typedModifiers(cdef.mods) @@ -1864,6 +1874,26 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // please FIXME: uncommenting this line breaks everything // val templ = treeCopy.Template(templ0, templ0.body, templ0.self, templ0.parents) val clazz = context.owner + + val parentTypes = parents1.map(_.tpe) + + // The parents may have been normalized by typedParentTypes. + // We must update the info as well, or we won't find the super constructor for our now-first parent class + // Consider `class C ; trait T extends C ; trait U extends T` + // `U`'s info will start with parent `T`, but `typedParentTypes` will return `List(C, T)` (`== parents1`) + // now, the super call in the primary ctor will fail to find `C`'s ctor, since it bases its search on + // `U`'s info, not the trees. + // + // For correctness and performance, we restrict this rewrite to anonymous classes, + // as others have their parents in order already (it seems!), and we certainly + // don't want to accidentally rewire superclasses for e.g. the primitive value classes. + // + // TODO: Find an example of a named class needing this rewrite, I tried but couldn't find one. + if (clazz.isAnonymousClass && clazz.info.parents != parentTypes) { +// println(s"updating parents of $clazz from ${clazz.info.parents} to $parentTypes") + updatePolyClassInfo(clazz, ClassInfoType(parentTypes, clazz.info.decls, clazz)) + } + clazz.annotations.map(_.completeInfo()) if (templ.symbol == NoSymbol) templ setSymbol clazz.newLocalDummy(templ.pos) @@ -2713,187 +2743,99 @@ 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... - * If it is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. - * - * The types T1' ... TN' and T' are derived from the method signature of the sam method, - * as seen from the fully defined `samClassTpFullyDefined`. - * - * 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. - * - * TODO: by-name params - * scala> trait LazySink { def accept(a: => Any): Unit } - * defined trait LazySink - * - * scala> val f: LazySink = (a) => (a, a) - * f: LazySink = $anonfun$1@1fb26910 - * - * scala> f(println("!")) - * <console>:10: error: LazySink does not take parameters - * f(println("!")) - * ^ - * - * scala> f.accept(println("!")) - * ! - * ! - */ - 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 + /** Synthesize and type check the implementation of a type with a Single Abstract Method. + * + * Based on a type checked Function node `{ (p1: T1, ..., pN: TN) => body } : S` + * where `S` is the expected type that defines a single abstract method (call it `apply` for the example), + * that has signature `(p1: T1', ..., pN: TN'): T'`, synthesize the instantiation of the following anonymous class + * + * ``` + * new S { + * def apply$body(p1: T1, ..., pN: TN): T = body + * def apply(p1: T1', ..., pN: TN'): T' = apply$body(p1,..., pN) + * } + * ``` + * + * The `apply` method is identified by the argument `sam`; `S` corresponds to the argument `pt`, + * If `pt` is not fully defined, we derive `samClassTpFullyDefined` by inferring any unknown type parameters. + * + * The types T1' ... TN' and T' are derived from the method signature of the sam method, + * as seen from the fully defined `samClassTpFullyDefined`. + * + * The function's body is put in a (static) method in the class definition to enforce scoping. + * S's members should not be in scope in `body`. (Putting it in the block outside the class runs into implementation problems described below) + * + * The restriction on implicit arguments (neither S's constructor, nor sam may take an implicit argument list), + * is to keep the implementation of type inference (the computation of `samClassTpFullyDefined`) simple. + * + * Impl notes: + * - `fun` has a FunctionType, but the expected type `pt` is some SAM type -- let's remedy that + * - `fun` is fully attributed, so we'll have to wrangle some symbols into shape (owner change, vparam syms) + * - after experimentation, it works best to type check function literals fully first and then adapt to a sam type, + * as opposed to a sam-specific code paths earlier on in type checking (in typedFunction). + * For one, we want to emit the same bytecode regardless of whether the expected + * function type is a built-in FunctionN or some SAM type + * + */ + def inferSamType(fun: Tree, pt: Type, mode: Mode): SAMFunction = { + val sam = + if (fun.isInstanceOf[Function] && !isFunctionType(pt)) { + // TODO: can we ensure there's always a SAMFunction attachment, instead of looking up the sam again??? + // seems like overloading complicates things? + val sam = samOf(pt) + if (samMatchesFunctionBasedOnArity(sam, fun.asInstanceOf[Function].vparams)) sam + else NoSymbol + } else NoSymbol + + def fullyDefinedMeetsExpectedFunTp(pt: Type): Boolean = isFullyDefined(pt) && { + val samMethType = pt memberInfo sam + fun.tpe <:< functionType(samMethType.paramTypes, samMethType.resultType) + } + + SAMFunction( + if (!sam.exists) NoType + else if (fullyDefinedMeetsExpectedFunTp(pt)) pt 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 + val samClassSym = pt.typeSymbol // we're trying to fully define the type arguments for this type constructor - val samTyCon = samClassTp.typeSymbol.typeConstructor + val samTyCon = samClassSym.typeConstructor // the unknowns - val tparams = samClassTp.typeSymbol.typeParams + val tparams = samClassSym.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) + val tvars = tparams map freshVar - // 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) + val ptVars = appliedType(samTyCon, tvars) - // solve constraints tracked by tvars - val targs = solvedTypes(tvars, tparams, tparams map varianceInType(sam.info), upper = false, lubDepth(sam.info :: Nil)) + // carry over info from pt + ptVars <:< pt - debuglog(s"sam infer: $samClassTp --> ${appliedType(samTyCon, targs)} by $actualSamType <:< $expectedSamType --> $targs for $tparams") + val samInfoWithTVars = ptVars.memberInfo(sam) - // a fully defined samClassTp - appliedType(samTyCon, targs) - } catch { - case _: NoInstance | _: TypeError => - devWarning(sampos, s"Could not define type $samClassTp using ${samBodyDef.symbol.rawInfo} <:< ${samClassTp memberInfo sam} (for $sam)") - samClassTp - } - - // what's the signature of the method that we should actually be overriding? - val samMethTp = samClassTpFullyDefined memberInfo sam - // Before the mutation, `tp <:< vpar.tpt.tpe` should hold. - // TODO: error message when this is not the case, as the expansion won't type check - // - Ti' <:< Ti and T <: T' must hold for the samDef body to type check - val funArgTps = foreach2(samMethTp.paramTypes, fun.vparams)((tp, vpar) => vpar.tpt setType tp) - - // `final override def ${sam.name}($p1: $T1', ..., $pN: $TN'): ${samMethTp.finalResultType} = ${sam.name}\$body'($p1, ..., $pN)` - val samDef = - DefDef(Modifiers(FINAL | OVERRIDE | SYNTHETIC), - sam.name.toTermName, - Nil, - List(fun.vparams), - TypeTree(samMethTp.finalResultType) setPos sampos.focus, - Apply(Ident(bodyName), fun.vparams map gen.paramToArg) - ) + // use function type subtyping, not method type subtyping (the latter is invariant in argument types) + fun.tpe <:< functionType(samInfoWithTVars.paramTypes, samInfoWithTVars.finalResultType) - 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 = noSelfType, - constrMods = NoMods, - vparamss = ListOfNil, - body = List(samDef), - superPos = sampos.focus - ) - ) + val variances = tparams map varianceInType(sam.info) - // 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) - ) - } + // solve constraints tracked by tvars + val targs = solvedTypes(tvars, tparams, variances, upper = false, lubDepth(sam.info :: Nil)) - // TODO: improve error reporting -- when we're in silent mode (from `silent(_.doTypedApply(tree, fun, args, mode, pt)) orElse onError`) - // the errors in the function don't get out... - if (block exists (_.isErroneous)) - context.error(fun.pos, s"Could not derive subclass of $samClassTp\n (with SAM `def $sam$samMethTp`)\n based on: $fun.") + debuglog(s"sam infer: $pt --> ${appliedType(samTyCon, targs)} by ${fun.tpe} <:< $samInfoWithTVars --> $targs for $tparams") - classDef.symbol addAnnotation SerialVersionUIDAnnotation - block + val ptFullyDefined = appliedType(samTyCon, targs) + if (ptFullyDefined <:< pt && fullyDefinedMeetsExpectedFunTp(ptFullyDefined)) { + debuglog(s"sam fully defined expected type: $ptFullyDefined from $pt for ${fun.tpe}") + ptFullyDefined + } else { + debuglog(s"Could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)") + NoType + } + } catch { + case e@(_: NoInstance | _: TypeError) => + debuglog(s"Error during SAM synthesis: could not define type $pt using ${fun.tpe} <:< ${pt memberInfo sam} (for $sam)\n$e") + NoType + }, sam) } /** Type check a function literal. @@ -2903,16 +2845,19 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * - 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 + val vparams = fun.vparams + val numVparams = vparams.length val FunctionSymbol = if (numVparams > definitions.MaxFunctionArity) NoSymbol else FunctionClass(numVparams) + val ptSym = pt.typeSymbol + /* 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 (pt.typeSymbol == FunctionSymbol) NoSymbol + if (ptSym == NoSymbol || ptSym == FunctionSymbol || ptSym == PartialFunctionClass) NoSymbol else samOf(pt) /* The SAM case comes first so that this works: @@ -2920,79 +2865,101 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper * (a => a): MyFun * * Note that the arity of the sam must correspond to the arity of the function. + * TODO: handle vararg sams? */ - val samViable = sam.exists && sameLength(sam.info.params, fun.vparams) - val ptNorm = if (samViable) samToFunctionType(pt, sam) else pt + val ptNorm = + if (samMatchesFunctionBasedOnArity(sam, vparams)) samToFunctionType(pt, sam) + else pt val (argpts, respt) = ptNorm baseType FunctionSymbol match { case TypeRef(_, FunctionSymbol, args :+ res) => (args, res) - case _ => (fun.vparams map (_ => if (pt == ErrorType) ErrorType else NoType), WildcardType) + case _ => (vparams map (if (pt == ErrorType) (_ => ErrorType) else (_ => NoType)), WildcardType) } - if (!FunctionSymbol.exists) - MaxFunctionArityError(fun) - else if (argpts.lengthCompare(numVparams) != 0) - WrongNumberOfParametersError(fun, argpts) + if (!FunctionSymbol.exists) MaxFunctionArityError(fun) + else if (argpts.lengthCompare(numVparams) != 0) WrongNumberOfParametersError(fun, argpts) else { - var issuedMissingParameterTypeError = false - foreach2(fun.vparams, argpts) { (vparam, argpt) => - if (vparam.tpt.isEmpty) { - val vparamType = - if (isFullyDefined(argpt)) argpt - else { - 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, - // 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) - if (isFunctionType(ftpe) && isFullyDefined(ftpe)) - return typedFunction(fun, mode, ftpe) - } - case _ => - } - MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) - issuedMissingParameterTypeError = true - ErrorType - } - vparam.tpt.setType(vparamType) + val paramsMissingType = mutable.ArrayBuffer.empty[ValDef] //.sizeHint(numVparams) probably useless, since initial size is 16 and max fun arity is 22 + // first, try to define param types from expected function's arg types if needed + foreach2(vparams, argpts) { (vparam, argpt) => + if (vparam.tpt isEmpty) { + if (isFullyDefined(argpt)) vparam.tpt setType argpt + else paramsMissingType += vparam + if (!vparam.tpt.pos.isDefined) vparam.tpt setPos vparam.pos.focus } } - fun.body match { - // translate `x => x match { <cases> }` : PartialFunction to - // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }` - case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => - // go to outer context -- must discard the context that was created for the Function since we're discarding the function - // thus, its symbol, which serves as the current context.owner, is not the right owner - // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) - val outerTyper = newTyper(context.outer) - val p = fun.vparams.head - if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe + // If we're typing `(a1: T1, ..., aN: TN) => m(a1,..., aN)`, where some Ti are not fully defined, + // type `m` directly (undoing eta-expansion of method m) to determine the argument types. + // This tree is the result from one of: + // - manual eta-expansion with named arguments (x => f(x)); + // - wildcard-style eta expansion (`m(_, _,)`); + // - instantiateToMethodType adapting a tree of method type to a function type using etaExpand. + // + // Note that method values are a separate thing (`m _`): they have the idiosyncratic shape + // of `Typed(expr, Function(Nil, EmptyTree))` + val ptUnrollingEtaExpansion = + if (paramsMissingType.nonEmpty && pt != ErrorType) fun.body match { + // we can compare arguments and parameters by name because there cannot be a binder between + // the function's valdefs and the Apply's arguments + case Apply(meth, args) if (vparams corresponds args) { case (p, Ident(name)) => p.name == name case _ => false } => + // We're looking for a method (as indicated by FUNmode in the silent typed below), + // so let's make sure our expected type is a MethodType + val methArgs = NoSymbol.newSyntheticValueParams(argpts map { case NoType => WildcardType case tp => tp }) + silent(_.typed(meth, mode.forFunMode, MethodType(methArgs, respt))) filter (isMonoContext) map { methTyped => + // if context.undetparams is not empty, the method was polymorphic, + // so we need the missing arguments to infer its type. See #871 + val funPt = normalize(methTyped.tpe) baseType FunctionClass(numVparams) + // println(s"typeUnEtaExpanded $meth : ${methTyped.tpe} --> normalized: $funPt") + + // If we are sure this function type provides all the necesarry info, so that we won't have + // any undetermined argument types, go ahead an recurse below (`typedFunction(fun, mode, ptUnrollingEtaExpansion)`) + // and rest assured we won't end up right back here (and keep recursing) + if (isFunctionType(funPt) && funPt.typeArgs.iterator.take(numVparams).forall(isFullyDefined)) funPt + else null + } orElse { _ => null } + case _ => null + } else null + + + if (ptUnrollingEtaExpansion ne null) typedFunction(fun, mode, ptUnrollingEtaExpansion) + else { + // we ran out of things to try, missing parameter types are an irrevocable error + var issuedMissingParameterTypeError = false + paramsMissingType.foreach { vparam => + vparam.tpt setType ErrorType + MissingParameterTypeError(fun, vparam, pt, withTupleAddendum = !issuedMissingParameterTypeError) + issuedMissingParameterTypeError = true + } - outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) + fun.body match { + // translate `x => x match { <cases> }` : PartialFunction to + // `new PartialFunction { def applyOrElse(x, default) = x match { <cases> } def isDefinedAt(x) = ... }` + case Match(sel, cases) if (sel ne EmptyTree) && (pt.typeSymbol == PartialFunctionClass) => + // go to outer context -- must discard the context that was created for the Function since we're discarding the function + // thus, its symbol, which serves as the current context.owner, is not the right owner + // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) + val outerTyper = newTyper(context.outer) + val p = vparams.head + if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe - // Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body` - // to an instance of the corresponding anonymous subclass of `pt`. - case _ if samViable => - newTyper(context.outer).synthesizeSAMFunction(sam, fun, respt, pt, mode) + outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt) - // regular Function - case _ => - val vparamSyms = fun.vparams map { vparam => - enterSym(context, vparam) - if (context.retyping) context.scope enter vparam.symbol - vparam.symbol - } - val vparams = fun.vparams mapConserve typedValDef - val formals = vparamSyms map (_.tpe) - val body1 = typed(fun.body, respt) - val restpe = packedType(body1, fun.symbol).deconst.resultType - val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe) + case _ => + val vparamSyms = vparams map { vparam => + enterSym(context, vparam) + if (context.retyping) context.scope enter vparam.symbol + vparam.symbol + } + val vparamsTyped = vparams mapConserve typedValDef + val formals = vparamSyms map (_.tpe) + val body1 = typed(fun.body, respt) + val restpe = packedType(body1, fun.symbol).deconst.resultType + val funtpe = phasedAppliedType(FunctionSymbol, formals :+ restpe) - treeCopy.Function(fun, vparams, body1) setType funtpe + treeCopy.Function(fun, vparamsTyped, body1) setType funtpe + } } } } @@ -3222,7 +3189,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // less expensive than including them in inferMethodAlternative (see below). def shapeType(arg: Tree): Type = arg match { case Function(vparams, body) => - functionType(vparams map (_ => AnyTpe), shapeType(body)) // TODO: should this be erased when retyping during erasure? + // No need for phasedAppliedType, as we don't get here during erasure -- + // overloading resolution happens during type checking. + // During erasure, the condition above (fun.symbol.isOverloaded) is false. + functionType(vparams map (_ => AnyTpe), shapeType(body)) case AssignOrNamedArg(Ident(name), rhs) => NamedType(name, shapeType(rhs)) case _ => @@ -4303,7 +4273,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (pt.typeSymbol == PartialFunctionClass) synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, paramSynthetic = true, tree, mode, pt) else { - val arity = if (isFunctionType(pt)) pt.dealiasWiden.typeArgs.length - 1 else 1 + val arity = functionArityFromType(pt) match { case -1 => 1 case arity => arity } // SI-8429: consider sam and function type equally in determining function arity + val params = for (i <- List.range(0, arity)) yield atPos(tree.pos.focusStart) { ValDef(Modifiers(PARAM | SYNTHETIC), @@ -4403,31 +4374,43 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper treeCopy.New(tree, tpt1).setType(tp) } - def functionTypeWildcard(tree: Tree, arity: Int): Type = { - val tp = functionType(List.fill(arity)(WildcardType), WildcardType) - if (tp == NoType) MaxFunctionArityError(tree) - tp - } - - def typedEta(expr1: Tree): Tree = expr1.tpe match { - case TypeRef(_, ByNameParamClass, _) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case NullaryMethodType(restpe) => - val expr2 = Function(List(), expr1) setPos expr1.pos - new ChangeOwnerTraverser(context.owner, expr2.symbol).traverse(expr2) - typed1(expr2, mode, pt) - case PolyType(_, MethodType(formals, _)) => - if (isFunctionType(pt)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) - case MethodType(formals, _) => - if (isFunctionType(pt)) expr1 - else adapt(expr1, mode, functionTypeWildcard(expr1, formals.length)) + def functionTypeWildcard(arity: Int): Type = + functionType(List.fill(arity)(WildcardType), WildcardType) + + def checkArity(tree: Tree)(tp: Type): tp.type = tp match { + case NoType => MaxFunctionArityError(tree); tp + case _ => tp + } + + + /** Eta expand an expression like `m _`, where `m` denotes a method or a by-name argument + * + * The spec says: + * The expression `$e$ _` is well-formed if $e$ is of method type or if $e$ is a call-by-name parameter. + * (1) If $e$ is a method with parameters, `$e$ _` represents $e$ converted to a function type + * by [eta expansion](#eta-expansion). + * (2) If $e$ is a parameterless method or call-by-name parameter of type `=>$T$`, `$e$ _` represents + * the function of type `() => $T$`, which evaluates $e$ when it is applied to the empty parameterlist `()`. + */ + def typedEta(methodValue: Tree): Tree = methodValue.tpe match { + case tp@(MethodType(_, _) | PolyType(_, MethodType(_, _))) => // (1) + val formals = tp.params + if (isFunctionType(pt) || samMatchesFunctionBasedOnArity(samOf(pt), formals)) methodValue + else adapt(methodValue, mode, checkArity(methodValue)(functionTypeWildcard(formals.length))) + + case TypeRef(_, ByNameParamClass, _) | NullaryMethodType(_) => // (2) + val pos = methodValue.pos + // must create it here to change owner (normally done by typed's typedFunction) + val funSym = context.owner.newAnonymousFunctionValue(pos) + new ChangeOwnerTraverser(context.owner, funSym) traverse methodValue + + typed(Function(List(), methodValue) setSymbol funSym setPos pos, mode, pt) + case ErrorType => - expr1 + methodValue + case _ => - UnderscoreEtaError(expr1) + UnderscoreEtaError(methodValue) } def tryTypedArgs(args: List[Tree], mode: Mode): Option[List[Tree]] = { @@ -4471,7 +4454,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case Annotated(_, r) => treesInResult(r) case If(_, t, e) => treesInResult(t) ++ treesInResult(e) case Try(b, catches, _) => treesInResult(b) ++ catches - case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) + case Typed(r, Function(Nil, EmptyTree)) => treesInResult(r) // a method value case Select(qual, name) => treesInResult(qual) case Apply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) case TypeApply(fun, args) => treesInResult(fun) ++ args.flatMap(treesInResult) @@ -4490,7 +4473,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper tryTypedArgs(args, forArgMode(fun, mode)) match { case Some(args1) if !args1.exists(arg => arg.exists(_.isErroneous)) => val qual1 = - if (!pt.isError) adaptToArguments(qual, name, args1, pt, reportAmbiguous = true, saveErrors = true) + if (!pt.isError) adaptToArguments(qual, name, args1, pt) else qual if (qual1 ne qual) { val tree1 = Apply(Select(qual1, name) setPos fun.pos, args1) setPos tree.pos @@ -4717,7 +4700,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // member. Added `| PATTERNmode` to allow enrichment in patterns (so we can add e.g., an // xml member to StringContext, which in turn has an unapply[Seq] method) if (name != nme.CONSTRUCTOR && mode.inAny(EXPRmode | PATTERNmode)) { - val qual1 = adaptToMemberWithArgs(tree, qual, name, mode, reportAmbiguous = true, saveErrors = true) + val qual1 = adaptToMemberWithArgs(tree, qual, name, mode) if ((qual1 ne qual) && !qual1.isErrorTyped) return typed(treeCopy.Select(tree, qual1, name), mode, pt) } @@ -5111,11 +5094,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper // because `expr` might contain nested macro calls (see SI-6673) // // Note: apparently `Function(Nil, EmptyTree)` is the secret parser marker - // which means trailing underscore. + // which means trailing underscore -- denoting a method value. See makeMethodValue in TreeBuilder. case Typed(expr, Function(Nil, EmptyTree)) => typed1(suppressMacroExpansion(expr), mode, pt) match { case macroDef if treeInfo.isMacroApplication(macroDef) => MacroEtaError(macroDef) - case exprTyped => typedEta(checkDead(exprTyped)) + case methodValue => typedEta(checkDead(methodValue)) } case Typed(expr, tpt) => val tpt1 = typedType(tpt, mode) // type the ascribed type first diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala index ae6a9e22b6..9c4d521336 100644 --- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala +++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala @@ -117,13 +117,15 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => def transformDuringTyper(expr: Tree, mode: scala.reflect.internal.Mode, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean)(transform: (analyzer.Typer, Tree) => Tree): Tree = { def withWrapping(tree: Tree)(op: Tree => Tree) = if (mode == TERMmode) wrappingIntoTerm(tree)(op) else op(tree) - withWrapping(verify(expr))(expr1 => { + withWrapping(verify(expr)) { expr => // need to extract free terms, because otherwise you won't be able to typecheck macros against something that contains them - val exprAndFreeTerms = extractFreeTerms(expr1, wrapFreeTermRefs = false) - var expr2 = exprAndFreeTerms._1 - val freeTerms = exprAndFreeTerms._2 - val dummies = freeTerms.map{ case (freeTerm, name) => ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) }.toList - expr2 = Block(dummies, expr2) + val (extracted, freeTerms) = extractFreeTerms(expr, wrapFreeTermRefs = false) + val exprBound = { + val binders = freeTerms.toList.map { case (freeTerm, name) => + ValDef(NoMods, name, TypeTree(freeTerm.info), Select(Ident(PredefModule), newTermName("$qmark$qmark$qmark"))) + } + Block(binders, extracted) + } // !!! Why is this is in the empty package? If it's only to make // it inaccessible then please put it somewhere designed for that @@ -131,26 +133,29 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => // [Eugene] how can we implement that? val ownerClass = rootMirror.EmptyPackageClass.newClassSymbol(newTypeName("<expression-owner>")) build.setInfo(ownerClass, ClassInfoType(List(ObjectTpe), newScope, ownerClass)) - val owner = ownerClass.newLocalDummy(expr2.pos) - val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(expr2, owner)) - val withImplicitFlag = if (!withImplicitViewsDisabled) (currentTyper.context.withImplicitsEnabled[Tree] _) else (currentTyper.context.withImplicitsDisabled[Tree] _) - val withMacroFlag = if (!withMacrosDisabled) (currentTyper.context.withMacrosEnabled[Tree] _) else (currentTyper.context.withMacrosDisabled[Tree] _) - def withContext (tree: => Tree) = withImplicitFlag(withMacroFlag(tree)) + val owner = ownerClass.newLocalDummy(exprBound.pos) + val currentTyper = analyzer.newTyper(analyzer.rootContext(NoCompilationUnit, EmptyTree).make(exprBound, owner)) + import currentTyper.{context => currCtx} val run = new Run run.symSource(ownerClass) = NoAbstractFile // need to set file to something different from null, so that currentRun.defines works phase = run.typerPhase // need to set a phase to something <= typerPhase, otherwise implicits in typedSelect will be disabled globalPhase = run.typerPhase // amazing... looks like phase and globalPhase are different things, so we need to set them separately - currentTyper.context.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions + currCtx.initRootContext() // need to manually set context mode, otherwise typer.silent will throw exceptions reporter.reset() - val expr3 = withContext(transform(currentTyper, expr2)) - var (dummies1, result) = expr3 match { - case Block(dummies, result) => ((dummies, result)) - case result => ((Nil, result)) - } + val (binders, transformed) = + currCtx.withImplicits(enabled = !withImplicitViewsDisabled) { + currCtx.withMacros(enabled = !withMacrosDisabled) { + transform(currentTyper, exprBound) + } + } match { + case Block(binders, transformed) => (binders, transformed) + case transformed => (Nil, transformed) + } + val invertedIndex = freeTerms map (_.swap) - result = new Transformer { + val indexed = new Transformer { override def transform(tree: Tree): Tree = tree match { case Ident(name: TermName) if invertedIndex contains name => @@ -158,10 +163,10 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf => case _ => super.transform(tree) } - }.transform(result) - new TreeTypeSubstituter(dummies1 map (_.symbol), dummies1 map (dummy => SingleType(NoPrefix, invertedIndex(dummy.symbol.name.toTermName)))).traverse(result) - result - }) + }.transform(transformed) + new TreeTypeSubstituter(binders map (_.symbol), binders map (b => SingleType(NoPrefix, invertedIndex(b.symbol.name.toTermName)))).traverse(indexed) + indexed + } } def typecheck(expr: Tree, pt: Type, mode: scala.reflect.internal.Mode, silent: Boolean, withImplicitViewsDisabled: Boolean, withMacrosDisabled: Boolean): Tree = diff --git a/src/library/scala/collection/mutable/AnyRefMap.scala b/src/library/scala/collection/mutable/AnyRefMap.scala index 2ed5bbea60..6ff79dd1b8 100644 --- a/src/library/scala/collection/mutable/AnyRefMap.scala +++ b/src/library/scala/collection/mutable/AnyRefMap.scala @@ -419,7 +419,11 @@ object AnyRefMap { private final val VacantBit = 0x40000000 private final val MissVacant = 0xC0000000 - private val exceptionDefault = (k: Any) => throw new NoSuchElementException(if (k == null) "(null)" else k.toString) + @SerialVersionUID(1L) + private class ExceptionDefault extends (Any => Nothing) with Serializable { + def apply(k: Any): Nothing = throw new NoSuchElementException(if (k == null) "(null)" else k.toString) + } + private val exceptionDefault = new ExceptionDefault implicit def canBuildFrom[K <: AnyRef, V, J <: AnyRef, U]: CanBuildFrom[AnyRefMap[K,V], (J, U), AnyRefMap[J,U]] = new CanBuildFrom[AnyRefMap[K,V], (J, U), AnyRefMap[J,U]] { diff --git a/src/library/scala/util/control/Exception.scala b/src/library/scala/util/control/Exception.scala index 702d3b5e5e..8aa4073a51 100644 --- a/src/library/scala/util/control/Exception.scala +++ b/src/library/scala/util/control/Exception.scala @@ -83,6 +83,10 @@ object Exception { * Pass a different value for rethrow if you want to probably * unwisely allow catching control exceptions and other throwables * which the rest of the world may expect to get through. + * @tparam T result type of bodies used in try and catch blocks + * @param pf Partial function used when applying catch logic to determine result value + * @param fin Finally logic which if defined will be invoked after catch logic + * @param rethrow Predicate on throwables determining when to rethrow a caught [[Throwable]] */ class Catch[+T]( val pf: Catcher[T], @@ -105,10 +109,12 @@ object Exception { } finally fin foreach (_.invoke()) - /* Create an empty Try container with this Catch and the supplied `Finally`. */ - def andFinally(body: => Unit): Catch[T] = fin match { - case None => new Catch(pf, Some(new Finally(body)), rethrow) - case Some(f) => new Catch(pf, Some(f and body), rethrow) + /** Create a new Catch container from this object and the supplied finally body. + * @param body The additional logic to apply after all existing finally bodies + */ + def andFinally(body: => Unit): Catch[T] = { + val appendedFin = fin map(_ and body) getOrElse new Finally(body) + new Catch(pf, Some(appendedFin), rethrow) } /** Apply this catch logic to the supplied body, mapping the result @@ -117,13 +123,13 @@ object Exception { def opt[U >: T](body: => U): Option[U] = toOption(Some(body)) /** Apply this catch logic to the supplied body, mapping the result - * into Either[Throwable, T] - Left(exception) if an exception was caught, - * Right(T) otherwise. + * into `Either[Throwable, T]` - `Left(exception)` if an exception was caught, + * `Right(T)` otherwise. */ def either[U >: T](body: => U): Either[Throwable, U] = toEither(Right(body)) /** Apply this catch logic to the supplied body, mapping the result - * into Try[T] - Failure if an exception was caught, Success(T) otherwise. + * into `Try[T]` - `Failure` if an exception was caught, `Success(T)` otherwise. */ def withTry[U >: T](body: => U): scala.util.Try[U] = toTry(Success(body)) diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index 9ff4e89903..8074b448fe 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -675,6 +675,32 @@ trait Definitions extends api.StandardDefinitions { // Note that these call .dealiasWiden and not .normalize, the latter of which // tends to change the course of events by forcing types. def isFunctionType(tp: Type) = isFunctionTypeDirect(tp.dealiasWiden) + // the number of arguments expected by the function described by `tp` (a FunctionN or SAM type), + // or `-1` if `tp` does not represent a function type or SAM + def functionArityFromType(tp: Type) = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.length - 1 + else samOf(tp) match { + case samSym if samSym.exists => samSym.info.params.length + case _ => -1 + } + } + + // the result type of a function or corresponding SAM type + def functionResultType(tp: Type): Type = { + val dealiased = tp.dealiasWiden + if (isFunctionTypeDirect(dealiased)) dealiased.typeArgs.last + else samOf(tp) match { + case samSym if samSym.exists => tp.memberInfo(samSym).resultType.deconst + case _ => NoType + } + } + + // the SAM's parameters and the Function's formals must have the same length + // (varargs etc don't come into play, as we're comparing signatures, not checking an application) + def samMatchesFunctionBasedOnArity(sam: Symbol, formals: List[Any]): Boolean = + sam.exists && sameLength(sam.info.params, formals) + def isTupleType(tp: Type) = isTupleTypeDirect(tp.dealiasWiden) def tupleComponents(tp: Type) = tp.dealiasWiden.typeArgs @@ -793,10 +819,6 @@ trait Definitions extends api.StandardDefinitions { private[this] var volatileRecursions: Int = 0 private[this] val pendingVolatiles = mutable.HashSet[Symbol]() - def abstractFunctionForFunctionType(tp: Type) = { - assert(isFunctionType(tp), tp) - abstractFunctionType(tp.typeArgs.init, tp.typeArgs.last) - } def functionNBaseType(tp: Type): Type = tp.baseClasses find isFunctionSymbol match { case Some(sym) => tp baseType unspecializedSymbol(sym) case _ => tp @@ -807,22 +829,29 @@ trait Definitions extends api.StandardDefinitions { (sym eq PartialFunctionClass) || (sym eq AbstractPartialFunctionClass) } + private[this] val doSam = settings.isScala212 || (settings.isScala211 && settings.Xexperimental) + /** 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 (!settings.Xexperimental) NoSymbol else findSam(tp) - - def findSam(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) + def samOf(tp: Type): Symbol = if (!doSam) NoSymbol else { + // look at erased type because we (only) care about what ends up in bytecode + // (e.g., an alias type or intersection type is fine as long as the intersection dominator compiles to an interface) + val tpSym = erasure.javaErasure(tp).typeSymbol + + if (tpSym.exists && tpSym.isClass + // if tp has a constructor (its class is not a trait), it must be public and must not take any arguments + // (implementation restriction: implicit argument lists are excluded to simplify type inference in adaptToSAM) + && { val ctor = tpSym.primaryConstructor + !ctor.exists || (!ctor.isOverloaded && ctor.isPublic && ctor.info.params.isEmpty && ctor.info.paramSectionCount <= 1)} + // we won't be able to create an instance of tp if it doesn't correspond to its self type + // (checking conformance gets complicated when tp is not fully defined, so let's just rule out self types entirely) + && !tpSym.hasSelfType + ) { - 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 } @@ -1538,7 +1567,6 @@ trait Definitions extends api.StandardDefinitions { private lazy val PolySigMethods: Set[Symbol] = Set[Symbol](MethodHandle.info.decl(sn.Invoke), MethodHandle.info.decl(sn.InvokeExact)).filter(_.exists) lazy val Scala_Java8_CompatPackage = rootMirror.getPackageIfDefined("scala.runtime.java8") - lazy val Scala_Java8_CompatPackage_JFunction = (0 to MaxFunctionArity).toArray map (i => getMemberIfDefined(Scala_Java8_CompatPackage.moduleClass, TypeName("JFunction" + i))) } } } diff --git a/src/reflect/scala/reflect/internal/StdAttachments.scala b/src/reflect/scala/reflect/internal/StdAttachments.scala index 8358c1295c..0243dd48d2 100644 --- a/src/reflect/scala/reflect/internal/StdAttachments.scala +++ b/src/reflect/scala/reflect/internal/StdAttachments.scala @@ -38,6 +38,19 @@ trait StdAttachments { */ case class CompoundTypeTreeOriginalAttachment(parents: List[Tree], stats: List[Tree]) + /** Attached to a Function node during type checking when the expected type is a SAM type (and not a built-in FunctionN). + * + * Ideally, we'd move to Dotty's Closure AST, which tracks the environment, + * the lifted method that has the implementation, and the target type. + * For backwards compatibility, an attachment is the best we can do right now. + * + * @param samTp the expected type that triggered sam conversion (may be a subtype of the type corresponding to sam's owner) + * @param sam the single abstract method implemented by the Function we're attaching this to + * + * @since 2.12.0-M4 + */ + case class SAMFunction(samTp: Type, sam: Symbol) extends PlainAttachment + /** When present, indicates that the host `Ident` has been created from a backquoted identifier. */ case object BackquotedIdentifierAttachment extends PlainAttachment diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 34f9417e57..ee763df849 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -2000,7 +2000,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => */ def thisSym: Symbol = this - def hasSelfType = thisSym.tpeHK != this.tpeHK + def hasSelfType = (thisSym ne this) && (typeOfThis.typeConstructor ne typeConstructor) /** The type of `this` in a class, or else the type of the symbol itself. */ def typeOfThis = thisSym.tpe_* @@ -3642,7 +3642,10 @@ trait Symbols extends api.Symbols { self: SymbolTable => assert((prev eq null) || phaseId(validFrom) > phaseId(prev.validFrom), this) assert(validFrom != NoPeriod, this) - private def phaseString = "%s: %s".format(phaseOf(validFrom), info) + private def phaseString = { + val phase = phaseOf(validFrom) + s"$phase: ${exitingPhase(phase)(info.toString)}" + } override def toString = toList reverseMap (_.phaseString) mkString ", " def toList: List[TypeHistory] = this :: ( if (prev eq null) Nil else prev.toList ) diff --git a/src/reflect/scala/reflect/internal/TreeGen.scala b/src/reflect/scala/reflect/internal/TreeGen.scala index ec6426558c..c5038fd1bb 100644 --- a/src/reflect/scala/reflect/internal/TreeGen.scala +++ b/src/reflect/scala/reflect/internal/TreeGen.scala @@ -310,13 +310,16 @@ abstract class TreeGen { /** Builds a tuple */ def mkTuple(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { case Nil => - Literal(Constant(())) + mkLiteralUnit case tree :: Nil if flattenUnary => tree case _ => Apply(scalaDot(TupleClass(elems.length).name.toTermName), elems) } + def mkLiteralUnit: Literal = Literal(Constant(())) + def mkUnitBlock(expr: Tree): Block = Block(List(expr), mkLiteralUnit) + def mkTupleType(elems: List[Tree], flattenUnary: Boolean = true): Tree = elems match { case Nil => scalaDot(tpnme.Unit) @@ -395,7 +398,7 @@ abstract class TreeGen { if (body forall treeInfo.isInterfaceMember) None else Some( atPos(wrappingPos(superPos, lvdefs)) ( - DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, Literal(Constant(())))))) + DefDef(NoMods, nme.MIXIN_CONSTRUCTOR, Nil, ListOfNil, TypeTree(), Block(lvdefs, mkLiteralUnit)))) } else { // convert (implicit ... ) to ()(implicit ... ) if it's the only parameter section @@ -409,7 +412,7 @@ abstract class TreeGen { // therefore here we emit a dummy which gets populated when the template is named and typechecked Some( atPos(wrappingPos(superPos, lvdefs ::: vparamss1.flatten).makeTransparent) ( - DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), Literal(Constant(())))))) + DefDef(constrMods, nme.CONSTRUCTOR, List(), vparamss1, TypeTree(), Block(lvdefs ::: List(superCall), mkLiteralUnit)))) } } constr foreach (ensureNonOverlapping(_, parents ::: gvdefs, focus = false)) @@ -481,7 +484,7 @@ abstract class TreeGen { * written by end user. It's important to distinguish the two so that * quasiquotes can strip synthetic ones away. */ - def mkSyntheticUnit() = Literal(Constant(())).updateAttachment(SyntheticUnitAttachment) + def mkSyntheticUnit() = mkLiteralUnit.updateAttachment(SyntheticUnitAttachment) /** Create block of statements `stats` */ def mkBlock(stats: List[Tree], doFlatten: Boolean = true): Tree = diff --git a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala index 38893d8db3..e75b3dff3d 100644 --- a/src/reflect/scala/reflect/internal/settings/MutableSettings.scala +++ b/src/reflect/scala/reflect/internal/settings/MutableSettings.scala @@ -58,6 +58,7 @@ abstract class MutableSettings extends AbsSettings { def maxClassfileName: IntSetting def isScala211: Boolean + def isScala212: Boolean } object MutableSettings { diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 32af6529ca..c069e2c198 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -112,8 +112,9 @@ trait Erasure { protected def eraseDerivedValueClassRef(tref: TypeRef): Type = erasedValueClassArg(tref) def apply(tp: Type): Type = tp match { - case ConstantType(_) => - tp + case ConstantType(ct) => + if (ct.tag == ClazzTag) ConstantType(Constant(apply(ct.typeValue))) + else tp case st: ThisType if st.sym.isPackageClass => tp case st: SubType => diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 49799136de..bdcfcabdd5 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -1161,6 +1161,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive propagatePackageBoundary(jmeth.javaFlags, meth) copyAnnotations(meth, jmeth) if (jmeth.javaFlags.isVarargs) meth modifyInfo arrayToRepeated + if (jmeth.getDefaultValue != null) meth.addAnnotation(AnnotationDefaultAttr) markAllCompleted(meth) meth } diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index 13874916cc..4630597668 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -37,6 +37,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => this.FixedMirrorTreeCreator this.FixedMirrorTypeCreator this.CompoundTypeTreeOriginalAttachment + this.SAMFunction this.BackquotedIdentifierAttachment this.ForAttachment this.SyntheticUnitAttachment diff --git a/src/reflect/scala/reflect/runtime/Settings.scala b/src/reflect/scala/reflect/runtime/Settings.scala index 27d574b1de..b1d7fde1b4 100644 --- a/src/reflect/scala/reflect/runtime/Settings.scala +++ b/src/reflect/scala/reflect/runtime/Settings.scala @@ -51,4 +51,5 @@ private[reflect] class Settings extends MutableSettings { val Yrecursion = new IntSetting(0) val maxClassfileName = new IntSetting(255) def isScala211 = true + def isScala212 = true } diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 830d902b68..e67a717257 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -236,7 +236,7 @@ trait ModelFactoryImplicitSupport { try { // TODO: Not sure if `owner = sym.owner` is the right thing to do -- seems similar to what scalac should be doing val silentContext = context.make(owner = sym.owner).makeSilent(reportAmbiguousErrors = false) - val search = inferImplicit(EmptyTree, tpe, false, false, silentContext, false) + val search = inferImplicitByTypeSilent(tpe, silentContext) available = Some(search.tree != EmptyTree) } catch { case _: TypeError => diff --git a/test/files/neg/logImplicits.check b/test/files/neg/logImplicits.check index df7b359767..479bf4ba2c 100644 --- a/test/files/neg/logImplicits.check +++ b/test/files/neg/logImplicits.check @@ -4,7 +4,7 @@ logImplicits.scala:2: applied implicit conversion from xs.type to ?{def size: ?} logImplicits.scala:7: applied implicit conversion from String("abc") to ?{def map: ?} = implicit def augmentString(x: String): scala.collection.immutable.StringOps def f = "abc" map (_ + 1) ^ -logImplicits.scala:15: inferred view from String("abc") to Int = C.this.convert:(p: String)Int +logImplicits.scala:15: inferred view from String("abc") to Int via C.this.convert: (p: String)Int math.max(122, x: Int) ^ logImplicits.scala:19: applied implicit conversion from Int(1) to ?{def ->: ?} = implicit def ArrowAssoc[A](self: A): ArrowAssoc[A] diff --git a/test/files/neg/names-defaults-neg.check b/test/files/neg/names-defaults-neg.check index 8a6aafd67a..875bc2ade0 100644 --- a/test/files/neg/names-defaults-neg.check +++ b/test/files/neg/names-defaults-neg.check @@ -130,7 +130,7 @@ names-defaults-neg.scala:102: error: unknown parameter name: m names-defaults-neg.scala:135: error: reference to var2 is ambiguous; it is both a method parameter and a variable in scope. delay(var2 = 40) ^ -names-defaults-neg.scala:138: error: missing parameter type for expanded function ((x$1) => a = x$1) +names-defaults-neg.scala:138: error: missing parameter type for expanded function ((x$1: <error>) => a = x$1) val taf2: Int => Unit = testAnnFun(a = _, b = get("+")) ^ names-defaults-neg.scala:138: error: not found: value a @@ -142,10 +142,10 @@ names-defaults-neg.scala:138: error: not found: value get names-defaults-neg.scala:139: error: parameter 'a' is already specified at parameter position 1 val taf3 = testAnnFun(b = _: String, a = get(8)) ^ -names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$3) => testAnnFun(x$3, ((x$4) => b = x$4))) +names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$3: <error>) => testAnnFun(x$3, ((x$4) => b = x$4))) val taf4: (Int, String) => Unit = testAnnFun(_, b = _) ^ -names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$4) => b = x$4) +names-defaults-neg.scala:140: error: missing parameter type for expanded function ((x$4: <error>) => b = x$4) val taf4: (Int, String) => Unit = testAnnFun(_, b = _) ^ names-defaults-neg.scala:140: error: not found: value b diff --git a/test/files/neg/sammy_disabled.check b/test/files/neg/sammy_disabled.check new file mode 100644 index 0000000000..66db9dd5f2 --- /dev/null +++ b/test/files/neg/sammy_disabled.check @@ -0,0 +1,4 @@ +sammy_disabled.scala:3: error: missing parameter type +class C { val f: F = x => "a" } + ^ +one error found diff --git a/test/files/neg/sammy_disabled.flags b/test/files/neg/sammy_disabled.flags new file mode 100644 index 0000000000..cf42e9f940 --- /dev/null +++ b/test/files/neg/sammy_disabled.flags @@ -0,0 +1 @@ +-Xsource:2.11 diff --git a/test/files/neg/sammy_disabled.scala b/test/files/neg/sammy_disabled.scala new file mode 100644 index 0000000000..12000a3e12 --- /dev/null +++ b/test/files/neg/sammy_disabled.scala @@ -0,0 +1,3 @@ +trait F { def apply(x: Int): String } + +class C { val f: F = x => "a" } diff --git a/test/files/neg/sammy_error.check b/test/files/neg/sammy_error.check new file mode 100644 index 0000000000..f14ac7e3a2 --- /dev/null +++ b/test/files/neg/sammy_error.check @@ -0,0 +1,4 @@ +sammy_error.scala:6: error: missing parameter type + foo(x => x) // should result in only one error (the second one stemmed from adapting to SAM when the tree was erroneous) + ^ +one error found diff --git a/test/files/neg/sammy_error.scala b/test/files/neg/sammy_error.scala new file mode 100644 index 0000000000..dbddebf325 --- /dev/null +++ b/test/files/neg/sammy_error.scala @@ -0,0 +1,7 @@ +trait F1[A, B] { def apply(a: A): B } + +class Test { + def foo[A](f1: F1[A, A]) = f1 + + foo(x => x) // should result in only one error (the second one stemmed from adapting to SAM when the tree was erroneous) +} diff --git a/test/files/neg/sammy_error_exist_no_crash.check b/test/files/neg/sammy_error_exist_no_crash.check index a0d2237ce0..944b6471fd 100644 --- a/test/files/neg/sammy_error_exist_no_crash.check +++ b/test/files/neg/sammy_error_exist_no_crash.check @@ -1,6 +1,4 @@ -sammy_error_exist_no_crash.scala:5: error: Could not derive subclass of F[? >: String] - (with SAM `def method apply(s: String)Int`) - based on: ((x$1: String) => x$1.<parseInt: error>). +sammy_error_exist_no_crash.scala:5: error: value parseInt is not a member of String bar(_.parseInt) ^ one error found diff --git a/test/files/neg/sammy_error_exist_no_crash.flags b/test/files/neg/sammy_error_exist_no_crash.flags deleted file mode 100644 index e1b37447c9..0000000000 --- a/test/files/neg/sammy_error_exist_no_crash.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental
\ No newline at end of file diff --git a/test/files/neg/sammy_error_exist_no_crash.scala b/test/files/neg/sammy_error_exist_no_crash.scala index da7e47206f..667b4db763 100644 --- a/test/files/neg/sammy_error_exist_no_crash.scala +++ b/test/files/neg/sammy_error_exist_no_crash.scala @@ -1,6 +1,6 @@ -abstract class F[T] { def apply(s: T): Int } +trait F[T] { def apply(s: T): Int } object NeedsNiceError { def bar(x: F[_ >: String]) = ??? bar(_.parseInt) -}
\ No newline at end of file +} diff --git a/test/files/neg/sammy_expected.check b/test/files/neg/sammy_expected.check new file mode 100644 index 0000000000..3b76aabdd2 --- /dev/null +++ b/test/files/neg/sammy_expected.check @@ -0,0 +1,6 @@ +sammy_expected.scala:4: error: type mismatch; + found : String => Int + required: F[Object,Int] + def wrong: F[Object, Int] = (x: String) => 1 + ^ +one error found diff --git a/test/files/neg/sammy_expected.scala b/test/files/neg/sammy_expected.scala new file mode 100644 index 0000000000..8fc1f66ff7 --- /dev/null +++ b/test/files/neg/sammy_expected.scala @@ -0,0 +1,5 @@ +trait F[A, B] { def apply(x: A): B } + +class MustMeetExpected { + def wrong: F[Object, Int] = (x: String) => 1 +}
\ No newline at end of file diff --git a/test/files/neg/sammy_overload.check b/test/files/neg/sammy_overload.check new file mode 100644 index 0000000000..903d7c88f4 --- /dev/null +++ b/test/files/neg/sammy_overload.check @@ -0,0 +1,7 @@ +sammy_overload.scala:11: error: missing parameter type for expanded function ((x$1: <error>) => x$1.toString) + O.m(_.toString) // error expected: eta-conversion breaks down due to overloading + ^ +sammy_overload.scala:12: error: missing parameter type + O.m(x => x) // error expected: needs param type + ^ +two errors found diff --git a/test/files/neg/sammy_overload.scala b/test/files/neg/sammy_overload.scala new file mode 100644 index 0000000000..91c52cf96c --- /dev/null +++ b/test/files/neg/sammy_overload.scala @@ -0,0 +1,13 @@ +trait ToString { def convert(x: Int): String } + +class ExplicitSamType { + object O { + def m(x: Int => String): Int = 0 + def m(x: ToString): Int = 1 + } + + O.m((x: Int) => x.toString) // ok, function type takes precedence + + O.m(_.toString) // error expected: eta-conversion breaks down due to overloading + O.m(x => x) // error expected: needs param type +} diff --git a/test/files/neg/sammy_restrictions.check b/test/files/neg/sammy_restrictions.check index 8cc49f9aa9..09579cbe21 100644 --- a/test/files/neg/sammy_restrictions.check +++ b/test/files/neg/sammy_restrictions.check @@ -1,26 +1,18 @@ -sammy_restrictions.scala:31: error: type mismatch; +sammy_restrictions.scala:35: error: type mismatch; found : () => Int required: NoAbstract (() => 0) : NoAbstract ^ -sammy_restrictions.scala:32: error: type mismatch; +sammy_restrictions.scala:36: error: type mismatch; found : Int => Int required: TwoAbstract ((x: Int) => 0): TwoAbstract ^ -sammy_restrictions.scala:34: error: class type required but DerivedOneAbstract with OneAbstract found - ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here. - ^ -sammy_restrictions.scala:35: error: type mismatch; +sammy_restrictions.scala:37: error: type mismatch; found : Int => Int required: NoEmptyConstructor ((x: Int) => 0): NoEmptyConstructor ^ -sammy_restrictions.scala:37: error: type mismatch; - found : Int => Int - required: OneEmptySecondaryConstructor - ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. - ^ sammy_restrictions.scala:38: error: type mismatch; found : Int => Int required: MultipleConstructorLists @@ -28,22 +20,32 @@ sammy_restrictions.scala:38: error: type mismatch; ^ sammy_restrictions.scala:39: error: type mismatch; found : Int => Int + required: OneEmptySecondaryConstructor + ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. + ^ +sammy_restrictions.scala:40: error: type mismatch; + found : Int => Int required: MultipleMethodLists ((x: Int) => 0): MultipleMethodLists ^ -sammy_restrictions.scala:40: error: type mismatch; +sammy_restrictions.scala:41: error: type mismatch; found : Int => Int required: ImplicitConstructorParam ((x: Int) => 0): ImplicitConstructorParam ^ -sammy_restrictions.scala:41: error: type mismatch; +sammy_restrictions.scala:42: error: type mismatch; found : Int => Int required: ImplicitMethodParam ((x: Int) => 0): ImplicitMethodParam ^ -sammy_restrictions.scala:44: error: type mismatch; +sammy_restrictions.scala:43: error: type mismatch; found : Int => Int required: PolyMethod ((x: Int) => 0): PolyMethod ^ +sammy_restrictions.scala:44: error: type mismatch; + found : Int => Int + required: SelfTp + ((x: Int) => 0): SelfTp + ^ 10 errors found diff --git a/test/files/neg/sammy_restrictions.flags b/test/files/neg/sammy_restrictions.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/neg/sammy_restrictions.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/neg/sammy_restrictions.scala b/test/files/neg/sammy_restrictions.scala index d003cfaf36..ff2c16b679 100644 --- a/test/files/neg/sammy_restrictions.scala +++ b/test/files/neg/sammy_restrictions.scala @@ -24,22 +24,29 @@ abstract class PolyMethod { def ap[T](a: T): T } abstract class OneAbstract { def ap(a: Int): Any } abstract class DerivedOneAbstract extends OneAbstract +abstract class SelfTp { self: NoAbstract => def ap(a: Int): Any } +abstract class SelfVar { self => def ap(a: Int): Any } + object Test { implicit val s: String = "" type NonClassType = DerivedOneAbstract with OneAbstract + // errors: (() => 0) : NoAbstract ((x: Int) => 0): TwoAbstract - ((x: Int) => 0): DerivedOneAbstract // okay - ((x: Int) => 0): NonClassType // "class type required". I think we should avoid SAM translation here. ((x: Int) => 0): NoEmptyConstructor - ((x: Int) => 0): OneEmptyConstructor // okay - ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. ((x: Int) => 0): MultipleConstructorLists + ((x: Int) => 0): OneEmptySecondaryConstructor // derived class must have an empty *primary* to call. ((x: Int) => 0): MultipleMethodLists ((x: Int) => 0): ImplicitConstructorParam ((x: Int) => 0): ImplicitMethodParam - - ((x: Int) => 0): PolyClass[Int] // okay ((x: Int) => 0): PolyMethod + ((x: Int) => 0): SelfTp + + // allowed: + ((x: Int) => 0): OneEmptyConstructor + ((x: Int) => 0): DerivedOneAbstract + ((x: Int) => 0): NonClassType // we also allow type aliases in instantiation expressions, if they resolve to a class type + ((x: Int) => 0): PolyClass[Int] + ((x: Int) => 0): SelfVar } diff --git a/test/files/neg/sammy_wrong_arity.flags b/test/files/neg/sammy_wrong_arity.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/neg/sammy_wrong_arity.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/neg/t5761.check b/test/files/neg/t5761.check index 2d66af26f6..15c0bc7634 100644 --- a/test/files/neg/t5761.check +++ b/test/files/neg/t5761.check @@ -13,7 +13,7 @@ Unspecified value parameter x. t5761.scala:13: error: not found: type Tread new Tread("sth") { }.run() ^ -t5761.scala:13: error: value run is not a member of AnyRef +t5761.scala:13: error: value run is not a member of <error> new Tread("sth") { }.run() ^ 5 errors found diff --git a/test/files/pos/fun_undo_eta.scala b/test/files/pos/fun_undo_eta.scala new file mode 100644 index 0000000000..466b0e2629 --- /dev/null +++ b/test/files/pos/fun_undo_eta.scala @@ -0,0 +1,10 @@ +class Test { + def m(i: Int) = i + + def expectWild[A](f: A) = ??? + def expectFun[A](f: A => Int) = ??? + + expectWild((i => m(i))) // manual eta expansion + expectWild(m(_)) // have to undo eta expansion with wildcard expected type + expectFun(m(_)) // have to undo eta expansion with function expected type +} diff --git a/test/files/pos/sammy_ctor_arg.scala b/test/files/pos/sammy_ctor_arg.scala new file mode 100644 index 0000000000..3c556d59f0 --- /dev/null +++ b/test/files/pos/sammy_ctor_arg.scala @@ -0,0 +1,4 @@ +trait Fun[A, B] { def apply(a: A): B } +// can't do sam expansion until the sam body def is a static method in the sam class, and not a local method in a block' +class C(f: Fun[Int, String]) +class Test extends C(s => "a")
\ No newline at end of file diff --git a/test/files/pos/sammy_exist.flags b/test/files/pos/sammy_exist.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_exist.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_implicit.scala b/test/files/pos/sammy_implicit.scala new file mode 100644 index 0000000000..ab63fc729e --- /dev/null +++ b/test/files/pos/sammy_implicit.scala @@ -0,0 +1,11 @@ +trait Fun[A, B] { def apply(a: A): B } + +abstract class SamImplicitConvert { + class Lst[T] + abstract class Str { def getBytes: Array[Int] } + def flatMap[B](f: Fun[Str, Lst[B]]): List[B] = ??? + + implicit def conv(xs: Array[Int]): Lst[Int] + + def encoded = flatMap (_.getBytes) +} diff --git a/test/files/pos/sammy_infer_argtype_subtypes.scala b/test/files/pos/sammy_infer_argtype_subtypes.scala new file mode 100644 index 0000000000..63966f879e --- /dev/null +++ b/test/files/pos/sammy_infer_argtype_subtypes.scala @@ -0,0 +1,6 @@ +trait Fun[A, B] { def apply(a: A): B } + +class SamInferResult { + def foreach[U](f: Fun[String, U]): U = ??? + def foo = foreach(println) +}
\ No newline at end of file diff --git a/test/files/pos/sammy_inferargs.scala b/test/files/pos/sammy_inferargs.scala new file mode 100644 index 0000000000..10d9b4f0dd --- /dev/null +++ b/test/files/pos/sammy_inferargs.scala @@ -0,0 +1,6 @@ +trait Proc { def apply(): Unit } +class Test { + val initCode = List[Proc]() + initCode foreach { proc => proc() } + +} diff --git a/test/files/pos/sammy_overload.flags b/test/files/pos/sammy_overload.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_overload.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_overload.scala b/test/files/pos/sammy_overload.scala index 5472248f4d..6a3c88ec55 100644 --- a/test/files/pos/sammy_overload.scala +++ b/test/files/pos/sammy_overload.scala @@ -6,4 +6,29 @@ object Test { def foo(x: String): Unit = ??? def foo(): Unit = ??? val f: Consumer[_ >: String] = foo -}
\ No newline at end of file +} + +trait A[A, B] { def apply(a: A): B } + +class ArityDisambiguate { + object O { + def m(a: A[Int, Int]) = 0 + def m(f: (Int, Int) => Int) = 1 + } + + O.m(x => x) // ok + O.m((x, y) => x) // ok +} + +class InteractionWithImplicits { + object O { + class Ev + implicit object E1 extends Ev + implicit object E2 extends Ev + def m(a: A[Int, Int])(implicit ol: E1.type) = 0 + def m(a: A[String, Int])(implicit ol: E2.type) = 1 + } + + O.m((x: Int) => 1) // ok + O.m((x: String) => 1) // ok +} diff --git a/test/files/pos/sammy_override.flags b/test/files/pos/sammy_override.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_override.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_poly.flags b/test/files/pos/sammy_poly.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_poly.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_poly.scala b/test/files/pos/sammy_poly.scala index c629be7166..ba10baea49 100644 --- a/test/files/pos/sammy_poly.scala +++ b/test/files/pos/sammy_poly.scala @@ -1,7 +1,12 @@ // 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' +trait F[T, R]{ def apply(x: T): R } + +class PolySammy { + (x => x + 1): F[Int, Int] + ((x: Int) => x + 1): F[Int, Int] + ((x: String) => 1): F[String, Int] + ((x: Object) => 1): F[String, Int] + 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 deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_scope.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_scope.scala b/test/files/pos/sammy_scope.scala index 8f1fe7058e..9d35501a47 100644 --- a/test/files/pos/sammy_scope.scala +++ b/test/files/pos/sammy_scope.scala @@ -1,8 +1,8 @@ // test synthesizeSAMFunction: scope hygiene -abstract class SamFun[T1, R] { self => +trait 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_single.flags b/test/files/pos/sammy_single.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_single.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/sammy_twice.flags b/test/files/pos/sammy_twice.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/sammy_twice.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/t8310.flags b/test/files/pos/t8310.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/pos/t8310.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/pos/t8429.scala b/test/files/pos/t8429.scala new file mode 100644 index 0000000000..a2d32637e1 --- /dev/null +++ b/test/files/pos/t8429.scala @@ -0,0 +1,7 @@ +trait Must { def musta(str: String, i: Int): Unit } + +object Mustare { + def takesM(m: Must) = ??? + takesM{ (a, b) => println } // ok + takesM{ case (a: String, b: Int) => println("") } // should also be accepted +} diff --git a/test/files/pos/t9449.scala b/test/files/pos/t9449.scala new file mode 100644 index 0000000000..3b86dc80a0 --- /dev/null +++ b/test/files/pos/t9449.scala @@ -0,0 +1,19 @@ +trait II { + def apply(x: Int): Int +} + +object Test { + def ii(x: Int): Int = x + def test = { + val ii1: II = x => ii(x) // works + val ii2: II = ii // works (adapting `ii` to `II`) + val ii3: II = ii _ // works (failed before the fix) + // typedTyped({ii : (() => <empty>)}) + // typedEta(ii, pt = II) + // adapt(ii, pt = (? => ?)) + // instantiatedToMethodType(ii, pt = (? => ?)) + // val ii3: II = ii _ // error: + // found : Int => Int + // required: II + } +}
\ No newline at end of file diff --git a/test/files/run/indylambda-boxing/test.scala b/test/files/run/indylambda-boxing/test.scala index cc0a460640..82f8d2f497 100644 --- a/test/files/run/indylambda-boxing/test.scala +++ b/test/files/run/indylambda-boxing/test.scala @@ -2,15 +2,16 @@ class Capture class Test { def test1 = (i: Int) => "" def test2 = (i: VC) => i - def test3 = (i: Int) => i + def test3 = (i: Int) => i // not adapted, specialized - def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} } + def test4 = {val c = new Capture; (i: Int) => {(c, Test.this.toString); 42} } // not adapted, specialized def test5 = {val c = new Capture; (i: VC) => (c, Test.this.toString) } def test6 = {val c = new Capture; (i: Int) => (c, Test.this.toString) } def test7 = {val vc = new Capture; (i: Int) => vc } - def test8 = {val c = 42; (s: String) => (s, c)} + def test8 = {val c = 42; (s: String) => (s, c)} // not adapted def test9 = {val c = 42; (s: String) => ()} + def test10 = {(s: List[String]) => ()} } object Test { diff --git a/test/files/run/lambda-serialization.scala b/test/files/run/lambda-serialization.scala index 46b26d7c5e..0eee1193d7 100644 --- a/test/files/run/lambda-serialization.scala +++ b/test/files/run/lambda-serialization.scala @@ -1,8 +1,11 @@ import java.io.{ByteArrayInputStream, ObjectInputStream, ObjectOutputStream, ByteArrayOutputStream} +trait IntToString { def apply(i: Int): String } + object Test { def main(args: Array[String]): Unit = { - roundTrip + roundTrip() + roundTripIndySam() } def roundTrip(): Unit = { @@ -22,6 +25,15 @@ object Test { assert(serializeDeserialize(serializeDeserialize(specializedLambda)).apply(42) == 2) } + // lambda targeting a SAM, not a FunctionN (should behave the same way) + def roundTripIndySam(): Unit = { + val lambda: IntToString = (x: Int) => "yo!" * x + val reconstituted1 = serializeDeserialize(lambda).asInstanceOf[IntToString] + val reconstituted2 = serializeDeserialize(reconstituted1).asInstanceOf[IntToString] + assert(reconstituted1.apply(2) == "yo!yo!") + assert(reconstituted1.getClass == reconstituted2.getClass) + } + def serializeDeserialize[T <: AnyRef](obj: T) = { val buffer = new ByteArrayOutputStream val out = new ObjectOutputStream(buffer) diff --git a/test/files/run/reflection-mem-typecheck.scala b/test/files/run/reflection-mem-typecheck.scala index e3cabf689d..93ec1c937a 100644 --- a/test/files/run/reflection-mem-typecheck.scala +++ b/test/files/run/reflection-mem-typecheck.scala @@ -11,7 +11,9 @@ object Test extends MemoryTest { cm.mkToolBox() } - override def maxDelta = 10 + // I'm not sure this is a great way to test for memory leaks, + // since we're also testing how good the JVM's GC is, and this is not easily reproduced between machines/over time + override def maxDelta = 12 override def calcsPerIter = 8 override def calc() { var snippet = """ diff --git a/test/files/run/sammy_after_implicit_view.scala b/test/files/run/sammy_after_implicit_view.scala new file mode 100644 index 0000000000..a13a71e562 --- /dev/null +++ b/test/files/run/sammy_after_implicit_view.scala @@ -0,0 +1,28 @@ +trait MySam { def apply(x: Int): String } + +// check that SAM conversion happens after implicit view application +object Test extends App { + final val AnonFunClass = "$anon$" + final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this + + // if there's an implicit conversion, it does not takes precedence (because that's what dotty does) + def implicitSam() = { + import language.implicitConversions + var ok = true + implicit def fun2sam(fun: Int => String): MySam = { ok = false; new MySam { def apply(x: Int) = fun(x) } } + val className = (((x: Int) => x.toString): MySam).getClass.toString + assert(ok, "implicit conversion not called") + assert(!(className contains AnonFunClass), className) + assert(className contains LMFClass, className) + } + + // indirectly check that this sam type instance was created from a class spun up by LambdaMetaFactory + def justSammy() = { + val className = (((x: Int) => x.toString): MySam).getClass.toString + assert(!(className contains AnonFunClass), className) + assert(className contains LMFClass, className) + } + + implicitSam() + justSammy() +} diff --git a/test/files/run/sammy_cbn.scala b/test/files/run/sammy_cbn.scala new file mode 100644 index 0000000000..b84b2fd8e5 --- /dev/null +++ b/test/files/run/sammy_cbn.scala @@ -0,0 +1,9 @@ +trait F0[T] { def apply(): T } + +object Test extends App { + def delay[T](v: => T) = (v _): F0[T] + + // should not fail with ClassCastException: $$Lambda$6279/897871870 cannot be cast to F0 + // (also, should not say boe!) + delay(println("boe!")) +} diff --git a/test/files/run/sammy_erasure_cce.scala b/test/files/run/sammy_erasure_cce.scala new file mode 100644 index 0000000000..fb973befe4 --- /dev/null +++ b/test/files/run/sammy_erasure_cce.scala @@ -0,0 +1,22 @@ +trait F1 { + def apply(a: List[String]): String + def f1 = "f1" +} + +object Test extends App { + // Wrap the sam-targeting function in a context where the expected type is erased (identity's argument type erases to Object), + // so that Erasure can't tell that the types actually conform by looking only + // at an un-adorned Function tree and the expected type + // (because a function type needs no cast it the expected type is a SAM type), + // + // A correct implementation of Typers/Erasure tracks a Function's SAM target type directly + // (currently using an attachment for backwards compat), + // and not in the expected type (which was the case in my first attempt), + // as the expected type may lose its SAM status due to erasure. + // (In a sense, this need not be so, but erasure drops type parameters, + // so that identity's F1 type argument cannot be propagated to its argument type.) + def foo = identity[F1]((as: List[String]) => as.head) + + // check that this doesn't CCE's + foo.f1 +} diff --git a/test/files/run/sammy_java8.flags b/test/files/run/sammy_java8.flags deleted file mode 100644 index 48fd867160..0000000000 --- a/test/files/run/sammy_java8.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental diff --git a/test/files/run/sammy_repeated.flags b/test/files/run/sammy_repeated.flags deleted file mode 100644 index e1b37447c9..0000000000 --- a/test/files/run/sammy_repeated.flags +++ /dev/null @@ -1 +0,0 @@ --Xexperimental
\ No newline at end of file diff --git a/test/files/run/sammy_repeated.scala b/test/files/run/sammy_repeated.scala deleted file mode 100644 index c24dc41909..0000000000 --- a/test/files/run/sammy_repeated.scala +++ /dev/null @@ -1,8 +0,0 @@ -trait RepeatedSink { def accept(a: Any*): Unit } - -object Test { - def main(args: Array[String]): Unit = { - val f: RepeatedSink = (a) => println(a) - f.accept(1) - } -}
\ No newline at end of file diff --git a/test/files/run/sammy_restrictions_LMF.check b/test/files/run/sammy_restrictions_LMF.check new file mode 100644 index 0000000000..6ed281c757 --- /dev/null +++ b/test/files/run/sammy_restrictions_LMF.check @@ -0,0 +1,2 @@ +1 +1 diff --git a/test/files/run/sammy_restrictions_LMF.scala b/test/files/run/sammy_restrictions_LMF.scala new file mode 100644 index 0000000000..aa49e14113 --- /dev/null +++ b/test/files/run/sammy_restrictions_LMF.scala @@ -0,0 +1,57 @@ +trait T[@specialized A] { def apply(a: A): A } +trait TInt extends T[Int] + +trait TWithVal { val x: Any = 1; def apply(x: Int): String } + +trait TImpure { def apply(x: Int): String ; println(1) } + +trait Println { println(1) } +trait TImpureSuper extends Println { def apply(x: Int): String } + +class C +trait A extends C +trait B extends A +trait TClassParent extends B { def apply(x: Int): String } + +object Test extends App { + final val AnonFunClass = "$anonfun$" + final val LMFClass = "$$Lambda$" // LambdaMetaFactory names classes like this + + private def LMF(f: Any): Unit = { + val className = f.getClass.toString + assert(!(className contains AnonFunClass), className) + assert((className contains LMFClass), className) + } + + private def notLMF(f: Any): Unit = { + val className = f.getClass.toString + assert((className contains AnonFunClass), className) + assert(!(className contains LMFClass), className) + } + + // Check that we expand the SAM of a type that is specialized. + // This is an implementation restriction -- the current specialization scheme is not + // amenable to using LambdaMetaFactory to spin up subclasses. + // Since the generic method is abstract, and the specialized ones are concrete, + // specialization is rendered moot because we cannot implement the specialized method + // with the lambda using LMF. + + // not LMF if specialized at this type + notLMF((x => x): T[Int]) + // not LMF if specialized at this type (via subclass) + notLMF((x => x): TInt) + // LMF ok if not specialized at this type + LMF((x => x): T[String]) + + // traits with a val member also cannot be instantiated by LMF + val fVal: TWithVal = (x => "a") + notLMF(fVal) + assert(fVal.x == 1) + + notLMF((x => "a"): TImpure) + notLMF((x => "a"): TImpureSuper) + + val fClassParent: TClassParent = x => "a" + notLMF(fClassParent) + assert(fClassParent(1) == "a") +} diff --git a/test/files/run/sammy_return.scala b/test/files/run/sammy_return.scala new file mode 100644 index 0000000000..e959619dd1 --- /dev/null +++ b/test/files/run/sammy_return.scala @@ -0,0 +1,14 @@ +trait Fun[A, B] { def apply(a: A): B } +class PF[A, B] { def runWith[U](action: Fun[B, U]): Fun[A, Boolean] = a => {action(a.asInstanceOf[B]); true} } + +class TO[A](x: A) { + def foreach[U](f: Fun[A, U]): U = f(x) + def collectFirst[B](pf: PF[A, B]): Option[B] = { + foreach(pf.runWith(b => return Some(b))) + None + } +} + +object Test extends App { + assert(new TO("a").collectFirst(new PF[String, String]).get == "a") +}
\ No newline at end of file diff --git a/test/files/run/sammy_repeated.check b/test/files/run/sammy_vararg_cbn.check index 1cff0f067c..1cff0f067c 100644 --- a/test/files/run/sammy_repeated.check +++ b/test/files/run/sammy_vararg_cbn.check diff --git a/test/files/run/sammy_vararg_cbn.scala b/test/files/run/sammy_vararg_cbn.scala new file mode 100644 index 0000000000..e5b49498ea --- /dev/null +++ b/test/files/run/sammy_vararg_cbn.scala @@ -0,0 +1,12 @@ +trait SamRepeated { def accept(a: Any*): Unit } +trait SamByName { def accept(a: => Any): (Any, Any) } + +object Test extends App { + val rep: SamRepeated = (a) => println(a) + rep.accept(1) + + val nam: SamByName = (a) => (a, a) + var v = 0 + assert(nam.accept({v += 1; v}) == (1, 2)) + assert(v == 2, "by name arg should be evaluated twice") +} diff --git a/test/files/run/t8549.scala b/test/files/run/t8549.scala index 233a05dee1..e2d0d335b0 100644 --- a/test/files/run/t8549.scala +++ b/test/files/run/t8549.scala @@ -79,7 +79,7 @@ object Test extends App { } } - // Generated on 20150925-14:41:27 with Scala version 2.12.0-20150924-125956-fd5994f397) + // Generated on 20160328-17:47:35 with Scala version 2.12.0-20160328-174205-d46145c) overwrite.foreach(updateComment) check(Some(1))("rO0ABXNyAApzY2FsYS5Tb21lESLyaV6hi3QCAAFMAAF4dAASTGphdmEvbGFuZy9PYmplY3Q7eHIADHNjYWxhLk9wdGlvbv5pN/3bDmZ0AgAAeHBzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAQ==") @@ -176,7 +176,7 @@ object Test extends App { // check(mutable.ArrayBuffer(1, 2, 3))( "rO0ABXNyACRzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlCdWZmZXIVOLBTg4KOcwIAA0kAC2luaXRpYWxTaXplSQAFc2l6ZTBbAAVhcnJheXQAE1tMamF2YS9sYW5nL09iamVjdDt4cAAAABAAAAADdXIAE1tMamF2YS5sYW5nLk9iamVjdDuQzlifEHMpbAIAAHhwAAAAEHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAFAAAAAnNxAH4ABQAAAANwcHBwcHBwcHBwcHBw") // TODO SI-8576 Uninitialized field under -Xcheckinit // check(mutable.ArraySeq(1, 2, 3))( "rO0ABXNyACFzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlTZXEVPD3SKEkOcwIAAkkABmxlbmd0aFsABWFycmF5dAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAA3VyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAANzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNxAH4ABQAAAAJzcQB+AAUAAAAD") - check(mutable.AnyRefMap("a" -> "A"))( "rO0ABXNyACJzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwAAAAAAAAAAECAAdJAAVfc2l6ZUkAB192YWNhbnRJAARtYXNrTAAMZGVmYXVsdEVudHJ5dAARTHNjYWxhL0Z1bmN0aW9uMTtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfaGFzaGVzdAACW0lbAClzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfa2V5c3QAE1tMamF2YS9sYW5nL09iamVjdDtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfdmFsdWVzcQB+AAN4cAAAAAEAAAAAAAAAB3NyACFqYXZhLmxhbmcuaW52b2tlLlNlcmlhbGl6ZWRMYW1iZGFvYdCULCk2hQIACkkADmltcGxNZXRob2RLaW5kWwAMY2FwdHVyZWRBcmdzcQB+AANMAA5jYXB0dXJpbmdDbGFzc3QAEUxqYXZhL2xhbmcvQ2xhc3M7TAAYZnVuY3Rpb25hbEludGVyZmFjZUNsYXNzdAASTGphdmEvbGFuZy9TdHJpbmc7TAAdZnVuY3Rpb25hbEludGVyZmFjZU1ldGhvZE5hbWVxAH4AB0wAImZ1bmN0aW9uYWxJbnRlcmZhY2VNZXRob2RTaWduYXR1cmVxAH4AB0wACWltcGxDbGFzc3EAfgAHTAAOaW1wbE1ldGhvZE5hbWVxAH4AB0wAE2ltcGxNZXRob2RTaWduYXR1cmVxAH4AB0wAFmluc3RhbnRpYXRlZE1ldGhvZFR5cGVxAH4AB3hwAAAABnVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAB2cgAjc2NhbGEuY29sbGVjdGlvbi5tdXRhYmxlLkFueVJlZk1hcCTiaIeloJGDLgIABUkACUluZGV4TWFza0kACk1pc3NWYWNhbnRJAApNaXNzaW5nQml0SQAJVmFjYW50Qml0TAA0c2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFueVJlZk1hcCQkZXhjZXB0aW9uRGVmYXVsdHEAfgABeHB0AB5zY2FsYS9ydW50aW1lL2phdmE4L0pGdW5jdGlvbjF0AAVhcHBseXQAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7dAAjc2NhbGEvY29sbGVjdGlvbi9tdXRhYmxlL0FueVJlZk1hcCR0AC5zY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCQkYW5vbmZ1biQydAAsKExqYXZhL2xhbmcvT2JqZWN0OylMc2NhbGEvcnVudGltZS9Ob3RoaW5nJDtxAH4AEnVyAAJbSU26YCZ26rKlAgAAeHAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPlJANgAAAAB1cQB+AAkAAAAIcHBwcHBwdAABYXB1cQB+AAkAAAAIcHBwcHBwdAABQXA=") + check(mutable.AnyRefMap("a" -> "A"))( "rO0ABXNyACJzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwAAAAAAAAAAECAAdJAAVfc2l6ZUkAB192YWNhbnRJAARtYXNrTAAMZGVmYXVsdEVudHJ5dAARTHNjYWxhL0Z1bmN0aW9uMTtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfaGFzaGVzdAACW0lbAClzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfa2V5c3QAE1tMamF2YS9sYW5nL09iamVjdDtbACtzY2FsYSRjb2xsZWN0aW9uJG11dGFibGUkQW55UmVmTWFwJCRfdmFsdWVzcQB+AAN4cAAAAAEAAAAAAAAAB3NyADNzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQW55UmVmTWFwJEV4Y2VwdGlvbkRlZmF1bHQAAAAAAAAAAQIAAHhwdXIAAltJTbpgJnbqsqUCAAB4cAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+UkA2AAAAAHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAhwcHBwcHB0AAFhcHVxAH4ACQAAAAhwcHBwcHB0AAFBcA==") check(mutable.ArrayStack(1, 2, 3))( "rO0ABXNyACNzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuQXJyYXlTdGFja3bdxXbcnLBeAgACSQAqc2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFycmF5U3RhY2skJGluZGV4WwAqc2NhbGEkY29sbGVjdGlvbiRtdXRhYmxlJEFycmF5U3RhY2skJHRhYmxldAATW0xqYXZhL2xhbmcvT2JqZWN0O3hwAAAAA3VyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAANzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAA3NxAH4ABQAAAAJzcQB+AAUAAAAB") check(mutable.DoubleLinkedList(1, 2, 3))( "rO0ABXNyAClzY2FsYS5jb2xsZWN0aW9uLm11dGFibGUuRG91YmxlTGlua2VkTGlzdI73LKsKRr1RAgADTAAEZWxlbXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wABG5leHR0AB5Mc2NhbGEvY29sbGVjdGlvbi9tdXRhYmxlL1NlcTtMAARwcmV2cQB+AAJ4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAABc3EAfgAAc3EAfgAEAAAAAnNxAH4AAHNxAH4ABAAAAANzcQB+AABwcQB+AAtxAH4ACXEAfgAHcQB+AANw") diff --git a/test/junit/scala/issues/RunTest.scala b/test/junit/scala/issues/RunTest.scala new file mode 100644 index 0000000000..2bc8008222 --- /dev/null +++ b/test/junit/scala/issues/RunTest.scala @@ -0,0 +1,143 @@ +package scala.issues + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.{AfterClass, BeforeClass, Test} +import org.junit.Assert._ + +import scala.reflect.runtime._ +import scala.tools.reflect.ToolBox +import scala.tools.testing.ClearAfterClass + +object RunTest extends ClearAfterClass.Clearable { + var toolBox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox() + override def clear(): Unit = { toolBox = null } + + // definitions for individual tests + + class VC(val x: Any) extends AnyVal +} + +@RunWith(classOf[JUnit4]) +class RunTest extends ClearAfterClass { + ClearAfterClass.stateToClear = RunTest + + def run[T](code: String): T = { + val tb = RunTest.toolBox + tb.eval(tb.parse(code)).asInstanceOf[T] + } + + @Test + def classOfValueClassAlias(): Unit = { + val code = + """import scala.issues.RunTest.VC + |type aVC = VC + |type aInt = Int + |type aInteger = Integer + |classOf[VC] == classOf[aVC] && + | classOf[aInt] == classOf[Int] && + | classOf[aInteger] == classOf[Integer] && + | classOf[aInt] != classOf[aInteger] + """.stripMargin + assertTrue(run[Boolean](code)) + } + + @Test + def classOfFinalVal(): Unit = { + val code = + """class C { + | final val a1 = classOf[Int] + | final val b1 = classOf[List[_]] + | final val c1 = classOf[List[String]] + | final val d1 = classOf[Array[Int]] + | final val e1 = classOf[Array[List[_]]] + | final val f1 = classOf[Array[_]] + | + | val a2 = classOf[Int] + | val b2 = classOf[List[_]] + | val c2 = classOf[List[String]] + | val d2 = classOf[Array[Int]] + | val e2 = classOf[Array[List[_]]] + | val f2 = classOf[Array[_]] + | + | val listC = Class.forName("scala.collection.immutable.List") + | + | val compare = List( + | (a1, a2, Integer.TYPE), + | (b1, b2, listC), + | (c1, c2, listC), + | (d1, d2, Array(1).getClass), + | (e1, e2, Array(List()).getClass), + | (f1, f2, new Object().getClass)) + |} + |(new C).compare + """.stripMargin + type K = Class[_] + val cs = run[List[(K, K, K)]](code) + for ((x, y, z) <- cs) { + assertEquals(x, y) + assertEquals(x, z) + } + } + + @Test + def t9702(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.issues.RunTest.VC + |class C { + | type aList[K] = List[K] + | type aVC = VC + | type aInt = Int + | type aInteger = Integer + | @Resource(`type` = classOf[List[Int]]) def a = 0 + | @Resource(`type` = classOf[List[_]]) def b = 0 + | @Resource(`type` = classOf[aList[_]]) def c = 0 + | @Resource(`type` = classOf[Int]) def d = 0 + | @Resource(`type` = classOf[aInt]) def e = 0 + | @Resource(`type` = classOf[Integer]) def f = 0 + | @Resource(`type` = classOf[aInteger]) def g = 0 + | @Resource(`type` = classOf[VC]) def h = 0 + | @Resource(`type` = classOf[aVC]) def i = 0 + | @Resource(`type` = classOf[Array[Int]]) def j = 0 + | @Resource(`type` = classOf[Array[List[_]]]) def k = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |('a' to 'k').toList.map(_.toString).map(typeArg) + """.stripMargin + + val l = Class.forName("scala.collection.immutable.List") + val i = Integer.TYPE + val ig = new Integer(1).getClass + val v = new RunTest.VC(1).getClass + val ai = Array(1).getClass + val al = Array(List()).getClass + + // sanity checks + assertEquals(i, classOf[Int]) + assertNotEquals(i, ig) + + assertEquals(run[List[Class[_]]](code), + List(l, l, l, i, i, ig, ig, v, v, ai, al)) + } + + @Test + def annotationInfoNotErased(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.annotation.meta.getter + |class C { + | type Rg = Resource @getter + | @(Resource @getter)(`type` = classOf[Int]) def a = 0 + | @Rg(`type` = classOf[Int]) def b = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |List("a", "b") map typeArg + |""".stripMargin + + val i = Integer.TYPE + assertEquals(run[List[Class[_]]](code), List(i, i)) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala index 758566fe53..d29f6b0a13 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/IndyLambdaTest.scala @@ -31,25 +31,44 @@ class IndyLambdaTest extends ClearAfterClass { case _ => Nil }.head } + + val obj = "Ljava/lang/Object;" + val str = "Ljava/lang/String;" + // unspecialized functions that have a primitive in parameter or return position // give rise to a "boxing bridge" method (which has the suffix `$adapted`). // This is because Scala's unboxing of null values gives zero, whereas Java's throw a NPE. // 1. Here we show that we are calling the boxing bridge (the lambda bodies here are compiled into // methods of `(I)Ljava/lang/Object;` / `(I)Ljava/lang/Object;` respectively.) - assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Int) => new Object")) - assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", implMethodDescriptorFor("(x: Object) => 0")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Int) => new Object")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x: Object) => 0")) // 2a. We don't need such adaptations for parameters or return values with types that differ // from Object due to other generic substitution, LambdaMetafactory will downcast the arguments. - assertEquals("(Ljava/lang/String;)Ljava/lang/String;", implMethodDescriptorFor("(x: String) => x")) + assertEquals(s"($str)$str", implMethodDescriptorFor("(x: String) => x")) // 2b. Testing 2a. in combination with 1. - assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", implMethodDescriptorFor("(x: Int) => \"\"")) - assertEquals("(Ljava/lang/String;)Ljava/lang/Object;", implMethodDescriptorFor("(x: String) => 0")) + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x: Int) => \"\"")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("(x: String) => 0")) // 3. Specialized functions, don't need any of this as they implement a method like `apply$mcII$sp`, // and the (un)boxing is handled in the base class in code emitted by scalac. assertEquals("(I)I", implMethodDescriptorFor("(x: Int) => x")) + + // non-builtin sams are like specialized functions + compileClasses(compiler)("class VC(private val i: Int) extends AnyVal; trait FunVC { def apply(a: VC): VC }") + assertEquals("(I)I", implMethodDescriptorFor("((x: VC) => x): FunVC")) + + compileClasses(compiler)("trait Fun1[T, U] { def apply(a: T): U }") + assertEquals(s"($obj)$str", implMethodDescriptorFor("(x => x.toString): Fun1[Int, String]")) + assertEquals(s"($obj)$obj", implMethodDescriptorFor("(x => println(x)): Fun1[Int, Unit]")) + assertEquals(s"($obj)$str", implMethodDescriptorFor("((x: VC) => \"\") : Fun1[VC, String]")) + assertEquals(s"($str)$obj", implMethodDescriptorFor("((x: String) => new VC(0)) : Fun1[String, VC]")) + + compileClasses(compiler)("trait Coll[A, Repr] extends Any") + compileClasses(compiler)("final class ofInt(val repr: Array[Int]) extends AnyVal with Coll[Int, Array[Int]]") + + assertEquals(s"([I)$obj", implMethodDescriptorFor("((xs: Array[Int]) => new ofInt(xs)): Array[Int] => Coll[Int, Array[Int]]")) } } diff --git a/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala new file mode 100644 index 0000000000..b9e45a7dc9 --- /dev/null +++ b/test/junit/scala/tools/nsc/backend/jvm/IndySammyTest.scala @@ -0,0 +1,160 @@ +package scala.tools.nsc +package backend.jvm + +import org.junit.Assert.assertEquals +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test + +import scala.tools.asm.Opcodes._ +import scala.tools.asm.tree._ +import scala.tools.nsc.reporters.StoreReporter +import CodeGenTools._ +import scala.tools.partest.ASMConverters +import ASMConverters._ + +import scala.tools.testing.ClearAfterClass + +object IndySammyTest extends ClearAfterClass.Clearable { + var _compiler = newCompiler() + + def compile(scalaCode: String, javaCode: List[(String, String)] = Nil, allowMessage: StoreReporter#Info => Boolean = _ => false): List[ClassNode] = + compileClasses(_compiler)(scalaCode, javaCode, allowMessage) + + def clear(): Unit = { _compiler = null } +} + +@RunWith(classOf[JUnit4]) +class IndySammyTest extends ClearAfterClass { + ClearAfterClass.stateToClear = IndySammyTest + import IndySammyTest._ + + val compiler = _compiler + + def funClassName(from: String, to: String) = s"Fun$from$to" + def classPrologue(from: String, to: String) = + "class VC(private val i: Int) extends AnyVal\n" + + s"trait ${funClassName(from, to)} { def apply(a: $from): $to}" + + def lamDef(from: String, to: String, body: String => String) = + s"""def lam = (x => ${body("x")}): ${funClassName(from, to)}""" + + def appDef(arg: String) = s"""def app = lam($arg)""" + + /* Create a lambda of type "$from => $to" (with body "$body(x)" if "x" is the argument name), + * and apply it to `arg`. + * + * Check: + * - the signature of the apply method + * - the instructions in the lambda's body (anonfun method) + * - the instructions used to create the argument for the application + * (and the return corresponding to the lambda's result type) + */ + def test(from: String, to: String, arg: String, body: String => String = x => x) + (expectedSig: String, lamBody: List[Instruction], appArgs: List[Instruction], ret: Instruction) + (allowMessage: StoreReporter#Info => Boolean = _ => false) = { + val cls = compile(s"${classPrologue(from, to)}") + val methodNodes = compileMethods(compiler)(lamDef(from, to, body) +";"+ appDef(arg), allowMessage) + + val applySig = cls.head.methods.get(0).desc + val anonfun = methodNodes.find(_.name contains "$anonfun$").map(convertMethod).get + val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp + val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get + + assertEquals(expectedSig, applySig) + assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) + assertSameCode(anonfun, lamBody) + assertSameCode(applyInvoke, List( + VarOp(ALOAD, 0), + Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( + Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) + ) + } + +// def testSpecial(lam: String, lamTp: String, arg: String)(allowMessage: StoreReporter#Info => Boolean = _ => false) = { +// val cls = compile("trait Special[@specialized A] { def apply(a: A): A}" ) +// val methodNodes = compileMethods(compiler)(s"def lam : $lamTp = $lam" +";"+ appDef(arg), allowMessage) +// +// val anonfun = methodNodes.filter(_.name contains "$anonfun$").map(convertMethod) +// val lamInsn = methodNodes.find(_.name == "lam").map(instructionsFromMethod).get.dropNonOp +// val applyInvoke = methodNodes.find(_.name == "app").map(convertMethod).get +// +// assert(lamInsn.length == 2 && lamInsn.head.isInstanceOf[InvokeDynamic], lamInsn) +// assertSameCode(anonfun, lamBody) +// assertSameCode(applyInvoke, List( +// VarOp(ALOAD, 0), +// Invoke(INVOKEVIRTUAL, "C", "lam", s"()L${funClassName(from, to)};", false)) ++ appArgs ++ List( +// Invoke(INVOKEINTERFACE, funClassName(from, to), "apply", applySig, true), ret) +// ) +// } + + // x => x : VC => VC applied to VC(1) + @Test + def testVC_VC_VC = + test("VC", "VC", "new VC(1)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(x) : Int => VC applied to 1 + @Test + def testInt_VC_1 = + test("Int", "VC", "1", x => s"new VC($x)")("(I)I", + List(VarOp(ILOAD, 0), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => x : VC => Int applied to VC(1) + @Test + def testVC_Int_VC = + test("VC", "Int", "new VC(1)", x => "1")("(I)I", + List(Op(ICONST_1), Op(IRETURN)), + List(Op(ICONST_1)), + Op(IRETURN))() + + // x => new VC(1) : VC => Any applied to VC(1) + @Test + def testVC_Any_VC = + test("VC", "Any", "new VC(1)", x => s"new VC(1)")("(I)Ljava/lang/Object;", + List(TypeOp(NEW, "VC"), Op(DUP), Op(ICONST_1), Invoke(INVOKESPECIAL, "VC", "<init>", "(I)V", false), Op(ARETURN)), + List(Op(ICONST_1)), + Op(ARETURN))() + + + // x => x : VC => Unit applied to VC(1) + @Test + def testVC_Unit_VC = + test("VC", "Unit", "new VC(1)")("(I)V", + List(VarOp(ILOAD, 0), Op(POP), Op(RETURN)), + List(Op(ICONST_1)), + Op(RETURN))(allowMessage = _.msg.contains("pure expression")) + + // x => new VC(x.asInstanceOf[Int]) : Any => VC applied to 1 + // + // Scala: + // def lam = (x => new VC(x.asInstanceOf[Int])): FunAny_VC + // def app = lam(1) + // Java: + // FunAny_VC lam() { return x -> BoxesRunTime.unboxToInt((Object)x); } + // int app() { lam().apply(BoxesRunTime.boxToInteger((int)1)); + @Test + def testAny_VC_1 = + test("Any", "VC", "1", x => s"new VC($x.asInstanceOf[Int])")("(Ljava/lang/Object;)I", + List(VarOp(ALOAD, 0), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "unboxToInt", "(Ljava/lang/Object;)I", false), Op(IRETURN)), + List(Op(ICONST_1), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false)), + Op(IRETURN))() + + // TODO + // x => x : Special[Int] applied to 1 +// @Test +// def testSpecial_Int_1 = +// testSpecial("x => x", "Special[Int]", "1")() + + + // Tests ThisReferringMethodsTraverser + @Test + def testStaticIfNoThisReference: Unit = { + val methodNodes = compileMethods(compiler)("def foo = () => () => () => 42") + methodNodes.forall(m => !m.name.contains("anonfun") || (m.access & ACC_STATIC) == ACC_STATIC) + } +} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala index 6e1ac3ba9f..b37b5efa7e 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/CallGraphTest.scala @@ -174,7 +174,7 @@ class CallGraphTest extends ClearAfterClass { | def t2(i: Int, f: Int => Int, z: Int) = h(f) + i - z | def t3(f: Int => Int) = h(x => f(x + 1)) |} - |abstract class D { + |trait D { | def iAmASam(x: Int): Int | def selfSamCall = iAmASam(10) |} diff --git a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala index 0ba0ecca4c..10ab006017 100644 --- a/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala +++ b/test/junit/scala/tools/nsc/backend/jvm/opt/ScalaInlineInfoTest.scala @@ -100,7 +100,7 @@ class ScalaInlineInfoTest extends ClearAfterClass { @Test def inlineInfoSam(): Unit = { val code = - """abstract class C { + """trait C { // expected to be seen as sam: g(I)I | def f = 0 | def g(x: Int): Int | val foo = "hi" @@ -108,10 +108,10 @@ class ScalaInlineInfoTest extends ClearAfterClass { |abstract class D { | val biz: Int |} - |trait T { + |trait T { // expected to be seen as sam: h(Ljava/lang/String;)I | def h(a: String): Int |} - |abstract class E extends T { + |trait E extends T { // expected to be seen as sam: h(Ljava/lang/String;)I | def hihi(x: Int) = x |} |class F extends T { diff --git a/test/junit/scala/util/control/ExceptionTest.scala b/test/junit/scala/util/control/ExceptionTest.scala new file mode 100644 index 0000000000..5211d31839 --- /dev/null +++ b/test/junit/scala/util/control/ExceptionTest.scala @@ -0,0 +1,42 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2016-2016, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.util + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.Test +import org.junit.Assert._ + +import scala.collection.mutable.ListBuffer + +import scala.util.control.Exception._ + +@RunWith(classOf[JUnit4]) +class ExceptionTest { + + @Test + def andFinally(): Unit = { + + locally { + val audit = ListBuffer[Int]() + val katch = nonFatalCatch[Unit].andFinally(audit append 1) + val result = katch(10) + assertEquals(result, 10) + assertEquals(audit.toList, 1 :: Nil) + } + + locally { + val audit = ListBuffer[Int]() + val katch = nonFatalCatch[Unit].andFinally(audit append 1).andFinally(audit append 2) + val result = katch(20) + assertEquals(result, 20) + assertEquals(audit.toList, 1 :: 2 :: Nil) + } + } +}
\ No newline at end of file |