From bba562a71ec4b4916b75054fb517f7924e0e2aec Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:08:44 +0200 Subject: [PATCH 01/62] Functional Typelevel Programming in Scala First version of working draft --- docs/docs/typelevel.md | 449 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 docs/docs/typelevel.md diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md new file mode 100644 index 000000000000..051a28c9e265 --- /dev/null +++ b/docs/docs/typelevel.md @@ -0,0 +1,449 @@ +# Functional Typelevel Programming in Scala + +This is a working draft document for discussing language constructs in +support of typelevel programming in Scala 3. + +## State of the Art + +Currently, typelevel programming in Scala is mainly done using implicits. +Proof search for finding implicit arguments can be used to compute new, interesting types. +This results in a programming style much like Prolog. Amazing feats have +been achieved using this scheme, but things are certainly far from +ideal. In particular: + + - The logic programming style requires a shift of mindset compared to the + usual functional programming style in Scala. + - The ways to control implicit search are underdeveloped, + leading to complicated schemes, requiring rule prioritization or other hacks. + - Because of their conceptual complexity the resulting typelevel programs are often + fragile. + - Error diagnostics are usually very bad, making programming with implicits somewhat + of a black art. Dotty has greatly improved error dignostics for recursive implicits, + but the fundamental problem remains. + +## The Core Idea + +A simple principle underlies the new design: Typelevel programming in Scala 3 means reducing terms and taking their types afterwards. Specifically, if `f` is a _transparent_ function applied to some arguments `es` then the type of the application `f(es)` is the type of the term to which `f(es)` reduces. Since reduction can specialize types, that type can be more specific than `f`'s declared result type. Type-level programming in Scala 3 is thus a form of partial evaluation or [type specialization](http://www.cse.chalmers.se/~rjmh/Papers/typed-pe.html). + +## Transparent Functions + +Consider the standard definition of typelevel Peano numbers: +```scala +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat +``` + +A function that maps non-negative integers to Peano numbers can be defined as follows: + +```scala +transparent def toNat(n: Int): Nat = n match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) +} +``` +Without the `transparent` modifier, an application of `toNat(3)` would have type `Nat`, as this +is the method's declared return type. But with `transparent`, the call to `toNat(3)` gets reduced _at compile time_ as follows: + + toNat(3) + -> + 3 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + } + -> + S(toNat(2)) + -> + S(2 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + }) + -> + S(S(toNat(1))) + -> -> + S(S(S(toNat(0)))) + -> + S(S(S(0 match { + case 0 => Z + case n if n > 0 => S(toNat(n - 1)) + }))) + -> + S(S(S(Z))) + +The type of `toNat(3)` is the type of its result, `S[S[S[Z]]]`, which is a subtype of the declared result type `Nat`. + +A `transparent` modifier on a method definition indicates that any application of the defined method outside a transparent method definition will be expanded to its right hand side, where formal parameters get bound to actual arguments. The right side will be further simplified using a set of rewrite rules given below. + +Top-level `match` expressions in transparent methods are treated specially. An expression is considered top-level if it appears + + - as the right hand side of the `transparent` function, or + - as the final expression of a top-level block, or + - as a branch of a top-level match expression. + +A top-level match expression in a transparent _must_ be rewritten at compile-time. That is, if there is enough static information to unambiguously pick one of its branches, the expression +is rewritten to that branch. Otherwise a compile-time error is signalled indicating that the match is ambiguous. If one wants to have a transparent function expand to a match expression that cannot be evaluated statically in this fashion, one can always enclose the expression in a +```scala +locally { ... } +``` +block, which de-classifies it as a top-level expression. (`locally` is a function in `Predef` which simply returns its argument.) + +Transparent methods are effectively final; they may not be overwritten. If a transparent +method has a top-level match expression then it can itself override no other method and it must always be fully applied. + +As another example, consider the following two functions over tuples: + +```scala +transparent def concat(xs: Tuple, ys: Tuple): Tuple = xs match { + case () => ys + case (x, xs1) => (x, concat(xs1, ys)) +} + +transparent def nth(xs: Tuple, n: Int): Any = xs match { + case (x, _) if n == 0 => x + case (_, xs1) if n > 0 => nth(xs1, n - 1) +} +``` +Assume +```scala +as: (Int, String) +bs: (Boolean, List[Int]) +tp: Tuple +``` + +Then we get the following typings: +```scala +concat(as, bs) : (Int, String, Boolean, List[Int]) +concat(as, ()) : (Int, String) +concat((), as) : (Int, String) +concat(as, tp) : (Int, (String, Tuple)) + +nth(as, 0) : Int +nth(as, 1) : String +nth(as, 2) : Nothing +nth(as, -1) : Nothing +nth(concat(as, bs), 3) : List[Int] +``` + +In each of these cases, the result is obtained by expanding the transparent function(s), simplifying (reducing) their right hand sides, and taking the type of the result. As an example, the applications `concat(as, bs)` and `nth(as, 1)` would produce expressions like these: +```scala +concat(as, bs) --> (as._1, { val a$1 = as._2; (a$1._1, bs) } +nth(as, 1) --> { val a$1 = as._2; a$1._1 } +``` +If tuples get large, so do the expansions. For instance, the size of the expansion of a valid selection `nth(xs, n)` is proportional to `n`. We will show later a scheme to avoid this code blowup using `erased` values. + +The following expressions would all give compile-time errors since a toplevel `match` could not be reduced: +```scala +concat(tp, bs) +nth(tp, 0) +nth(as, 2) +nth(as -1) +``` +It's possible to add more cases to a toplevel match, thereby moving an error from compile-time to runtime. For instance, here is a version of `nth` that throws a runtime error in case the index is out of bounds: +```scala +transparent def nthDynamic(xs: Tuple, n: Int): Any = xs match { + case (x, _) if n == 0 => x + case (_, xs1) => nthDynamic(xs1, n - 1) + case () => throw new IndexOutOfBoundsException +} +``` +Here is an expansion of `nthDynamic` applied to a tuple `as: (Int, String)` and a negative index. For clarity we have added the computed types of the intermediate values `as$i`. +``` + nthDynamic(as, -1) + -> + { val as$0: (String, ()) = as._1 + nthDynamic(as$0, -2) + } + -> + { val as$0: (String, ()) = as._1 + { val as$1: () = as$0._1 + nthDynamic(as$1, -3) + } + } + -> + throw new IndexOutOfBoundsException +``` +So the end result of the expansion is the expression `throw new IndexOutOfBoundsException`, which has type `Nothing`. It is important to note that programs are treated as _data_ in this process, so the exception will not be thrown at compile time, but only if the program is run after it compiles without errors. + +**Rewrite Rules** The following rewrite rules are performed when simplifying inlined bodies: + + - constant folding + - evaluation of pattern matches in toplevel match expressions + - reduction of projections + - evaluation of if-then-else with constant expressions + +(to be expanded) + +## Matching on Types + +We have seen so far transparent functions that take terms (tuples and integers) as parameters. What if we want to base case distinctions on types instead? For instance, one would like to be able to write a function `defaultValue`, that, given a type `T` +returns optionally the default value of `T`, if it exists. In fact, we can already express +this using ordinary match expressions and a simple helper function, `scala.typelevel.anyValue`, which is defined as follows: +```scala +def anyValue[T]: T = ??? +``` +The `anyValue` function _pretends_ to return a value of its type argument `T`. In fact, it will always raise a `NotImplementedError` exception instead. +This is OK, since the function is intended to be used only on the type-level; it should never be executed at run-time. + +Using `anyValue`, we can then define `defaultValue` as follows: +```scala +transparent def defaultValue[T]: Option[T] = anyValue[T] match { + case _: Byte => Some(0: Byte) + case _: Char => Some(0: Char) + case _: Short => Some(0: Short) + case _: Int => Some(0) + case _: Long => Some(0L) + case _: Float => Some(0.0f) + case _: Double => Some(0.0d) + case _: Boolean => Some(false) + case _: Unit => Some(()) + case _: t >: Null => Some(null) + case _ => None +} +``` +Then: +```scala +defaultValue[Int] = Some(0) +defaultValue[Boolean] = Some(false) +defaultValue[String | Null] = Some(null) +defaultValue[AnyVal] = None +``` + +As another example, consider the type-level inverse of `toNat`: given a _type_ representing a Peano number, return the integer _value_ corresponding to it. Here's how this can be defined: +```scala +transparent def toInt[N <: Nat]: Int = anyValue[N] match { + case _: Z => 0 + case _: S[n] => toInt[n] + 1 +} +``` + +## Computing New Types + +The examples so far all computed _terms_ that have interesting new types. Since in Scala terms can contain types it is equally easy to compute the types of these terms directly as well. To this purpose, it is helpful +to base oneself on the helper class `scala.typelevel.Typed`, defined as follows: +```scala +class Typed[T](val value: T) { type Type = T } +``` +Here is a version of `concat` that computes at the same time a result and its type: +```scala +transparent def concatTyped(xs: Tuple, ys: Tuple): Typed[_ <: Tuple] = xs match { + case () => Typed(ys) + case (x, xs1) => Typed((x, concatTyped(xs1, ys).value)) +} +``` + +## Avoiding Code Explosion + +Recursive transparent functions implement a form of loop unrolling through inlining. This can lead to very large generated expressions. The code explosion can be avoided by calling typed versions of the transparent functions to define erased values, of which just the typed part is used afterwards. Here is how this can be done for `concat`: + +```scala +def concatImpl(xs: Tuple, ys: Tuple): Tuple = xs match { + case () => ys + case (x, xs1) => (x, concatImpl(xs1, ys)) +} + +transparent def concat(xs: Tuple, ys: Tuple): Tuple = { + erased val r = concatTyped(xs, ys) + concatImpl(xs, ys).asInstanceOf[r.Type] +} +``` +The transparent `concat` method makes use of two helper functions, `concatTyped` (described in the last section) and `concatImpl`. `concatTyped` is called as the right hand side of an `erased` value `r`. Since `r` is `erased`, no code is generated for its definition. +`concatImpl` is a regular, non-transparent function that implements `concat` on generic tuples. It is not inlineable, and its result type is always `Tuple`. The actual code for `concat` calls `concatImpl` and casts its result to type `r.Type`, the computed result type of the concatenation. This gives the best of both worlds: Compact code and expressive types. + +One might criticize that this scheme involves code duplication. In the example above, the recursive `concat` algorithm had to be implemented twice, once as a regular function, the other time as a transparent function. However, in practice it is is quite likely that the regular function would use optimized data representatons and algortihms that do not lend themselves easily to a typelevel interpretation. In these cases a dual implementation is required anyway. + +## Code Specialization + +Transparent functions are a convenient means to achieve code specialization. As an example, consider implementing a math library that implements (among others) a `dotProduct` method. Here is a possible implementation of `MathLib` with some user code. +```scala +abstract class MathLib[N : Numeric] { + def dotProduct(xs: Array[N], ys: Array[N]): N +} +object MathLib { + + transparent def apply[N](implicit n: Numeric[N]) = new MathLib[N] { + import n._ + def dotProduct(xs: Array[N], ys: Array[N]): N = { + require(xs.length == ys.length) + var i = 0 + var s: N = n.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s + } + } +} +``` +`MathLib` is intentionally kept very abstract - it works for any element type `N` for which a `math.Numeric` implementation exists. +Here is some code that uses `MathLib`: +```scala +val mlib = MathLib[Double] + +val xs = Array(1.0, 1.0) +val ys = Array(2.0, -3.0) +mlib.dotProduct(xs, ys) +``` +The implementation code for a given numeric type `N` is produced by the `apply` method of `MathLib`. +Even though the `dotProduct` code looks simple, there is a lot of hidden complexity in the generic code: + + - It uses unbounded generic arrays, which means code on the JVM needs reflection to access their elements + - All operations on array elements forward to generic operations in class `Numeric`. + +It would be quite hard for even a good optimizer to undo the generic abstractions and replace them with something simpler if `N` is specialized to a primitive type like `Double`. But since `apply` is marked `transparent`, the specialization happens automatically as a result of inlining the body of `apply` with the new types. Indeed, the specialized version of `dotProduct` looks like this: +``` +def dotProduct(xs: Array[Double], ys: Array[Double]): Double = { + require(xs.length == ys.length) + var i = 0 + var s: Double = n.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s +} +``` +In other words, specialization with transparent functions allows "abstraction without regret". The price to pay for this +is the increase of code size through inlining. That price is often worth paying, but inlining decisions need to be considered carefully. + +## Implicit Matches + +It is foreseen that many areas of typelevel programming can be done with transparent functions instead of implicits. But sometimes implicits are unavoidable. The problem so far was that the Prolog-like programming style of implicit search becomes viral: Once some construct depends on implicit search it has to be written as a logic program itself. Consider for instance the problem +of creating a `TreeSet[T]` or a `HashSet[T]` depending on whether `T` has an `Ordering` or not. We can create a set of implicit definitions like this: +```scala +trait SetFor[T, S <: Set[T]] +class LowPriority { + implicit def hashSetFor[T]: SetFor[T, HashSet[T]] = ... +} +object SetsFor extends LowPriority { + implicit def treeSetFor[T: Ordering]: SetFor[T, TreeSet[T]] = ... +} +``` +Clearly, this is not pretty. Besides all the usual indirection of implicit search, +we face the problem of rule prioritization where +we have to ensure that `treeSetFor` takes priority over `hashSetFor` if the element type has an ordering. This is solved +(clumsily) by putting `hashSetFor` in a superclass `LowPriority` of the object `SetsFor` where `treeSetFor` is defined. +Maybe the boilerplate would still be acceptable if the crufty code could be contained. However, this is not the case. Every user of +the abstraction has to be parameterized itself with a `SetFor` implicit. Considering the simple task _"I want a `TreeSet[T]` if +`T` has an ordering and a `HashSet[T]` otherwise"_, this seems like a lot of ceremony. + +There are some proposals to improve the situation in specific areas, for instance by allowing more elaborate schemes to specify priorities. But they all keep the viral nature of implicit search programs based on logic programming. + +By contrast, the new `implicit match` construct makes implicit search available in a functional context. To solve +the problem of creating the right set, one would use it as follows: +```scala +transparent def setFor[T]: Set[T] = implicit match { + case Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] +} +``` +An implicit match uses the `implicit` keyword in the place of the scrutinee. Its patterns are types. +Patterns are tried in sequence. The first case with a pattern for which an implicit can be summoned is chosen. + +Implicit matches can only occur as toplevel match expressions of transparent methods. This ensures that +all implicit searches are done at compile-time. + +There may be several patterns in a case of an implicit match, separated by commas. Patterns may bind type variables. +For instance the case +```scala + case Convertible[T, u], Printable[`u`] => ... +``` +matches any type `T` that is `Convertible` to another type `u` such that `u` is `Printable`. + +## New Syntax for Type Variables in Patterns + +This last example exhibits some awkwardness in the way type variables are currently handled in patterns. To _bind_ a type variable, +it needs to start with a lower case letter, then to refer to the bound variable itself, the variable has to be +put in backticks. This is doable, but feels a bit unnatural. To improve on the syntax, we +allow an alternative syntax that prefixes type variables by `type`: +```scala + case Convertible[T, type U], Printable[U] => ... +``` + +## Transparent Values + +Value definitions can also be marked `transparent`. Examples: +```scala +transparent val label = "url" +transparent val pi: Double = 3.14159265359 +transparent val field = outer.field +``` +The right hand side of a `transparent` value definition must have singleton type. The type of the value is then the singleton type of its right hand side, without any widenings. For instance, the type of `label` above is the singleton type `"url"` instead of `String` and the type of `pi` is `3.14159265359` instead of `Double`. + +Transparent values are effectively final; they may not be overridden. In Scala-2, constant values had to be expressed using `final`, which gave an unfortunate double meaning to the modifier. The `final` syntax is still supported in Scala 3 for a limited time to support cross-building. + +Transparent values are more general than the old meaning of `final` since they also work on paths. For instance, the `field` definition above establishes at typing time the knowledge that `field` is an alias of `outer.field`. The same effect can be achieved with an explicit singleton type ascription: +```scala +final val field: outer.field.type = outer.field +``` +The transparent definition of `field` is equivalent but arguably easier to read. + +It is currently open whether we want to support `transparent` on parameters, with the semantics that corresponding arguments are required to have singleton types. It does not seem to be needed for expressiveness, but might help with better error messages. + +## Transparent and Inline + +Dotty up to version 0.9 also supports an `inline` modifier. `inline` is similar to `transparent` in that inlining happens during type-checking. However, `inline` does not change the types of the inlined expressions. The expressions are instead inlined in the form of trees that are already typed. + +Since there is very large overlap between `transparent` and `inline`, we propose to drop `inline` as a separate modifier. The current `@inline` annotation, which represents a hint to the optimizer that inlining might be advisable, will remain unaffected. + +## Relationship to "TypeOf" + +This document describes one particular way to conceptualize and implement transparent methods. An implementation is under way in #4616. An alternative approach is explored in #4671. The main difference is that the present proposal uses the machinery of partial evaluation (PE) whereas #4671 uses the machinery of (term-) dependent types (DT). + +The PE approach reduces the original right-hand side of a transparent function via term rewriting. The right hand side is conceptually the term as it is written down, closed over the environment where the transparent function was defined. This is implemented by rewriting and then re-type-checking the original untyped tree, but with typed leaves that refer to the tree's free variables. The re-type-checking with more specific argument types can lead to a type derivation that is quite different +than the original typing of the transparent method's right hand side. Types might be inferred to be more specific, overloading resolution could pick different alternatives, and implicit searches might yield different results or might be elided altogether. +This flexibility is what makes code specialization work smoothly. + +By constrast, the DT approach typechecks the right hand side of a transparent function in a special way. Instead of +computing types as usual, it "lifts" every term into a type that mirrors it. To do this, it needs to create type equivalents +of all relevant term forms including applications, conditionals, match expressions, and the various forms of pattern matches. +The end result of the DT approach is then a type `{t}` that represents essentially the right-hand side term `t` itself, or at least all portions that might contribute to that type. The type has to be constructed in such a way that the result type of every application +of a transparent function can be read off from it. +An application of a transparent function then needs to just instantiate that type, whereas the term is not affected at all, and the function is called as usual. This means there is a guarantee that the +semantics of a transparent call is the same as a normal call, only the type is better. On the other hand, one cannot play +tricks such as code specialization, since there is nothing to specialize. +Also, implicit matches would not fit into this scheme, as they require term rewritings. Another concern is how much additional complexity would be caused by mirroring the necessary term forms in types and tweaking type inference to work with the new types. + +To summarize, here are some advantages of DT over PE: + + + It avoids code explosion by design (can be solved in PE but requires extra work). + + Expansion only works on types, so this might well be faster at compile-time than PE's term rewriting. + + It gives a guarantee that `transparent` is semantics preserving. + + The typeof `{t}` operator is interesting in its own right. + +By contrast, here are some advantages of PE over DT: + + + It uses the standard type checker and typing rules with no need to go to full dependent types + (only path dependent types instead of full term-dependencies). + + It can do code specialization. + + It can do implicit matches. + + It can also serve as the inlining mechanism for implementing macros with staging (see next section). + +## Relationship To Meta Programming + +Transparent functions are a safer alternative to the whitebox macros in Scala 2. Both share the principle that some function applications get expanded such that the types of these applications are the types of their expansions. But the way the expansions are performed is completely different: For transparent functions, expansions are simply beta reduction (_aka_ inlining) and a set of rewrite rules that are sound wrt the language semantics. All rewritings are performed by the compiler itself. It is really as if some parts of the program reduction are performed early, which is the essence of partial evaluation. + +By contrast, Scala 2 macros mean that user-defined code is invoked to process program fragments as data. The result of this computation is then embedded instead of the macro call. Macros are thus a lot more powerful than transparent functions, but also a lot less safe. + +Functionality analogous to blackbox macros in Scala-2 is available in Scala-3 through its [principled meta programming](https://dotty.epfl.ch/docs/reference/principled-meta-programming.html) system: Code can be turned into data using quotes `(')`. Code-returning computations can be inserted into quotes using splices `(~)`. A splice outside quotes means that the spliced computation is _run_, which is the analogue of +invoking a macro. Quoted code can be inspected using reflection on Tasty trees. + +To compare: here's the scheme used in Scala-2 to define a macro: +```scala +def m(x: T) = macro mImpl +... +def mImpl(x: Tree) = ... +``` +Here's analogous functionality in Scala-3: +```scala +transparent def m(x: T) = ~mImpl('x) +... +def mImpl(x: Expr[T]) = ... +``` +The new scheme is more explicit and orthogonal: The usual way to define a macro is as a transparent function that invokes with `~` a macro library method, passing it quoted arguments as data. The role of the transparent function is simply to avoid to have to write the splices and quotes in user code. + +One important difference between the two schemes is that the reflective call implied by `~` is performed _after_ the program is typechecked. Besides a better separation of concerns, this also provides a more predictable environment for macro code to run in. + +## Acknowledgments + +Many of the ideas in this proposal resulted from discussions with @gsps and @OlivierBlanvillain, the authors of the "TypeOf" approach (PR #4671). @gsps suggested the use of the `transparent` keyword. @OlivierBlanvillain suggested techniques like `anyValue` and `Typed` to lift term computations to types. The present proposal also benefited from feedback from @milessabin, @adriaanm, @sjrd, Andrei Alexandrescu, John Hughes, Conor McBride and Stephanie Weirich on earlier designs. The relationship with meta programming has a lot in common with the original inline and meta proposals in SIP 28 and SIP 29. From 8b70e37cbb756a7f75421045509ede8b2f62493a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:09:20 +0200 Subject: [PATCH 02/62] Update section on transparent values In order to work with PMP, we need stronger restrictions on transparent parameters than were outlined previously. Specifically, arguments to transparent parameters must be constant expressions. To stay uniform between value definitions and parameters we now impose the same restriction on value definitions. The use case of `transparent` definitions with paths as right hand side has to be dropped for consistency. Also, we allow transparent modifiers only in transparent methods. Anywhere else they just contribute to puzzlers, I fear. --- docs/docs/typelevel.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 051a28c9e265..5f28fff2abb8 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -366,19 +366,20 @@ Value definitions can also be marked `transparent`. Examples: ```scala transparent val label = "url" transparent val pi: Double = 3.14159265359 -transparent val field = outer.field ``` -The right hand side of a `transparent` value definition must have singleton type. The type of the value is then the singleton type of its right hand side, without any widenings. For instance, the type of `label` above is the singleton type `"url"` instead of `String` and the type of `pi` is `3.14159265359` instead of `Double`. +The right hand side of a `transparent` value definition must be a pure expression of constant type. The type of the value is then the type of its right hand side, without any widenings. For instance, the type of `label` above is the singleton type `"url"` instead of `String` and the type of `pi` is `3.14159265359` instead of `Double`. Transparent values are effectively final; they may not be overridden. In Scala-2, constant values had to be expressed using `final`, which gave an unfortunate double meaning to the modifier. The `final` syntax is still supported in Scala 3 for a limited time to support cross-building. -Transparent values are more general than the old meaning of `final` since they also work on paths. For instance, the `field` definition above establishes at typing time the knowledge that `field` is an alias of `outer.field`. The same effect can be achieved with an explicit singleton type ascription: +The `transparent` modifier can also be used for value parameters of `transparent` methods. Example ```scala -final val field: outer.field.type = outer.field +transparent def power(x: Double, transparent n: Int) = ... ``` -The transparent definition of `field` is equivalent but arguably easier to read. +If a `transparent` modifier is given, corresponding arguments must be pure expressions of constant type. -It is currently open whether we want to support `transparent` on parameters, with the semantics that corresponding arguments are required to have singleton types. It does not seem to be needed for expressiveness, but might help with better error messages. +However, the restrictions on right-hand sides or arguments mentioned in this section do not apply in code that is +itself in a `transparent` method. In other words, constantness checking is performed only when a `transparent` method +is expanded, not when it is defined. ## Transparent and Inline From 44a5de1d58277875c28113f1b0cd8ff7c564e628 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:09:53 +0200 Subject: [PATCH 03/62] Introduce UntypedSplice Introduce UntypedSplice and make TreeCopiers and TreeMaps more regular to take splices into account. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 2 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 56 +++++++++++++++---- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +- compiler/src/dotty/tools/dotc/ast/untpd.scala | 26 ++++++--- .../tools/dotc/printing/RefinedPrinter.scala | 3 + .../tools/dotc/transform/MacroTransform.scala | 2 + .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 7 files changed, 73 insertions(+), 22 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index e52213cf0485..ccadcf15d4da 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -11,7 +11,7 @@ import core.tasty.TreePickler.Hole /** A map that applies three functions and a substitution together to a tree and * makes sure they are coordinated so that the result is well-typed. The functions are - * @param typeMap A function from Type to Type that gets applied to the + * @param typeMap A function from Type to Type that gets applied to the * type of every tree node and to all locally defined symbols, * followed by the substitution [substFrom := substTo]. * @param treeMap A transformer that translates all encountered subtrees in diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index 9f72feb216b9..a100c14cd57c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -1109,6 +1109,10 @@ object Trees { case tree: Annotated if (arg eq tree.arg) && (annot eq tree.annot) => tree case _ => finalize(tree, untpd.Annotated(arg, annot)) } + def UntypedSplice(tree: Tree)(splice: untpd.Tree) = tree match { + case tree: tpd.UntypedSplice if tree.splice `eq` splice => tree + case _ => finalize(tree, tpd.UntypedSplice(splice)) + } def Thicket(tree: Tree)(trees: List[Tree]): Thicket = tree match { case tree: Thicket if trees eq tree.trees => tree case _ => finalize(tree, untpd.Thicket(trees)) @@ -1146,7 +1150,7 @@ object Trees { */ protected def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx - abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { + abstract class TreeMap(val cpy: TreeCopier = inst.cpy) { self => def transform(tree: Tree)(implicit ctx: Context): Tree = { Stats.record(s"TreeMap.transform $getClass") @@ -1245,8 +1249,8 @@ object Trees { case Thicket(trees) => val trees1 = transform(trees) if (trees1 eq trees) tree else Thicket(trees1) - case _ if ctx.reporter.errorsReported => - tree + case _ => + transformMoreCases(tree) } } @@ -1258,9 +1262,26 @@ object Trees { transform(tree).asInstanceOf[Tr] def transformSub[Tr <: Tree](trees: List[Tr])(implicit ctx: Context): List[Tr] = transform(trees).asInstanceOf[List[Tr]] + + protected def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree map: homomorphism on the untyped part with + // recursive mapping of typed splices. + // The case is overridden in UntypedTreeMap.## + val untpdMap = new untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = tree match { + case untpd.TypedSplice(tsplice) => + untpd.cpy.TypedSplice(tree)(self.transform(tsplice).asInstanceOf[tpd.Tree]) + // the cast is safe, since the UntypedSplice case is overridden in UntypedTreeMap. + case _ => super.transform(tree) + } + } + cpy.UntypedSplice(tree)(untpdMap.transform(usplice)) + case _ if ctx.reporter.errorsReported => tree + } } - abstract class TreeAccumulator[X] { + abstract class TreeAccumulator[X] { self => // Ties the knot of the traversal: call `foldOver(x, tree))` to dive in the `tree` node. def apply(x: X, tree: Tree)(implicit ctx: Context): X @@ -1355,14 +1376,29 @@ object Trees { this(this(x, arg), annot) case Thicket(ts) => this(x, ts) - case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => - // In interactive mode, errors might come from previous runs. - // In case of errors it may be that typed trees point to untyped ones. - // The IDE can still traverse inside such trees, either in the run where errors - // are reported, or in subsequent ones. - x + case _ => + foldMoreCases(x, tree) } } + + def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + case tpd.UntypedSplice(usplice) => + // For a typed tree accumulator: skip the untyped part and fold all typed splices. + // The case is overridden in UntypedTreeAccumulator. + val untpdAcc = new untpd.UntypedTreeAccumulator[X] { + override def apply(x: X, tree: untpd.Tree)(implicit ctx: Context): X = tree match { + case untpd.TypedSplice(tsplice) => self(x, tsplice) + case _ => foldOver(x, tree) + } + } + untpdAcc(x, usplice) + case _ if ctx.reporter.errorsReported || ctx.mode.is(Mode.Interactive) => + // In interactive mode, errors might come from previous runs. + // In case of errors it may be that typed trees point to untyped ones. + // The IDE can still traverse inside such trees, either in the run where errors + // are reported, or in subsequent ones. + x + } } abstract class TreeTraverser extends TreeAccumulator[Unit] { diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 440266d32f3e..85779e437d75 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -325,7 +325,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { case pre: ThisType => tp.isType || pre.cls.isStaticOwner || - tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls + tp.symbol.isParamOrAccessor && !pre.cls.is(Trait) && ctx.owner.enclosingClass == pre.cls // was ctx.owner.enclosingClass.derivesFrom(pre.cls) which was not tight enough // and was spuriously triggered in case inner class would inherit from outer one // eg anonymous TypeMap inside TypeMap.andThen @@ -490,6 +490,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else foldOver(sym, tree) } + case class UntypedSplice(splice: untpd.Tree) extends Tree + override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 663f4393ad67..bc806a47bd03 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -24,8 +24,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice * @param owner The current owner at the time the tree was defined */ - abstract case class TypedSplice(tree: tpd.Tree)(val owner: Symbol) extends ProxyTree { - def forwardTo = tree + abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol) extends ProxyTree { + def forwardTo = splice } object TypedSplice { @@ -495,10 +495,14 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case tree: PatDef if (mods eq tree.mods) && (pats eq tree.pats) && (tpt eq tree.tpt) && (rhs eq tree.rhs) => tree case _ => finalize(tree, untpd.PatDef(mods, pats, tpt, rhs)) } + def TypedSplice(tree: Tree)(splice: tpd.Tree)(implicit ctx: Context) = tree match { + case tree: TypedSplice if splice `eq` tree.splice => tree + case _ => finalize(tree, untpd.TypedSplice(splice)) + } } abstract class UntypedTreeMap(cpy: UntypedTreeCopier = untpd.cpy) extends TreeMap(cpy) { - override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + override def transformMoreCases(tree: Tree)(implicit ctx: Context): Tree = tree match { case ModuleDef(name, impl) => cpy.ModuleDef(tree)(name, transformSub(impl)) case ParsedTry(expr, handler, finalizer) => @@ -539,15 +543,17 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { cpy.ContextBounds(tree)(transformSub(bounds), transform(cxBounds)) case PatDef(mods, pats, tpt, rhs) => cpy.PatDef(tree)(mods, transform(pats), transform(tpt), transform(rhs)) + case tpd.UntypedSplice(splice) => + cpy.UntypedSplice(tree)(transform(splice)) case TypedSplice(_) => tree case _ => - super.transform(tree) + super.transformMoreCases(tree) } } - abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { - override def foldOver(x: X, tree: Tree)(implicit ctx: Context): X = tree match { + abstract class UntypedTreeAccumulator[X] extends TreeAccumulator[X] { self => + override def foldMoreCases(x: X, tree: Tree)(implicit ctx: Context): X = tree match { case ModuleDef(name, impl) => this(x, impl) case ParsedTry(expr, handler, finalizer) => @@ -588,10 +594,12 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { this(this(x, bounds), cxBounds) case PatDef(mods, pats, tpt, rhs) => this(this(this(x, pats), tpt), rhs) - case TypedSplice(tree) => - this(x, tree) + case TypedSplice(splice) => + this(x, splice) + case tpd.UntypedSplice(splice) => + this(x, splice) case _ => - super.foldOver(x, tree) + super.foldMoreCases(x, tree) } } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index b64ec3a5ad92..60d12147ce5d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -446,6 +446,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case TypedSplice(t) => if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ "]#TS#" else toText(t) + case tpd.UntypedSplice(t) => + if (ctx.settings.YprintDebug.value) "[" ~ toText(t) ~ ":" ~ toText(tree.typeOpt) ~ "]#US#" + else toText(t) case tree @ ModuleDef(name, impl) => withEnclosingDef(tree) { modText(tree.mods, NoSymbol, keywordStr("object")) ~~ nameIdText(tree) ~ toTextTemplate(impl) diff --git a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala index 5c3dea9cb79e..8d561f5c916a 100644 --- a/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/MacroTransform.scala @@ -59,6 +59,8 @@ abstract class MacroTransform extends Phase { transform(parents)(ctx.superCallContext), transformSelf(self), transformStats(impl.body, tree.symbol)) + case UntypedSplice(_) => + tree case _ => super.transform(tree) } diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index a45c8174395a..bb90e23a4cd3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1671,7 +1671,7 @@ class Typer extends Namer } def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tree.tree match { + tree.splice match { case tree1: TypeTree => tree1 // no change owner necessary here ... case tree1: Ident => tree1 // ... or here, since these trees cannot contain bindings case tree1 => From 9d7fff91290a664f2a9835119d0501c90ba3e47f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:10:23 +0200 Subject: [PATCH 04/62] Untasty trees Allow pickling/unpickling untyped trees in Tasty (Tasteless?) Concretely, Allow UNTYPEDSPLICE roots with TYPEDSPLICE subtrees. Trees between them are untyped. Among others, adds a new tag for EMPTYTREE. The serialized info is ambiguous otherwise. E.g. TypeBounds(Empty, B) and TypeBounds(B, Empty) serialize in the same way. Also, fix wrong owner when reading annotations. This was the enclosing class instead of the method containing the annotation. Fixing this is surprisingly hard. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 2 +- .../tools/dotc/core/tasty/TastyFormat.scala | 32 ++- .../tools/dotc/core/tasty/TastyPrinter.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 253 +++++++++++++++++- .../tools/dotc/core/tasty/TreeUnpickler.scala | 253 ++++++++++++++++-- 5 files changed, 507 insertions(+), 35 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 85779e437d75..aade111c5bf6 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -661,7 +661,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def skipTransform(tree: Tree)(implicit ctx: Context) = tree.tpe.isError - implicit class TreeOps[ThisTree <: tpd.Tree](val tree: ThisTree) extends AnyVal { + implicit class TreeOps[ThisTree <: tpd.Tree](private val tree: ThisTree) extends AnyVal { def isValue(implicit ctx: Context): Boolean = tree.isTerm && tree.tpe.widen.isValueType diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index f1237189459c..de1e04fcadf2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -59,6 +59,7 @@ Standard-Section: "ASTs" TopLevelStat* DEFDEF Length NameRef TypeParam* Params* returnType_Term rhs_Term? Modifier* TYPEDEF Length NameRef (type_Term | Template) Modifier* + OBJECTDEF Length NameRef Template Modifier* IMPORT Length qual_Term Selector* Selector = IMPORTED name_NameRef RENAMED to_NameRef @@ -109,6 +110,7 @@ Standard-Section: "ASTs" TopLevelStat* EMPTYTREE SHAREDterm term_ASTRef HOLE Length idx_Nat arg_Tree* + UNTYPEDSPLICE Length splice_TermUntyped splice_Type CaseDef = CASEDEF Length pat_Term rhs_Tree guard_Tree? ImplicitArg = IMPLICITARG arg_Term @@ -203,6 +205,14 @@ Standard-Section: "ASTs" TopLevelStat* Annotation = ANNOTATION Length tycon_Type fullAnnotation_Term +// --------------- untyped additions ------------------------------------------ + + TermUntyped = Term + TYPEDSPLICE Length splice_Term + FUNCTION Length body_Term arg_Term* + INFIXOP Length op_NameRef left_Term right_Term + PATDEF Length type_Term rhs_Term pattern_Term* Modifier* + Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way. Category 1 (tags 1-49) : tag @@ -307,6 +317,7 @@ object TastyFormat { final val MACRO = 34 final val ERASED = 35 final val PARAMsetter = 36 + final val EMPTYTREE = 37 // Cat. 2: tag Nat @@ -408,6 +419,7 @@ object TastyFormat { final val ANNOTATION = 172 final val TERMREFin = 173 final val TYPEREFin = 174 + final val OBJECTDEF = 175 // In binary: 101100EI // I = implicit method type @@ -417,6 +429,14 @@ object TastyFormat { final val ERASEDMETHODtype = 178 final val ERASEDIMPLICITMETHODtype = 179 + final val UNTYPEDSPLICE = 199 + + // Tags for untyped trees only: + final val TYPEDSPLICE = 200 + final val FUNCTION = 201 + final val INFIXOP = 202 + final val PATDEF = 203 + def methodType(isImplicit: Boolean = false, isErased: Boolean = false) = { val implicitOffset = if (isImplicit) 1 else 0 val erasedOffset = if (isErased) 2 else 0 @@ -432,7 +452,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= PARAMsetter || + firstSimpleTreeTag <= tag && tag <= EMPTYTREE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -529,6 +549,7 @@ object TastyFormat { case DEFAULTparameterized => "DEFAULTparameterized" case STABLE => "STABLE" case PARAMsetter => "PARAMsetter" + case EMPTYTREE => "EMPTYTREE" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" @@ -560,6 +581,7 @@ object TastyFormat { case VALDEF => "VALDEF" case DEFDEF => "DEFDEF" case TYPEDEF => "TYPEDEF" + case OBJECTDEF => "OBJECTDEF" case IMPORT => "IMPORT" case TYPEPARAM => "TYPEPARAM" case PARAMS => "PARAMS" @@ -627,13 +649,19 @@ object TastyFormat { case PRIVATEqualified => "PRIVATEqualified" case PROTECTEDqualified => "PROTECTEDqualified" case HOLE => "HOLE" + + case UNTYPEDSPLICE => "UNTYPEDSPLICE" + case TYPEDSPLICE => "TYPEDSPLICE" + case FUNCTION => "FUNCTION" + case INFIXOP => "INFIXOP" + case PATDEF => "PATDEF" } /** @return If non-negative, the number of leading references (represented as nats) of a length/trees entry. * If negative, minus the number of leading non-reference trees. */ def numRefs(tag: Int) = tag match { - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | RETURN | BIND | SELFDEF | REFINEDtype | TERMREFin | TYPEREFin | HOLE => 1 case RENAMED | PARAMtype => 2 case POLYtype | METHODtype | TYPELAMBDAtype => -1 diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala index 3a0442d7cbc6..80a7aac1743b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala @@ -64,7 +64,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) { tag match { case RENAMED => printName(); printName() - case VALDEF | DEFDEF | TYPEDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => + case VALDEF | DEFDEF | TYPEDEF | OBJECTDEF | TYPEPARAM | PARAM | NAMEDARG | BIND => printName(); printTrees() case REFINEDtype | TERMREFin | TYPEREFin => printName(); printTree(); printTrees() diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index c2b22ade4ed6..4175aa41f3b0 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -4,7 +4,7 @@ package core package tasty import ast.Trees._ -import ast.{untpd, tpd} +import ast.{untpd, tpd, desugar} import TastyFormat._ import Contexts._, Symbols._, Types._, Names._, Constants._, Decorators._, Annotations._, StdNames.tpnme, NameOps._ import collection.mutable @@ -536,7 +536,7 @@ class TreePickler(pickler: TastyPickler) { pickleTree(tp) case Annotated(tree, annot) => writeByte(ANNOTATEDtpt) - withLength { pickleTree(tree); pickleTree(annot.tree) } + withLength { pickleTree(tree); pickleTree(annot) } case LambdaTypeTree(tparams, body) => writeByte(LAMBDAtpt) withLength { pickleParams(tparams); pickleTree(body) } @@ -546,6 +546,9 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } + case tpd.UntypedSplice(splice) => + writeByte(UNTYPEDSPLICE) + withLength { pickleUntyped(splice); pickleType(tree.tpe) } case Hole(idx, args) => writeByte(HOLE) withLength { @@ -577,15 +580,24 @@ class TreePickler(pickler: TastyPickler) { def pickleModifiers(sym: Symbol)(implicit ctx: Context): Unit = { import Flags._ - val flags = sym.flags + var flags = sym.flags val privateWithin = sym.privateWithin if (privateWithin.exists) { writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) pickleType(privateWithin.typeRef) + flags = flags &~ Protected } + if ((flags is ParamAccessor) && sym.isTerm && !sym.isSetter) + flags = flags &~ ParamAccessor // we only generate a tag for parameter setters + pickleFlags(flags, sym.isTerm) + sym.annotations.foreach(pickleAnnotation(sym, _)) + } + + def pickleFlags(flags: Flags.FlagSet, isTerm: Boolean)(implicit ctx: Context): Unit = { + import Flags._ if (flags is Private) writeByte(PRIVATE) - if (flags is Protected) if (!privateWithin.exists) writeByte(PROTECTED) - if ((flags is Final) && !(sym is Module)) writeByte(FINAL) + if (flags is Protected) writeByte(PROTECTED) + if (flags.is(Final, butNot = Module)) writeByte(FINAL) if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) if (flags is Inline) writeByte(INLINE) @@ -598,7 +610,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Synthetic) writeByte(SYNTHETIC) if (flags is Artifact) writeByte(ARTIFACT) if (flags is Scala2x) writeByte(SCALA2X) - if (sym.isTerm) { + if (isTerm) { if (flags is Implicit) writeByte(IMPLICIT) if (flags is Erased) writeByte(ERASED) if (flags.is(Lazy, butNot = Module)) writeByte(LAZY) @@ -608,7 +620,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is CaseAccessor) writeByte(CASEaccessor) if (flags is DefaultParameterized) writeByte(DEFAULTparameterized) if (flags is Stable) writeByte(STABLE) - if ((flags is ParamAccessor) && sym.isSetter) writeByte(PARAMsetter) + if (flags is ParamAccessor) writeByte(PARAMsetter) if (flags is Label) writeByte(LABEL) } else { if (flags is Sealed) writeByte(SEALED) @@ -617,7 +629,6 @@ class TreePickler(pickler: TastyPickler) { if (flags is Covariant) writeByte(COVARIANT) if (flags is Contravariant) writeByte(CONTRAVARIANT) } - sym.annotations.foreach(pickleAnnotation(sym, _)) } private def isUnpicklable(owner: Symbol, ann: Annotation)(implicit ctx: Context) = ann match { @@ -636,6 +647,232 @@ class TreePickler(pickler: TastyPickler) { withLength { pickleType(ann.symbol.typeRef); pickleTree(ann.tree) } } +// ---- pickling untyped trees ---------------------------------- + + def pickleUntyped(tree: untpd.Tree)(implicit ctx: Context): Unit = { + + def pickleDummyRef(): Unit = writeNat(0) + + def pickleDummyType(): Unit = { + writeByte(SHAREDtype) + pickleDummyRef() + } + + def pickleUnlessEmpty(tree: untpd.Tree): Unit = + if (!tree.isEmpty) pickleUntyped(tree) + + def pickleTpt(tree: untpd.Tree) = pickleUntyped(tree)(ctx.addMode(Mode.Type)) + def pickleTerm(tree: untpd.Tree) = pickleUntyped(tree)(ctx.retractMode(Mode.Type)) + + def pickleAllParams(tree: untpd.DefDef): Unit = { + pickleParams(tree.tparams) + for (vparams <- tree.vparamss) { + writeByte(PARAMS) + withLength { pickleParams(vparams) } + } + } + + def pickleParams(trees: List[untpd.Tree]): Unit = + trees.foreach(pickleParam) + + def pickleParam(tree: untpd.Tree): Unit = tree match { + case tree: untpd.ValDef => pickleDef(PARAM, tree, tree.tpt) + case tree: untpd.DefDef => pickleDef(PARAM, tree, tree.tpt, tree.rhs) + case tree: untpd.TypeDef => pickleDef(TYPEPARAM, tree, tree.rhs) + } + + def pickleParent(tree: untpd.Tree): Unit = tree match { + case _: untpd.Apply | _: untpd.TypeApply => pickleUntyped(tree) + case _ => pickleTpt(tree) + } + + def pickleDef(tag: Int, tree: untpd.MemberDef, tpt: untpd.Tree, rhs: untpd.Tree = untpd.EmptyTree, pickleParams: => Unit = ()) = { + import untpd.modsDeco + writeByte(tag) + withLength { + pickleName(tree.name) + pickleParams + pickleTpt(tpt) + pickleUnlessEmpty(rhs) + pickleModifiers(tree.mods, tree.name.isTermName) + } + } + + def pickleModifiers(mods: untpd.Modifiers, isTerm: Boolean): Unit = { + import Flags._ + var flags = mods.flags + val privateWithin = mods.privateWithin + if (!privateWithin.isEmpty) { + writeByte(if (flags is Protected) PROTECTEDqualified else PRIVATEqualified) + pickleUntyped(untpd.Ident(privateWithin)) + flags = flags &~ Protected + } + pickleFlags(flags, isTerm) + mods.annotations.foreach(pickleAnnotation) + } + + def pickleAnnotation(annotTree: untpd.Tree) = { + writeByte(ANNOTATION) + withLength { pickleDummyType(); pickleUntyped(annotTree) } + } + + try tree match { + case Ident(name) => + writeByte(if (name.isTypeName) TYPEREF else TERMREF) + pickleName(name) + pickleDummyType() + case This(qual) => + writeByte(QUALTHIS) + pickleUntyped(qual) + case Select(qual, name) => + writeByte(if (name.isTypeName) SELECTtpt else SELECT) + pickleName(name) + if (qual.isType) pickleTpt(qual) else pickleTerm(qual) + case Apply(fun, args) => + writeByte(APPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleUntyped) + } + case untpd.Throw(exc) => + writeByte(THROW) + pickleUntyped(exc) + case TypeApply(fun, args) => + writeByte(TYPEAPPLY) + withLength { + pickleUntyped(fun) + args.foreach(pickleTpt) + } + case Literal(const) => + pickleConstant(const) + case Super(qual, mix) => + writeByte(SUPER) + withLength { + pickleUntyped(qual); + if (!mix.isEmpty) pickleUntyped(mix) + } + case New(tpt) => + writeByte(NEW) + pickleTpt(tpt) + case Typed(expr, tpt) => + writeByte(TYPED) + withLength { pickleUntyped(expr); pickleTpt(tpt) } + case NamedArg(name, arg) => + writeByte(NAMEDARG) + pickleName(name) + pickleUntyped(arg) + case Assign(lhs, rhs) => + writeByte(ASSIGN) + withLength { pickleUntyped(lhs); pickleUntyped(rhs) } + case Block(stats, expr) => + writeByte(BLOCK) + withLength { pickleUntyped(expr); stats.foreach(pickleUntyped) } + case If(cond, thenp, elsep) => + writeByte(IF) + withLength { pickleUntyped(cond); pickleUntyped(thenp); pickleUntyped(elsep) } + case Match(selector, cases) => + writeByte(MATCH) + withLength { pickleUntyped(selector); cases.foreach(pickleUntyped) } + case CaseDef(pat, guard, rhs) => + writeByte(CASEDEF) + withLength { pickleUntyped(pat); pickleUntyped(rhs); pickleUnlessEmpty(guard) } + case Return(expr, from) => + writeByte(RETURN) + withLength { pickleDummyRef(); pickleUnlessEmpty(expr) } + case Try(block, cases, finalizer) => + writeByte(TRY) + withLength { pickleUntyped(block); cases.foreach(pickleUntyped); pickleUnlessEmpty(finalizer) } + case Bind(name, body) => + writeByte(BIND) + withLength { + pickleName(name); pickleDummyType(); pickleUntyped(body) + } + case Alternative(alts) => + writeByte(ALTERNATIVE) + withLength { alts.foreach(pickleUntyped) } + case tree: untpd.ValDef => + pickleDef(VALDEF, tree, tree.tpt, tree.rhs) + case tree: untpd.DefDef => + pickleDef(DEFDEF, tree, tree.tpt, tree.rhs, pickleAllParams(tree)) + case tree: untpd.TypeDef => + pickleDef(TYPEDEF, tree, tree.rhs) + case tree: untpd.ModuleDef => + pickleDef(OBJECTDEF, tree, tree.impl) + case tree: untpd.Template => + writeByte(TEMPLATE) + tree.parents.foreach(pickleParent) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleTpt(tree.self.tpt) + } + pickleUntyped(tree.constr) + tree.body.foreach(pickleUntyped) + case Import(expr, selectors) => + writeByte(IMPORT) + withLength { pickleUntyped(expr); pickleSelectors(selectors) } + case tree: untpd.TypeTree => + pickleDummyType() + case SingletonTypeTree(ref) => + writeByte(SINGLETONtpt) + pickleTerm(ref) + case RefinedTypeTree(parent, refinements) => + writeByte(REFINEDtpt) + withLength { pickleTpt(parent); refinements.foreach(pickleTerm) } + case AppliedTypeTree(tycon, args) => + writeByte(APPLIEDtpt) + withLength { pickleTpt(tycon); args.foreach(pickleTpt) } + case AndTypeTree(tp1, tp2) => + writeByte(ANDtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case OrTypeTree(tp1, tp2) => + writeByte(ORtpt) + withLength { pickleTpt(tp1); pickleTpt(tp2) } + case ByNameTypeTree(tp) => + writeByte(BYNAMEtpt) + pickleTpt(tp) + case Annotated(tree, annot) => + writeByte(ANNOTATEDtpt) + withLength { pickleTpt(tree); pickleTerm(annot) } + case LambdaTypeTree(tparams, body) => + writeByte(LAMBDAtpt) + withLength { pickleParams(tparams); pickleTpt(body) } + case TypeBoundsTree(lo, hi) => + writeByte(TYPEBOUNDStpt) + withLength { + pickleTpt(lo); + if (hi ne lo) pickleTpt(hi) + } + case untpd.Function(args, body) => + writeByte(FUNCTION) + withLength { pickleUntyped(body); args.foreach(pickleUntyped) } + case untpd.InfixOp(l, op, r) => + writeByte(INFIXOP) + withLength { pickleUntyped(l); pickleUntyped(op); pickleUntyped(r) } + case untpd.PatDef(mods, pats, tpt, rhs) => + writeByte(PATDEF) + withLength { + pickleTpt(tpt) + pickleUntyped(rhs) + pats.foreach(pickleUntyped) + pickleModifiers(mods, isTerm = true) + } + case untpd.TypedSplice(splice) => + writeByte(TYPEDSPLICE) + withLength { pickleTree(splice) } + case Thicket(trees) => + if (trees.isEmpty) writeByte(EMPTYTREE) + else trees.foreach(pickleUntyped) + case _ => + pickleUntyped(desugar(tree)) + } + catch { + case ex: AssertionError => + println(i"error when pickling tree $tree") + throw ex + } + } + +// ---- main entry points --------------------------------------- + def pickle(trees: List[Tree])(implicit ctx: Context) = { trees.foreach(tree => if (!tree.isEmpty) pickleTree(tree)) def missing = forwardSymRefs.keysIterator.map(_.showLocated).toList diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index cdd68445b46f..1c21914fc148 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -515,7 +515,7 @@ class TreeUnpickler(reader: TastyReader, val rhsStart = currentAddr val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() - val (givenFlags, annotFns, privateWithin) = readModifiers(end) + val (givenFlags, annotFns, privateWithin) = readModifiers(end, readAnnot, readWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = @@ -562,10 +562,12 @@ class TreeUnpickler(reader: TastyReader, /** Read modifier list into triplet of flags, annotations and a privateWithin * boundary symbol. */ - def readModifiers(end: Addr)(implicit ctx: Context): (FlagSet, List[Symbol => Annotation], Symbol) = { + def readModifiers[WithinType, AnnotType] + (end: Addr, readAnnot: Context => Symbol => AnnotType, readWithin: Context => WithinType, defaultWithin: WithinType) + (implicit ctx: Context): (FlagSet, List[Symbol => AnnotType], WithinType) = { var flags: FlagSet = EmptyFlags - var annotFns: List[Symbol => Annotation] = Nil - var privateWithin: Symbol = NoSymbol + var annotFns: List[Symbol => AnnotType] = Nil + var privateWithin = defaultWithin while (currentAddr.index != end.index) { def addFlag(flag: FlagSet) = { flags |= flag @@ -611,10 +613,10 @@ class TreeUnpickler(reader: TastyReader, addFlag(ParamAccessor) case PRIVATEqualified => readByte() - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case PROTECTEDqualified => addFlag(Protected) - privateWithin = readType().typeSymbol + privateWithin = readWithin(ctx) case ANNOTATION => annotFns = readAnnot(ctx) :: annotFns case tag => @@ -624,6 +626,9 @@ class TreeUnpickler(reader: TastyReader, (flags, annotFns.reverse, privateWithin) } + private val readWithin: Context => Symbol = + implicit ctx => readType().typeSymbol + private val readAnnot: Context => Symbol => Annotation = { implicit ctx => readByte() @@ -924,26 +929,27 @@ class TreeUnpickler(reader: TastyReader, readByte() readEnd() val expr = readTerm() - def readSelectors(): List[untpd.Tree] = nextByte match { - case IMPORTED => - val start = currentAddr - readByte() - val from = setPos(start, untpd.Ident(readName())) - nextByte match { - case RENAMED => - val start2 = currentAddr - readByte() - val to = setPos(start2, untpd.Ident(readName())) - untpd.Thicket(from, to) :: readSelectors() - case _ => - from :: readSelectors() - } - case _ => - Nil - } setPos(start, Import(expr, readSelectors())) } + def readSelectors()(implicit ctx: Context): List[untpd.Tree] = nextByte match { + case IMPORTED => + val start = currentAddr + readByte() + val from = setPos(start, untpd.Ident(readName())) + nextByte match { + case RENAMED => + val start2 = currentAddr + readByte() + val to = setPos(start2, untpd.Ident(readName())) + untpd.Thicket(from, to) :: readSelectors() + case _ => + from :: readSelectors() + } + case _ => + Nil + } + def readIndexedStats(exprOwner: Symbol, end: Addr)(implicit ctx: Context): List[Tree] = until(end)(readIndexedStat(exprOwner)) @@ -1131,6 +1137,8 @@ class TreeUnpickler(reader: TastyReader, TypeBoundsTree(lo, hi) case HOLE => readHole(end, isType = false) + case UNTYPEDSPLICE => + tpd.UntypedSplice(readUntyped()).withType(readType()) case _ => readPathTerm() } @@ -1200,6 +1208,205 @@ class TreeUnpickler(reader: TastyReader, PickledQuotes.quotedExprToTree(quotedExpr) } } +// ------ Reading untyped trees -------------------------------------------- + + def readUntyped()(implicit ctx: Context): untpd.Tree = { + val start = currentAddr + val tag = readByte() + pickling.println(s"reading term ${astTagToString(tag)} at $start") + + def readDummyType(): Unit = { + assert(readByte() == SHAREDtype) + assert(readNat() == 0) + } + + def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] + + def readParams[T <: untpd.MemberDef](tag: Int): List[T] = + collectWhile(nextByte == tag) { + import untpd.modsDeco + val m: T = readUntyped().asInstanceOf[T] + m.withMods(m.mods | Param).asInstanceOf[T] + } + + def readParamss(): List[List[untpd.ValDef]] = + collectWhile(nextByte == PARAMS) { + readByte() + readEnd() + readParams[untpd.ValDef](PARAM) + } + + def readCases(end: Addr): List[untpd.CaseDef] = + collectWhile((nextUnsharedTag == CASEDEF) && currentAddr != end) { + readUntyped().asInstanceOf[untpd.CaseDef] + } + + def readSimpleTerm(): untpd.Tree = (tag: @switch) match { + case TERMREF => + val name = readName() + readDummyType() + untpd.Ident(name) + case TYPEREF => + val name = readName().toTypeName + readDummyType() + untpd.Ident(name) + case SELECT => + val name = readName() + val qual = readUntyped() + untpd.Select(qual, name) + case SELECTtpt => + val name = readName().toTypeName + val qual = readUntyped() + untpd.Select(qual, name) + case QUALTHIS => + untpd.This(readIdent()) + case NEW => + untpd.New(readUntyped()) + case THROW => + untpd.Throw(readUntyped()) + case SINGLETONtpt => + untpd.SingletonTypeTree(readUntyped()) + case BYNAMEtpt => + untpd.ByNameTypeTree(readUntyped()) + case NAMEDARG => + untpd.NamedArg(readName(), readUntyped()) + case EMPTYTREE => + untpd.EmptyTree + case SHAREDtype => + val b = readNat() + assert(b == 0, i"bad shared type $b at $currentAddr when reading ${ctx.owner.ownersIterator.toList}%, %") + untpd.TypeTree() + case _ => + untpd.Literal(readConstant(tag)) + } + + def readLengthTerm(): untpd.Tree = { + val end = readEnd() + + def readMods(): untpd.Modifiers = { + val (flags, annots, privateWithin) = + readModifiers(end, readUntypedAnnot, readUntypedWithin, EmptyTypeName) + untpd.Modifiers(flags, privateWithin, annots.map(_(NoSymbol))) + } + + def readRhs(): untpd.Tree = + if (nothingButMods(end)) untpd.EmptyTree else readUntyped() + + val result = (tag: @switch) match { + case SUPER => + val qual = readUntyped() + val mixId = ifBefore(end)(readIdent(), untpd.EmptyTypeIdent) + untpd.Super(qual, mixId) + case APPLY => + val fn = readUntyped() + untpd.Apply(fn, until(end)(readUntyped())) + case TYPEAPPLY => + untpd.TypeApply(readUntyped(), until(end)(readUntyped())) + case TYPED => + val expr = readUntyped() + val tpt = readUntyped() + untpd.Typed(expr, tpt) + case ASSIGN => + untpd.Assign(readUntyped(), readUntyped()) + case BLOCK => + val expr = readUntyped() + val stats = until(end)(readUntyped()) + untpd.Block(stats, expr) + case IF => + untpd.If(readUntyped(), readUntyped(), readUntyped()) + case MATCH => + untpd.Match(readUntyped(), readCases(end)) + case CASEDEF => + val pat = readUntyped() + val rhs = readUntyped() + val guard = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.CaseDef(pat, guard, rhs) + case RETURN => + readNat() + val expr = ifBefore(end)(readUntyped(), untpd.EmptyTree) + untpd.Return(expr, untpd.EmptyTree) + case TRY => + untpd.Try(readUntyped(), readCases(end), ifBefore(end)(readUntyped(), untpd.EmptyTree)) + case BIND => + val name = readName() + readDummyType() + untpd.Bind(name, readUntyped()) + case ALTERNATIVE => + untpd.Alternative(until(end)(readUntyped())) + case DEFDEF => + untpd.DefDef(readName(), readParams[TypeDef](TYPEPARAM), readParamss(), readUntyped(), readRhs()) + .withMods(readMods()) + case VALDEF | PARAM => + untpd.ValDef(readName(), readUntyped(), readRhs()) + .withMods(readMods()) + case TYPEDEF | TYPEPARAM => + untpd.TypeDef(readName().toTypeName, readUntyped()) + .withMods(readMods()) + case OBJECTDEF => + untpd.ModuleDef(readName(), readUntyped().asInstanceOf[untpd.Template]) + .withMods(readMods()) + case TEMPLATE => + val parents = collectWhile(nextByte != SELFDEF && nextByte != DEFDEF)(readUntyped()) + val self = + if (nextByte == SELFDEF) { + readByte() + untpd.ValDef(readName(), readUntyped(), untpd.EmptyTree) + } + else untpd.EmptyValDef + val constr = readUntyped().asInstanceOf[untpd.DefDef] + val body = until(end)(readUntyped()) + untpd.Template(constr, parents, self, body) + case IMPORT => + untpd.Import(readUntyped(), readSelectors()) + case REFINEDtpt => + untpd.RefinedTypeTree(readUntyped(), until(end)(readUntyped())) + case APPLIEDtpt => + untpd.AppliedTypeTree(readUntyped(), until(end)(readUntyped())) + case ANDtpt => + untpd.AndTypeTree(readUntyped(), readUntyped()) + case ORtpt => + untpd.OrTypeTree(readUntyped(), readUntyped()) + case ANNOTATEDtpt => + untpd.Annotated(readUntyped(), readUntyped()) + case LAMBDAtpt => + val tparams = readParams[TypeDef](TYPEPARAM) + val body = readUntyped() + untpd.LambdaTypeTree(tparams, body) + case TYPEBOUNDStpt => + val lo = readUntyped() + val hi = ifBefore(end)(lo, readUntyped()) + untpd.TypeBoundsTree(lo, hi) + case TYPEDSPLICE => + untpd.TypedSplice(readTerm()) + case FUNCTION => + val body = readUntyped() + import untpd.modsDeco + val params = until(end)(readUntyped()).map { + case param: untpd.ValDef => param.withMods(param.mods | Param) + case param => param + } + untpd.Function(params, body) + case INFIXOP => + untpd.InfixOp(readUntyped(), readIdent(), readUntyped()) + case PATDEF => + val tpt = readUntyped() + val rhs = readUntyped() + val pats = collectWhile(!nothingButMods(end))(readUntyped()) + untpd.PatDef(readMods(), pats, tpt, rhs) + } + assert(currentAddr == end, s"$start $currentAddr $end ${astTagToString(tag)}") + result + } + + val tree = if (tag < firstLengthTreeTag) readSimpleTerm() else readLengthTerm() + setPos(start, tree) + } + + private val readUntypedWithin: Context => TypeName = + implicit ctx => readName().toTypeName + + private val readUntypedAnnot: Context => Symbol => untpd.Tree = + implicit ctx => _ => readUntyped() // ------ Setting positions ------------------------------------------------ From f8f82e1174c0189802b163370a030e332650bce2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:11:01 +0200 Subject: [PATCH 05/62] Dual purpose inliner The new inliner inlines both inline methods with fully-typed expansions and transparent methods with partially untyped extensions. --- .../dotty/tools/dotc/ast/TreeTypeMap.scala | 6 +- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +- compiler/src/dotty/tools/dotc/core/Mode.scala | 6 +- .../tools/dotc/core/SymDenotations.scala | 2 +- .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 11 +- .../dotty/tools/dotc/typer/Implicits.scala | 29 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 245 +++++++-- .../src/dotty/tools/dotc/typer/Namer.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 15 +- tests/run/lst-transparent/Lst.scala | 496 ++++++++++++++++++ tests/run/lst-transparent/LstTest.scala | 329 ++++++++++++ tests/run/transparent-implicits.check | 1 + tests/run/transparent-implicits.scala | 26 + tests/run/transparent-object.check | 1 + tests/run/transparent-object.scala | 14 + tests/run/transparent.check | 9 + tests/run/transparent/Test_2.scala | 21 + tests/run/transparent/inlines_1.scala | 41 ++ tests/run/transparentAccess/C_1.scala | 7 + tests/run/transparentAccess/Test_2.scala | 7 + tests/run/transparentArrowAssoc.scala | 24 + tests/run/transparentAssign.scala | 24 + tests/run/transparentByName.scala | 37 ++ tests/run/transparentForeach.check | 137 +++++ tests/run/transparentForeach.scala | 67 +++ tests/run/transparentPower.check | 2 + tests/run/transparentPower/Test_2.scala | 9 + tests/run/transparentPower/power_1.scala | 12 + tests/run/transparentPrivates.scala | 36 ++ tests/run/transparentProtected.scala | 22 + tests/run/typelevel.scala | 12 +- 32 files changed, 1586 insertions(+), 74 deletions(-) create mode 100644 tests/run/lst-transparent/Lst.scala create mode 100644 tests/run/lst-transparent/LstTest.scala create mode 100644 tests/run/transparent-implicits.check create mode 100644 tests/run/transparent-implicits.scala create mode 100644 tests/run/transparent-object.check create mode 100644 tests/run/transparent-object.scala create mode 100644 tests/run/transparent.check create mode 100644 tests/run/transparent/Test_2.scala create mode 100644 tests/run/transparent/inlines_1.scala create mode 100644 tests/run/transparentAccess/C_1.scala create mode 100644 tests/run/transparentAccess/Test_2.scala create mode 100644 tests/run/transparentArrowAssoc.scala create mode 100644 tests/run/transparentAssign.scala create mode 100644 tests/run/transparentByName.scala create mode 100644 tests/run/transparentForeach.check create mode 100644 tests/run/transparentForeach.scala create mode 100644 tests/run/transparentPower.check create mode 100644 tests/run/transparentPower/Test_2.scala create mode 100644 tests/run/transparentPower/power_1.scala create mode 100644 tests/run/transparentPrivates.scala create mode 100644 tests/run/transparentProtected.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index ccadcf15d4da..c75bdb2a9cf8 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -95,7 +95,9 @@ class TreeTypeMap( val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) res.symbol.transformAnnotations { - case ann: BodyAnnotation => ann.derivedAnnotation(res.rhs) + case ann: BodyAnnotation => + if (res.symbol.isTransparentMethod) ann.derivedAnnotation(transform(ann.tree)) + else ann.derivedAnnotation(res.rhs) case ann => ann } res @@ -126,7 +128,7 @@ class TreeTypeMap( override def transformStats(trees: List[tpd.Tree])(implicit ctx: Context) = transformDefs(trees)._2 - private def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = { + def transformDefs[TT <: tpd.Tree](trees: List[TT])(implicit ctx: Context): (TreeTypeMap, List[TT]) = { val tmap = withMappedSyms(tpd.localSyms(trees)) (tmap, tmap.transformSub(trees)) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index aade111c5bf6..974df76eb501 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -21,6 +21,8 @@ import scala.io.Codec /** Some creators for typed trees */ object tpd extends Trees.Instance[Type] with TypedTreeInfo { + case class UntypedSplice(splice: untpd.Tree) extends Tree + private def ta(implicit ctx: Context) = ctx.typeAssigner def Ident(tp: NamedType)(implicit ctx: Context): Ident = @@ -490,8 +492,6 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } else foldOver(sym, tree) } - case class UntypedSplice(splice: untpd.Tree) extends Tree - override val cpy: TypedTreeCopier = // Type ascription needed to pick up any new members in TreeCopier (currently there are none) new TypedTreeCopier diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index e689544503db..d05191dc024b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -95,7 +95,9 @@ object Mode { /** We are in the IDE */ val Interactive = newMode(20, "Interactive") - /** Read comments from definitions when unpickling from TASTY */ - val ReadComments = newMode(21, "ReadComments") + /** We are typing the body of a transparent method */ + val TransparentBody = newMode(21, "TransparentBody") + /** Read comments from definitions when unpickling from TASTY */ + val ReadComments = newMode(22, "ReadComments") } diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 171cb189ccf9..7253da7dcd0b 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -792,7 +792,7 @@ object SymDenotations { is(InlineMethod, butNot = Accessor) def isTransparentMethod(implicit ctx: Context): Boolean = - is(TransparentMethod, butNot = Accessor) + is(TransparentMethod, butNot = AccessorOrSynthetic) def isInlineableMethod(implicit ctx: Context) = isInlinedMethod || isTransparentMethod diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 4175aa41f3b0..b1e6b87e9a78 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -637,8 +637,8 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot - // inline bodies are reconstituted automatically when unpickling + case _ => ann.symbol == defn.BodyAnnot && owner.isInlinedMethod + // bodies of inlined (but not transparent) methods are reconstituted automatically when unpickling } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1c21914fc148..432b95af686a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -549,7 +549,7 @@ class TreeUnpickler(reader: TastyReader, sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else if (sym.isInlineableMethod) + else if (sym.isInlinedMethod) sym.addAnnotation(LazyBodyAnnotation { ctx0 => implicit val ctx: Context = localContext(sym)(ctx0).addMode(Mode.ReadPositions) // avoids space leaks by not capturing the current context @@ -637,9 +637,12 @@ class TreeUnpickler(reader: TastyReader, val lazyAnnotTree = readLaterWithOwner(end, rdr => ctx => rdr.readTerm()(ctx)) owner => - Annotation.deferredSymAndTree( - implicit ctx => tp.typeSymbol, - implicit ctx => lazyAnnotTree(owner).complete) + if (tp.isRef(defn.BodyAnnot)) + LazyBodyAnnotation(implicit ctx => lazyAnnotTree(owner).complete) + else + Annotation.deferredSymAndTree( + implicit ctx => tp.typeSymbol, + implicit ctx => lazyAnnotTree(owner).complete) } /** Create symbols for the definitions in the statement sequence between diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 4ba82bc897b9..6c8e362ba0b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1132,19 +1132,24 @@ trait Implicits { self: Typer => val eligible = if (contextual) ctx.implicits.eligible(wildProto) else implicitScope(wildProto).eligible - searchImplicits(eligible, contextual).recoverWith { - failure => failure.reason match { - case _: AmbiguousImplicits => failure - case reason => - if (contextual) - bestImplicit(contextual = false).recoverWith { - failure2 => reason match { - case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure - case _ => failure2 + searchImplicits(eligible, contextual) match { + case result: SearchSuccess => + if (contextual && ctx.mode.is(Mode.TransparentBody)) + Inliner.markContextualImplicit(result.tree) + result + case failure: SearchFailure => + failure.reason match { + case _: AmbiguousImplicits => failure + case reason => + if (contextual) + bestImplicit(contextual = false).recoverWith { + failure2 => reason match { + case (_: DivergingImplicit) | (_: ShadowedImplicit) => failure + case _ => failure2 + } } - } - else failure - } + else failure + } } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 82e314050cd0..4b069c437a5d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -27,10 +27,20 @@ import collection.mutable import transform.TypeUtils._ import reporting.trace import util.Positions.Position +import util.Property object Inliner { import tpd._ + /** Marks an implicit reference found in the context (as opposed to the implicit scope) + * from an inlineable body. Such references will be carried along with the body to + * the expansion site. + */ + private val ContextualImplicit = new Property.StickyKey[Unit] + + def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = + methPart(tree).putAttachment(ContextualImplicit, ()) + class InlineAccessors extends AccessProxies { /** If an inline accessor name wraps a unique inline name, this is taken as indication @@ -63,7 +73,9 @@ object Inliner { def postTransform(tree: Tree)(implicit ctx: Context) = tree match { case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => - cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) + val setter = useSetter(lhs) + if (inlineSym.isTransparentMethod) tree // just generate a setter, but don't integrate it in the tree + else cpy.Apply(tree)(setter, rhs :: Nil) case _ => tree } @@ -215,7 +227,7 @@ object Inliner { * to have the inlined method as owner. */ def registerInlineInfo( - inlined: Symbol, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { inlined.unforcedAnnotation(defn.BodyAnnot) match { case Some(ann: ConcreteBodyAnnotation) => case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => @@ -224,18 +236,130 @@ object Inliner { val inlineCtx = ctx inlined.updateAnnotation(LazyBodyAnnotation { _ => implicit val ctx = inlineCtx - val body = treeExpr(ctx) - if (ctx.reporter.hasErrors) body else ctx.compilationUnit.inlineAccessors.makeInlineable(body) + val rawBody = treeExpr(ctx) + val typedBody = + if (ctx.reporter.hasErrors) rawBody + else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) + val inlineableBody = + if (inlined.isInlinedMethod) typedBody + else addReferences(inlined, originalBody, typedBody) + inlining.println(i"Body to inline for $inlined: $inlineableBody") + inlineableBody }) } } } + /** Tweak untyped tree `original` so that all external references are typed + * and it reflects the changes in the corresponding typed tree `typed` that + * make `typed` inlineable. Concretely: + * + * - all external references via identifiers or this-references are converted + * to typed splices, + * - if X gets an inline accessor in `typed`, references to X in `original` + * are converted to the inline accessor name. + */ + def addReferences(inlineMethod: Symbol, + original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + + def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod) + + // Maps from positions to external reference types and inline selector names. + object referenced extends TreeTraverser { + val typeAtPos = mutable.Map[Position, Type]() + val nameAtPos = mutable.Map[Position, Name]() + val implicitSyms = mutable.Set[Symbol]() + val implicitRefs = new mutable.ListBuffer[Tree] + def registerIfContextualImplicit(tree: Tree) = tree match { + case tree: RefTree + if tree.removeAttachment(ContextualImplicit).isDefined && + isExternal(tree.symbol) && + !implicitSyms.contains(tree.symbol) => + if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) + ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) + else { + implicitSyms += tree.symbol + implicitRefs += tree + } + case _ => + } + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case _: Ident | _: This => + //println(i"leaf: $tree at ${tree.pos}") + if (isExternal(tree.symbol)) { + inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + typeAtPos(tree.pos.toSynthetic) = tree.tpe + } + case _: Select if tree.symbol.name.is(InlineAccessorName) => + inlining.println(i"accessor: $tree at ${tree.pos}") + nameAtPos(tree.pos.toSynthetic) = tree.symbol.name + case _ => + } + registerIfContextualImplicit(tree) + traverseChildren(tree) + } + } + referenced.traverse(typed) + + // The untyped tree transform that applies the tweaks + object addRefs extends untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = { + def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match { + case Some(tpe) => untpd.TypedSplice(tree.withType(tpe)) + case none => tree + } + def adjustName(name: Name) = referenced.nameAtPos.get(tree.pos.toSynthetic) match { + case Some(n) => n + case none => name + } + def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match { + case tree @ Ident(name1) => + referenced.typeAtPos.get(tree.pos.startPos) match { + case Some(tp: ThisType) => + val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos)) + cpy.Select(tree)(qual, name1) + case none => + tree + } + case tree => tree + } + val tree1 = super.transform(tree) + tree1 match { + case This(_) => + adjustLeaf(tree1) + case Ident(name) => + adjustQualifier(adjustLeaf(cpy.Ident(tree1)(adjustName(name)))) + case Select(pre, name) => + cpy.Select(tree1)(pre, adjustName(name)) + case tree: untpd.DerivedTypeTree => + inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}") + untpd.TypedSplice(ctx.typer.typed(tree)) + case _ => + tree1 + } + } + } + val implicitBindings = + for (iref <- referenced.implicitRefs.toList) yield { + val localImplicit = iref.symbol.asTerm.copy( + owner = inlineMethod, + name = UniqueInlineName.fresh(iref.symbol.name.asTermName), + flags = Implicit | Method | Stable, + info = iref.symbol.info.ensureMethodic, + coord = inlineMethod.pos).asTerm + polyDefDef(localImplicit, tps => vrefss => + iref.appliedToTypes(tps).appliedToArgss(vrefss)) + } + val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe) + seq(implicitBindings, untpdSplice) + } + /** `sym` has an inline method with a known body to inline (note: definitions coming * from Scala2x class files might be `@forceInline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlinedMethod && sym.hasAnnotation(defn.BodyAnnot) // TODO: Open this up for transparent methods as well + sym.isInlineableMethod && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) @@ -492,17 +616,38 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree }} - val inlineCtx = inlineContext(call) + val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper + val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope + // The complete translation maps references to `this` and parameters to // corresponding arguments or proxies on the type and term level. It also changes // the owner from the inlined method to the current owner. val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx) - val expansion = inliner(rhsToInline.withPos(call.pos)) + val expansion = inliner.transform(rhsToInline.withPos(call.pos)) match { + case Block(implicits, tpd.UntypedSplice(expansion)) => + val prevOwners = implicits.map(_.symbol.owner).distinct + val localizer = new TreeTypeMap(oldOwners = prevOwners, newOwners = prevOwners.map(_ => ctx.owner)) + val (_, implicits1) = localizer.transformDefs(implicits) + for (idef <- implicits1) { + bindingsBuf += idef.withType(idef.symbol.typeRef).asInstanceOf[ValOrDefDef] + // Note: Substituting new symbols does not automatically lead to good prefixes + // if the previous symbol was owned by a class. That's why we need to set the type + // of `idef` explicitly. It would be nice if substituters were smarter, but + // it seems non-trivial to come up with rules that work in + inlineCtx.enter(idef.symbol) + } + expansion + case tpd.UntypedSplice(expansion) => + expansion + case expansion => + expansion + } + trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. - val expansion1 = InlineTyper.typed(expansion, pt)(inlineCtx) + val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) /** All bindings in `bindingsBuf` except bindings of inlineable closures */ val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) @@ -522,8 +667,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ private object InlineableArg { lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Ident)(implicit ctx: Context): Option[Tree] = - if (paramProxies.contains(tree.tpe)) + def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = + if (paramProxies.contains(tree.typeOpt)) bindingsBuf.find(_.name == tree.name) match { case Some(vdef: ValDef) if vdef.symbol.is(Inline) => Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) @@ -534,47 +679,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else None } - /** A typer for inlined code. Its purpose is: + /** A base trait of InlineTyper and InlineReTyper containing operations that + * work the same way for both. Beyond typing or retyping, an inline typer performs + * the following functions: + * * 1. Implement constant folding over inlined code * 2. Selectively expand ifs with constant conditions * 3. Inline arguments that are by-name closures * 4. Make sure inlined code is type-correct. * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ - private object InlineTyper extends ReTyper { - - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { - tpe match { - case tpe: NamedType if !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case info: ConstantType if tpe.symbol.isStable => return info - case _ => - } - case _ => - } - super.ensureAccessible(tpe, superAccess, pos) - } - - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = - tree.asInstanceOf[tpd.Tree] match { - case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs - case _ => super.typedIdent(tree, pt) - } - - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - assert(tree.hasType, tree) - val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) - val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) - res - } + trait InlineTyping extends Typer { override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { case ConstantType(Constant(condVal: Boolean)) => - val selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + var selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + if (selected.isEmpty) selected = tpd.Literal(Constant(())) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) case _ => @@ -622,6 +744,49 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } + /** A full typer used for transparent methods */ + private class InlineTyper extends Typer with InlineTyping { + + override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + tree.splice match { + case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs + case _ => super.typedTypedSplice(tree) + } + } + + /** A re-typer used for inlined methods */ + private class InlineReTyper extends ReTyper with InlineTyping { + + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + tpe match { + case tpe: NamedType if !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case info: ConstantType if tpe.symbol.isStable => return info + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = + tree.asInstanceOf[tpd.Tree] match { + case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs + case _ => super.typedIdent(tree, pt) + } + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { + assert(tree.hasType, tree) + val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) + val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) + ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) + res + } + + override def newLikeThis: Typer = new InlineReTyper + } + /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. * Inline def bindings that are used only once. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a052786f8b61..03f2d4280b41 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -763,6 +763,7 @@ class Namer { typer: Typer => case original: untpd.DefDef if sym.isInlineableMethod => Inliner.registerInlineInfo( sym, + original.rhs, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs )(localContext(sym)) case _ => @@ -1089,7 +1090,8 @@ class Namer { typer: Typer => // it would be erased to BoxedUnit. def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp - val rhsCtx = ctx.addMode(Mode.InferringReturnType) + var rhsCtx = ctx.addMode(Mode.InferringReturnType) + if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index bb90e23a4cd3..69afc643404d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1437,10 +1437,11 @@ class Typer extends Namer (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } + if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) // Overwrite inline body to make sure it is not evaluated twice - if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, _ => rhs1) + if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1909,7 +1910,17 @@ class Typer extends Namer case none => typed(mdef) match { case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => - buf += inlineExpansion(mdef1) + if (mdef1.symbol.isInlinedMethod) { + buf += inlineExpansion(mdef1) + // replace body with expansion, because it will be used as inlined body + // from separately compiled files - the original BodyAnnotation is not kept. + } + else { + assert(mdef1.symbol.isTransparentMethod, mdef.symbol) + Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed, + buf += mdef1 // but keep original definition, since inline-expanded code + // is pickled in this case. + } case mdef1 => import untpd.modsDeco mdef match { diff --git a/tests/run/lst-transparent/Lst.scala b/tests/run/lst-transparent/Lst.scala new file mode 100644 index 000000000000..9e0f52076c0d --- /dev/null +++ b/tests/run/lst-transparent/Lst.scala @@ -0,0 +1,496 @@ +package dotty.tools.dotc +package util + +import printing.{Printer, Texts} +import Texts.Text +import collection.mutable.{ListBuffer, StringBuilder} + + +/** A lightweight class for lists, optimized for short and medium lengths. + * A list is represented at runtime as + * + * If it is empty: the value `Lst.Empty` + * If it contains one element: the element itself + * If it contains more elements: an Array[Any] containing the elements + */ +class Lst[+T](val elems: Any) extends AnyVal { + import Lst._ + + def length: Int = elems match { + case null => 0 + case elems: Arr => elems.length + case elem => 1 + } + + def isEmpty = elems == null + def nonEmpty = elems != null + + transparent def foreach(op: => T => Unit): Unit = { + def sharedOp(x: T) = op(x) + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { sharedOp(elem(i)); i += 1 } + case elem: T @ unchecked => sharedOp(elem) + } + } + + /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. + * Should be used only of `op` is small + */ + transparent def foreachInlined(op: => T => Unit): Unit = elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { op(elem(i)); i += 1 } + case elem: T @unchecked => op(elem) + } + + def iterator(): Iterator[T] = elems match { + case null => Iterator.empty + case elems: Arr => elems.iterator.asInstanceOf[Iterator[T]] + case elem: T @unchecked => Iterator.single(elem) + } + + def copyToArray[U >: T](target: Array[U], from: Int) = elems match { + case null => + case elems: Arr => System.arraycopy(elems, 0, target, from, elems.length) + case elem: T @ unchecked => target(from) = elem + } + + /** `f` is pulled out, not duplicated */ + transparent def map[U](f: => T => U): Lst[U] = { + def op(x: T) = f(x) + elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } + new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](op(elem)) + } + } + + def mapConserve[U](f: T => U): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var newElems: Arr = null + var i = 0 + while (i < elems.length) { + val x = elem(i) + val y = f(x) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(elems.length) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) + case elem: T @ unchecked => new Lst[U](f(elem)) + } + + def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElemss: Arr = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val ys = f(elem(i)) + len += ys.length + newElemss(i) = ys.elems + i += 1 + } + if (len == 0) Empty + else if (len == 1) { + i = 0 + while (newElemss(i) == null) i += 1 + new Lst[U](newElemss(i)) + } + else { + val newElems = new Arr(len) + i = 0 + var j = 0 + while (i < newElemss.length) { + val ys = new Lst[U](newElemss(i)) + ys.copyToArray(newElems, j) + j += ys.length + i += 1 + } + new Lst[U](newElems) + } + case elem: T @ unchecked => new Lst[U](f(elem).elems) + } + + def filter(p: T => Boolean): Lst[T] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val scratch = new Arr(elems.length) + var i = 0 + var len = 0 + while (i < elems.length) { + val x = elem(i) + if (p(x)) { scratch(len) = x; len += 1 } + i += 1 + } + if (len == elems.length) this + else _fromArray(scratch, 0, len) + case elem: T @unchecked => + if (p(elem)) this else Empty + } + def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) + + transparent def exists(p: => T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && !op(elem(i))) i += 1 + i < elems.length + case elem: T @unchecked => + op(elem) + } + } + + transparent def forall(p: => T => Boolean): Boolean = { + def op(x: T) = p(x) + elems match { + case null => true + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && op(elem(i))) i += 1 + i == elems.length + case elem: T @unchecked => + op(elem) + } + } + + transparent def contains[U >: T](x: U): Boolean = elems match { + case null => false + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length && elem(i) != x) i += 1 + i < elems.length + case elem: T @unchecked => + elem == x + } + + transparent def foldLeft[U](z: U)(f: => (U, T) => U) = { + def op(x: U, y: T) = f(x, y) + elems match { + case null => z + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + var acc = z + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + op(z, elem) + } + } + + transparent def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) + + def reduceLeft[U >: T](op: (U, U) => U) = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 1 + var acc: U = elem(0) + while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } + acc + case elem: T @unchecked => + elem + } + + def reverse: Lst[T] = elems match { + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { + newElems(elems.length - 1 - i) = elem(i) + i += 1 + } + new Lst[T](newElems) + case _ => this + } + + def apply(n: Int): T = elems match { + case null => throw new IndexOutOfBoundsException(n.toString) + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + elem(n) + case elem: T @unchecked => + if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString) + } + + def head: T = apply(0) + def last: T = apply(length - 1) + + def headOr[U >: T](alt: => U): U = if (isEmpty) alt else head + + def slice(start: Int, end: Int): Lst[T] = + if (start < 0) slice(0, end) + else elems match { + case null => this + case elems: Arr => _fromArray(elems, start, end `min` elems.length) + case elem: T @ unchecked => if (end == 0) Empty else this + } + + def drop(n: Int): Lst[T] = slice(n, length) + def tail = drop(1) + def take(n: Int): Lst[T] = slice(0, n) + + def ++ [U >: T](that: Lst[U]): Lst[U] = + if (elems == null) that + else if (that.elems == null) this + else { + val len1 = this.length + val len2 = that.length + val newElems = new Arr(len1 + len2) + this.copyToArray(newElems, 0) + that.copyToArray(newElems, len1) + new Lst[U](newElems) + } + + def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = + this.elems match { + case null => Empty + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length min elems2.length + if (len == 0) Empty + else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) + else { + var newElems: Arr = null + var i = 0 + while (i < len) { + val x = elem1(i) + val y = op(x, elem2(i)) + if (newElems != null) newElems(i) = y + else if (!eq(x, y)) { + newElems = new Arr(len) + System.arraycopy(elems, 0, newElems, 0, i) + newElems(i) = y + } + i += 1 + } + new Lst[V](newElems) + } + case elem2: U @unchecked => + new Lst[V](op(elem1(0), elem2)) + } + case elem1: T @unchecked => + that.elems match { + case null => Empty + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + new Lst[V](op(elem1, elem2(0))) + case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) + } + } + + def zip[U](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _)) + + def zipWithIndex: Lst[(T, Int)] = elems match { + case null => Empty + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + val newElems = new Arr(elems.length) + var i = 0 + while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } + new Lst[(T, Int)](newElems) + case elem: T @unchecked => + new Lst[(T, Int)]((elem, 0)) + } + + def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = + (this `eqLst` that) || { + this.elems match { + case null => + that.elems == null + case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] + that.elems match { + case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && p(elem1(i), elem2(i))) i += 1 + i == len + } + case _ => false + } + case elem1: T @unchecked => + that.elems match { + case null => false + case elems2: Arr => false + case elem2: U @unchecked => p(elem1, elem2) + } + } + } + + def === [U](that: Lst[U]) = + (this `eqLst` that) || { + elems match { + case elems1: Arr => + that.elems match { + case elems2: Arr => + val len = elems1.length + len == elems2.length && { + var i = 0 + while (i < len && elems1(i).equals(elems2(i))) i += 1 + i == len + } + case _ => false + } + case elem => elem == that.elems + } + } + + def eqLst[U](that: Lst[U]) = eq(elems, that.elems) + + def eqElements[U](that: Lst[U]): Boolean = corresponds(that)(eqFn) + + def mkString: String = mkString(", ") + + def mkString(sep: String): String = mkString("", sep, "") + def mkString(left: String, sep: String, right: String): String = { + val b = new StringBuilder(left) + var first = true + foreachInlined { elem => + if (first) first = false else b ++= sep + b ++= elem.toString + } + b ++= right + b.toString + } + + override def toString = mkString("Lst(", ", ", ")") +} + +object Lst { + + private type Arr = Array[Any] + + private def eq(x: Any, y: Any) = x.asInstanceOf[AnyRef] `eq` y.asInstanceOf[AnyRef] + private val eqFn = (x: Any, y: Any) => eq(x, y) + + val Empty = new Lst[Nothing](null) + + def apply[T](): Lst[T] = Empty + + def apply[T](x0: T): Lst[T] = new Lst[T](x0) + + def apply[T](x0: T, x1: T): Lst[T] = { + val elems = new Arr(2) + elems(0) = x0 + elems(1) = x1 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T): Lst[T] = { + val elems = new Arr(3) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T): Lst[T] = { + val elems = new Arr(4) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + new Lst[T](elems) + } + + def apply[T](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = { + val elems = new Arr(5 + xs.length) + elems(0) = x0 + elems(1) = x1 + elems(2) = x2 + elems(3) = x3 + elems(4) = x4 + xs.copyToArray(elems, 5) + new Lst[T](elems) + } + + def fill[T](n: Int)(elem: => T) = { + val elems = new Arr(n) + var i = 0 + while (i < n) { elems(i) = elem; i += 1} + new Lst[T](elems) + } + + class Buffer[T] { + private var len = 0 + private var elem: T = _ + private var elems: Arr = _ + + def size = len + + /** pre: len > 0, n > 1 */ + private def ensureSize(n: Int) = + if (len == 1) { + elems = new Arr(n `max` 16) + elems(0) = elem + } + else if (len < n) { + val newLen = n `max` len << 2 + val newElems = new Arr(newLen) + System.arraycopy(elems, 0, newElems, 0, len) + elems = newElems + } + + def += (x: T): this.type = { + if (len == 0) elem = x + else { + ensureSize(len + 1) + elems(len) = x + } + len += 1 + this + } + + def ++= (xs: Lst[T]): this.type = { + xs.elems match { + case null => this + case elems2: Arr => + if (len == 0) elems = elems2 + else { + ensureSize(len + elems2.length) + System.arraycopy(elems2, 0, elems, len, elems2.length) + } + len += elems2.length + case elem: T @unchecked => this += elem + } + this + } + + def toLst: Lst[T] = + if (len == 0) Empty + else if (len == 1) new Lst[T](elem) + else _fromArray(elems, 0, len) + + def clear() = + len = 0 + } + + private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { + val len = end - start + if (len <= 0) Empty + else if (len == 1) new Lst[T](elems(start)) + else if (start == 0 && end == elems.length) new Lst[T](elems) + else { + val newElems = new Arr(len) + System.arraycopy(elems, start, newElems, 0, len) + new Lst[T](newElems) + } + } + + def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = + _fromArray(elems.asInstanceOf[Arr], start, end) +} diff --git a/tests/run/lst-transparent/LstTest.scala b/tests/run/lst-transparent/LstTest.scala new file mode 100644 index 000000000000..87f2541fb62a --- /dev/null +++ b/tests/run/lst-transparent/LstTest.scala @@ -0,0 +1,329 @@ +object Test extends App { + import dotty.tools.dotc.util.Lst + + val xs0: Lst[String] = Lst.Empty + val xs1 = Lst("a") + val xs2 = Lst("a", "b") + val xs3 = Lst("a", "b", "c") + val xs4 = Lst("a", "b", "c", "d") + val xs5 = Lst("a", "b", "c", "d", "e") + val xs10 = xs5 ++ xs5 + + val is0: Lst[Int] = Lst.Empty + val is1 = Lst(1) + val is2 = Lst(1, 2) + val is3 = Lst(1, 2, 3) + val is4 = Lst(1, 2, 3, 4) + val is5 = Lst(1, 2, 3, 4, 5) + val is10 = is5 ++ is5 + + def lengthTest() = { + assert(xs0.length == 0) + assert(xs1.length == 1) + assert(xs2.length == 2) + assert(xs10.length == 10) + + assert(is0.length == 0) + assert(is1.length == 1) + assert(is2.length == 2) + assert(is10.length == 10) + } + + def concatTest() = { + assert(xs1 ++ xs0 == xs1) + assert(xs0 ++ xs1 == xs1) + assert(xs3 ++ xs0 == xs3) + assert(xs0 ++ xs4 == xs4) + + assert(is1 ++ is0 == is1) + assert(is0 ++ is1 == is1) + assert(is3 ++ is0 == is3) + assert(is0 ++ is4 == is4) + } + + def foreachTest() = { + xs0.foreach(s => assert(s.length == 1)) + xs3.foreach(s => assert(s.length == 1)) + def test1() = { + var x = 0 + xs10.foreach(elem => x += elem.length) + } + def test2() = { + var y = 0 + xs10.foreachInlined(elem => y += elem.length) + } + test1() + test2() + + is0.foreach(i => assert(i == 1)) + is3.foreach(i => assert(i <= 3)) + } + + def mapTest() = { + val ys0 = xs0.map(_.reverse) + val ys1 = xs1.map(s => s + s) + assert(ys1.mkString == "aa") + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + + val js0 = is0.map(i => i * i) + val js1 = is1.map(i => i + i) + assert(js1.mkString == "2") + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + } + + def mapConserveTest() = { + val ys0 = xs0.mapConserve(_.reverse) + val ys1 = xs1.mapConserve(s => s + s) + assert(ys1.mkString == "aa") + val ys2 = xs2.mapConserve(identity) + assert(ys2 `eqLst` xs2) + val ys5 = xs5.map(s => s + s) + assert(ys5.mkString == "aa, bb, cc, dd, ee") + val ys4 = xs4.mapConserve(s => if (s == "c") "cc" else s) + assert(ys4.mkString == "a, b, cc, d") + + val js0 = is0.mapConserve(i => i * i) + val js1 = is1.mapConserve(s => s + s) + assert(js1.mkString == "2") + val js2 = is2.mapConserve(identity) + assert(js2 `eqLst` is2) + val js5 = is5.map(s => s + s) + assert(js5.mkString == "2, 4, 6, 8, 10") + val js4 = is4.mapConserve(s => if (s == 3) -3 else s) + assert(js4.mkString == "1, 2, -3, 4") + } + + def flatMapTest() = { + val ys0 = xs0.flatMap(s => Lst(s, s)) + assert(ys0.isEmpty) + val ys2 = xs2.flatMap(s => Lst(s, s)) + assert(ys2.mkString == "a, a, b, b") + val ys2a = xs2.flatMap(_ => Lst.Empty) + assert(ys2a.isEmpty) + val ys4 = xs4.flatMap(s => Lst.fill(s.head - 'a')(s)) + assert(ys4.mkString == "b, c, c, d, d, d") + val ys5 = xs5.flatMap(s => if s == "c" then Lst(s) else Lst()) + assert(ys5 == Lst("c")) + + val js0 = is0.flatMap(s => Lst(s, s)) + assert(js0.isEmpty) + val js2 = is2.flatMap(s => Lst(s, s)) + assert(js2.mkString == "1, 1, 2, 2") + val js2a = is2.flatMap(_ => Lst.Empty) + assert(js2a.isEmpty) + val js4 = is4.flatMap(s => Lst.fill(s - 1)(s)) + assert(js4.mkString == "2, 3, 3, 4, 4, 4", js4) + val js5 = is5.flatMap(s => if s == 3 then Lst(-3) else Lst()) + assert(js5 == Lst(-3)) + } + + def filterTest() = { + val ys0 = xs0.filter(_.head >= 'c') + assert(ys0.isEmpty) + val ys1 = xs1.filter(_.head >= 'c') + assert(ys1.isEmpty) + val ys1a = xs1.filterNot(_.head >= 'c') + assert(ys1a `eqLst` xs1) + val ys5 = xs5.filter(_.head % 2 != 0) + assert(ys5 === Lst("a", "c", "e"), ys5) + + val js0 = is0.filter(_ > 3) + assert(js0.isEmpty) + val js1 = is1.filter(_ > 3) + assert(js1.isEmpty) + val js1a = is1.filterNot(_ > 3) + assert(js1a `eqLst` is1) + val js5 = is5.filter(_ % 2 != 0) + assert(js5 === Lst(1, 3, 5), js5) + } + + def existsTest() = { + assert(!xs0.exists(_ => true)) + assert(xs1.exists(_ == "a")) + assert(xs5.exists(_ == "c")) + assert(!xs5.exists(_.head > 'e')) + + assert(!is0.exists(_ => true)) + assert(is1.exists(_ == 1)) + assert(is5.exists(_ == 3)) + assert(!is5.exists(_ > 5)) + } + + def forallTest() = { + assert(xs0.forall(_ => false)) + assert(xs1.forall(_ == "a")) + assert(xs5.forall(_.head <= 'e')) + assert(!xs5.forall(_ == "c")) + } + + def containsTest() = { + assert(!xs0.contains("")) + assert(xs1.contains("a")) + assert(xs10.contains("e")) + assert(!xs10.contains("f")) + + assert(!is0.contains(2)) + assert(is1.contains(1)) + assert(is10.contains(5)) + assert(!is10.contains(6)) + } + + def foldTest() = { + assert(xs0.foldLeft("x")(_ ++ _) == "x") + assert(xs1.foldLeft("x")(_ ++ _) == "xa") + assert(xs2.foldLeft("x")(_ ++ _) == "xab") + assert(xs3.foldLeft("x")(_ ++ _) == "xabc") + assert(("x" /: xs0)(_ ++ _) == "x") + assert(("x" /: xs1)(_ ++ _) == "xa") + assert(("x" /: xs2)(_ ++ _) == "xab") + assert(("x" /: xs3)(_ ++ _) == "xabc") + assert(xs1.reduceLeft(_ + _) == "a") + assert(xs3.reduceLeft(_ + _) == "abc") + + assert(is0.foldLeft(3)(_ + _) == 3) + assert(is1.foldLeft(3)(_ + _) == 4) + assert(is2.foldLeft(3)(_ + _) == 6) + assert(is3.foldLeft(3)(_ + _) == 9) + assert((3 /: is0)(_ + _) == 3) + assert((3 /: is1)(_ + _) == 4) + assert((3 /: is2)(_ + _) == 6) + assert((3 /: is3)(_ + _) == 9) + assert(is1.reduceLeft(_ + _) == 1) + assert(is3.reduceLeft(_ + _) == 6) + } + + def reverseTest() = { + assert(xs0.reverse === xs0) + assert(xs1.reverse === xs1) + assert(xs3.reverse.mkString == "c, b, a", xs3.reverse.mkString) + assert(xs4.reverse.reverse === xs4, xs4.reverse.reverse) + } + + def applyTest() = { + assert(xs5.head == "a") + assert(xs5.last == "e") + assert(xs5(3) == "d") + assert(xs1(0) == "a") + + assert(is5.head == 1) + assert(is5.last == 5) + assert(is5(3) == 4) + assert(is1(0) == 1) + } + + def sliceTest() = { + assert(xs5.slice(2, 4) === Lst("c", "d")) + assert(xs5.drop(4) === Lst("e")) + assert(xs5.take(4).mkString("") == "abcd") + assert(xs5.drop(-1) `eqLst` xs5) + assert(xs1.take(1) `eqLst` xs1) + assert(xs0.take(10).length == 0) + + assert(is5.slice(2, 4) === Lst(3, 4)) + assert(is5.drop(4) === Lst(5)) + assert(is5.take(4).mkString("") == "1234") + assert(is5.drop(-1) `eqLst` is5) + assert(is1.take(1) `eqLst` is1) + assert(is0.take(10).length == 0) + } + + def zipWithTest() = { + val ys4a = xs4.zipWith(xs5)(_ + _) + val ys4b = xs5.zipWith(xs4)(_ + _) + assert(ys4a.mkString("") == "aabbccdd", ys4a) + assert(ys4a === ys4b) + val ys1a = xs1.zipWith(xs1)(_ + _) + assert(ys1a === Lst("aa")) + val ys1b = xs1.zipWith(xs2)(_ + _) + assert(ys1b === Lst("aa")) + val ys1c = xs2.zipWith(xs1)(_ + _) + assert(ys1c === Lst("aa")) + val ys0a = xs1.zipWith(xs0)(_ + _) + val ys0b = xs0.zipWith(xs1)(_ + _) + assert((ys0a ++ ys0b).isEmpty) + val ys3i = xs3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(ys3i === Lst(("a", 1), ("b", 2), ("c", 3)), ys3i) + + val js4a = is4.zipWith(is5)(_ + _) + val js4b = is5.zipWith(is4)(_ + _) + assert(js4a.mkString("") == "2468", js4a) + assert(js4a === js4b) + val js1a = is1.zipWith(is1)(_ + _) + assert(js1a === Lst(2)) + val js1b = is1.zipWith(is2)(_ + _) + assert(js1b === Lst(2)) + val js1c = is2.zipWith(is1)(_ + _) + assert(js1c === Lst(2)) + val js0a = is1.zipWith(is0)(_ + _) + val js0b = is0.zipWith(is1)(_ + _) + assert((js0a ++ js0b).isEmpty) + val js3i = is3.zipWithIndex.map((x, y) => (x, y + 1)) + assert(js3i === Lst((1, 1), (2, 2), (3, 3)), js3i) + assert(js3i.forall(_ == _)) + } + + def correspondsTest() = { + assert(xs4.corresponds(xs4)(_ == _)) + assert(!xs4.corresponds(xs5)(_ == _)) + assert(xs1.corresponds(xs1)(_ == _)) + assert(!xs1.corresponds(Lst("b"))(_ == _)) + assert(xs0.corresponds(xs0)(_ == _)) + assert(!xs0.corresponds(xs1)(_ == _)) + val zs1 = Lst(new Object, new Object) + val zs2 = Lst(zs1(0), zs1(1)) + val zs3 = Lst(new Object, new Object) + assert(zs1.eqElements(zs1)) + assert(zs1.eqElements(zs2)) + assert(!zs1.eqElements(zs3)) + + assert(is4.corresponds(is4)(_ == _)) + assert(!is4.corresponds(is5)(_ == _)) + assert(is1.corresponds(is1)(_ == _)) + assert(!is1.corresponds(Lst(-1))(_ == _)) + assert(is0.corresponds(is0)(_ == _)) + assert(!is0.corresponds(is1)(_ == _)) + } + + def bufferTest() = { + { val b = new Lst.Buffer[String] + b += "a" + assert(b.size == 1) + assert(b.toLst === Lst("a")) + b += "aa" + b ++= Lst.fill(20)("a") + assert(b.toLst.mkString("") == "a" * 23) + assert(b.size == 22) + } + + { val b = new Lst.Buffer[Int] + b += 1 + assert(b.size == 1) + assert(b.toLst === Lst(1)) + b += 11 + b ++= Lst.fill(20)(1) + assert(b.toLst.mkString("") == "1" * 23) + assert(b.size == 22) + } + } + + println("testing") + lengthTest() + concatTest() + foreachTest() + mapTest() + mapConserveTest() + flatMapTest() + filterTest() + existsTest() + forallTest() + containsTest() + foldTest() + reverseTest() + applyTest() + sliceTest() + zipWithTest() + correspondsTest() + bufferTest() +} \ No newline at end of file diff --git a/tests/run/transparent-implicits.check b/tests/run/transparent-implicits.check new file mode 100644 index 000000000000..17a1d370d730 --- /dev/null +++ b/tests/run/transparent-implicits.check @@ -0,0 +1 @@ +(X(),Y()) diff --git a/tests/run/transparent-implicits.scala b/tests/run/transparent-implicits.scala new file mode 100644 index 000000000000..bd41c41eecb9 --- /dev/null +++ b/tests/run/transparent-implicits.scala @@ -0,0 +1,26 @@ +case class X() +case class Y() + +object impl { + implicit val y: Y = new Y() +} + +object inlines { + import impl._ + + class C { + implicit val x: X = new X() + + transparent + def f(): (X, Y) = + (implicitly[X], implicitly[Y]) + } +} + +object Test { + def main(args: Array[String]) = { + val c = new inlines.C + val xy = c.f() + println(xy) + } +} diff --git a/tests/run/transparent-object.check b/tests/run/transparent-object.check new file mode 100644 index 000000000000..5716ca5987cb --- /dev/null +++ b/tests/run/transparent-object.check @@ -0,0 +1 @@ +bar diff --git a/tests/run/transparent-object.scala b/tests/run/transparent-object.scala new file mode 100644 index 000000000000..21d2566022df --- /dev/null +++ b/tests/run/transparent-object.scala @@ -0,0 +1,14 @@ + +object Test { + def main(args: Array[String]): Unit = { + Foo.foo + } +} + +object Foo extends Bar { + transparent def foo: Unit = bar +} + +class Bar { + def bar: Unit = println("bar") +} diff --git a/tests/run/transparent.check b/tests/run/transparent.check new file mode 100644 index 000000000000..5f711274b935 --- /dev/null +++ b/tests/run/transparent.check @@ -0,0 +1,9 @@ +100 +10000 + + Inner +Outer.f +Outer.f Inner + Inner +Outer.f +Outer.f Inner diff --git a/tests/run/transparent/Test_2.scala b/tests/run/transparent/Test_2.scala new file mode 100644 index 000000000000..a2ab2caca22f --- /dev/null +++ b/tests/run/transparent/Test_2.scala @@ -0,0 +1,21 @@ +object Test { + + import p.transparents._ + + def main(args: Array[String]): Unit = { + println(f(10)) + println(f(f(10))) + + track("hello") { println("") } + + val o = new Outer + val i = new o.Inner + println(i.m) + println(i.g) + println(i.h) + println(o.inner.m) + println(o.inner.g) + println(o.inner.h) + } + +} diff --git a/tests/run/transparent/inlines_1.scala b/tests/run/transparent/inlines_1.scala new file mode 100644 index 000000000000..946599b1a196 --- /dev/null +++ b/tests/run/transparent/inlines_1.scala @@ -0,0 +1,41 @@ +package p +import collection.mutable + +object transparents { + + final val monitored = false + + transparent def f(x: Int): Int = x * x + + val hits = new mutable.HashMap[String, Int] { + override def default(key: String): Int = 0 + } + + def record(fn: String, n: Int = 1) = { + if (monitored) { + val name = if (fn.startsWith("member-")) "member" else fn + hits(name) += n + } + } + + @volatile private var stack: List[String] = Nil + + transparent def track[T](fn: String)(op: => T) = + if (monitored) { + stack = fn :: stack + record(fn) + try op + finally stack = stack.tail + } else op + + class Outer { + def f = "Outer.f" + class Inner { + val msg = " Inner" + transparent def m = msg + transparent def g = f + transparent def h = f ++ m + } + val inner = new Inner + } +} diff --git a/tests/run/transparentAccess/C_1.scala b/tests/run/transparentAccess/C_1.scala new file mode 100644 index 000000000000..45db8603e8c6 --- /dev/null +++ b/tests/run/transparentAccess/C_1.scala @@ -0,0 +1,7 @@ +package p { +class C { + protected def f(): Unit = () + + transparent def inl() = f() // error (when inlined): not accessible +} +} diff --git a/tests/run/transparentAccess/Test_2.scala b/tests/run/transparentAccess/Test_2.scala new file mode 100644 index 000000000000..98ea7693abb7 --- /dev/null +++ b/tests/run/transparentAccess/Test_2.scala @@ -0,0 +1,7 @@ + +object Test { + def main(args: Array[String]) = { + val c = new p.C() + c.inl() + } +} diff --git a/tests/run/transparentArrowAssoc.scala b/tests/run/transparentArrowAssoc.scala new file mode 100644 index 000000000000..80dd0d90d3c3 --- /dev/null +++ b/tests/run/transparentArrowAssoc.scala @@ -0,0 +1,24 @@ +import scala.collection.immutable._ + +import scala.collection.mutable.{ Builder, ListBuffer } + +object Test { + + private val defaultOrdering = Map[Numeric[_], Ordering[_]]( + Numeric.BigIntIsIntegral -> Ordering.BigInt, + Numeric.IntIsIntegral -> Ordering.Int + ) + + final implicit class ArrowAssoc[A](private val self: A) extends AnyVal { + transparent def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) + def →[B](y: B): Tuple2[A, B] = ->(y) + } + + def main(args: Array[String]): Unit = { + assert((1 -> 2) == (1, 2)) + assert((1 → 2) == (1, 2)) + } + + +} + diff --git a/tests/run/transparentAssign.scala b/tests/run/transparentAssign.scala new file mode 100644 index 000000000000..4fec5fe255be --- /dev/null +++ b/tests/run/transparentAssign.scala @@ -0,0 +1,24 @@ +object Test { + + transparent def swap[T](x: T, x_= : => T => Unit, y: T, y_= : => T => Unit) = { + x_=(y) + y_=(x) + } + + transparent def f(x: Int => Unit) = x + + def main(args: Array[String]) = { + var x = 1 + var y = 2 + transparent def setX(z: Int) = x = z + transparent def setY(z: Int) = y = z + swap(x, setX, y, setY) + assert(x == 2 && y == 1) + + swap(x, x = _, y, y = _) + assert(x == 1 && y == 2) + + + val z = f(setX) // tests case where inline arg is not applied + } +} diff --git a/tests/run/transparentByName.scala b/tests/run/transparentByName.scala new file mode 100644 index 000000000000..b45451d4feda --- /dev/null +++ b/tests/run/transparentByName.scala @@ -0,0 +1,37 @@ +object Test { + + class Range(from: Int, end: Int) { + transparent def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + } + transparent def twice(op: => Int => Unit): Unit = { + op(1) + op(2) + } + transparent def thrice(op: => Unit): Unit = { + op + op + op + } + + def main(args: Array[String]) = { + var j = 0 + new Range(1, 10).foreach(j += _) + assert(j == 45, j) + twice { x => j = j - x } + thrice { j = j + 1 } + val f = new Range(1, 10).foreach + f(j -= _) + assert(j == 0, j) + new Range(1, 10).foreach { i1 => + new Range(2, 11).foreach { i2 => + j += i1 * i2 + } + } + } +} diff --git a/tests/run/transparentForeach.check b/tests/run/transparentForeach.check new file mode 100644 index 000000000000..3fced2fad78d --- /dev/null +++ b/tests/run/transparentForeach.check @@ -0,0 +1,137 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +3 +3 +3 +3 +3 +3 +3 +3 +3 +3 +4 +4 +4 +4 +4 +4 +4 +4 +4 +4 +5 +5 +5 +5 +5 +5 +5 +5 +5 +5 +6 +6 +6 +6 +6 +6 +6 +6 +6 +6 +7 +7 +7 +7 +7 +7 +7 +7 +7 +7 +8 +8 +8 +8 +8 +8 +8 +8 +8 +8 +9 +9 +9 +9 +9 +9 +9 +9 +9 +9 +10 +10 +10 +10 +10 +10 +10 +10 +10 +10 diff --git a/tests/run/transparentForeach.scala b/tests/run/transparentForeach.scala new file mode 100644 index 000000000000..eda163707093 --- /dev/null +++ b/tests/run/transparentForeach.scala @@ -0,0 +1,67 @@ +object Test { + + class Range(from: Int, end: Int) { + + transparent + def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + + def filter(p: Int => Boolean): List[Int] = ??? + } + + implicit class intWrapper(private val start: Int) extends AnyVal { + def until(end: Int) = new Range(start, end) + def to(limit: Int) = new Range(start, limit + 1) + } + + def matmul(xs: Array[Array[Double]], ys: Array[Array[Double]]): Array[Array[Double]] = { + def nrows = xs.length + def ncols = ys(0).length + def n = ys.length + assert(xs(0).length == n) + val zs = Array.ofDim[Double](nrows, ncols) + for (i <- intWrapper(0) until nrows) + for (j <- 0 until ncols) { + var x = 0.0 + for (k <- 0 until n) + x += xs(i)(k) * ys(k)(j) + zs(i)(j) = x + } + zs + } + + implicit class intArrayOps(arr: Array[Int]) { + transparent def foreach(op: => Int => Unit): Unit = { + var i = 0 + while (i < arr.length) { + op(arr(i)) + i += 1 + } + } + } + + def sum(ints: Array[Int]): Int = { + var t = 0 + for (n <- ints) t += n + t + } + + def main(args: Array[String]) = { + 1.until(10).foreach(i => println(i)) + 1.until(10).foreach(println(_)) + 1.until(10).foreach(println) + for (i <- 1 to 10) println(i) + + for (k1 <- 1 to 10) + for (k2 <- 1 to 10) + println(s"$k1") + + val xs = Array(1, 2, 3, 4) + assert(sum(xs) == 10, sum(xs)) + } +} diff --git a/tests/run/transparentPower.check b/tests/run/transparentPower.check new file mode 100644 index 000000000000..25e11563452f --- /dev/null +++ b/tests/run/transparentPower.check @@ -0,0 +1,2 @@ +1024.0 +2048.0 diff --git a/tests/run/transparentPower/Test_2.scala b/tests/run/transparentPower/Test_2.scala new file mode 100644 index 000000000000..8e16587b5653 --- /dev/null +++ b/tests/run/transparentPower/Test_2.scala @@ -0,0 +1,9 @@ +import p.pow.power +object Test { + + def main(args: Array[String]): Unit = { + println(power(2.0, 10)) + def x = 2.0 + println(power(x, 11)) + } +} diff --git a/tests/run/transparentPower/power_1.scala b/tests/run/transparentPower/power_1.scala new file mode 100644 index 000000000000..ef43488ce387 --- /dev/null +++ b/tests/run/transparentPower/power_1.scala @@ -0,0 +1,12 @@ +package p + +object pow { + + transparent def power(x: Double, n: Int): Double = + if (n == 0) 1.0 + else if (n == 1) x + else { + val y = power(x, n / 2) + if (n % 2 == 0) y * y else y * y * x + } +} diff --git a/tests/run/transparentPrivates.scala b/tests/run/transparentPrivates.scala new file mode 100644 index 000000000000..8498203af9ba --- /dev/null +++ b/tests/run/transparentPrivates.scala @@ -0,0 +1,36 @@ +object Test { + + class C[T](private val x: T) { + + private def foo[Z](z: Z): T = x + + private var y: T = _ + + transparent def get1 = x + transparent def get2[U](c: C[U]) = c.x + + transparent def foo1(x: Int) = foo(x) + transparent def foo2[U](c: C[U]) = c.foo(x) + + transparent def set1(z: T) = { y = z; y } + transparent def set2[U](c: C[U]) = { c.y = c.x; c.y } + } + + object CC { + private val x = 3 + transparent def get1 = x + } + + def main(args: Array[String]) = { + val cc = new C(2) + assert(cc.get1 == 2) + assert(cc.get2(cc) == 2) + assert(cc.foo1(1) == 2) + assert(cc.foo2(cc) == 2) + assert(cc.set1(3) == 3) + assert(cc.set2(cc) == 2) + + assert(CC.get1 == 3) + } + +} diff --git a/tests/run/transparentProtected.scala b/tests/run/transparentProtected.scala new file mode 100644 index 000000000000..478f599b2293 --- /dev/null +++ b/tests/run/transparentProtected.scala @@ -0,0 +1,22 @@ +package P { + class C { + protected def f(): Int = 22 + } +} + +package Q { + class D extends P.C { + class Inner { + transparent def g() = f() + } + } +} + +object Test extends App { + import Q._ + + val d = new D + val i = new d.Inner + val x = i.g() + assert(x == 22) +} diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 9a836565ef51..737943b59753 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -17,14 +17,14 @@ object Test extends App { } val x0 = ToNat(0) - //val y0: Z = x0.value - //val z0: x0.Result = y0 + val y0: Z = x0.value + val z0: x0.Result = y0 val x1 = ToNat(1) - //val y1: S[Z] = x1.value - //val z1: x1.Result = y1 + val y1: S[Z] = x1.value + val z1: x1.Result = y1 val x2 = ToNat(2) - //val y2: S[S[Z]] = x2.value - //val z2: x2.Result = y2 + val y2: S[S[Z]] = x2.value + val z2: x2.Result = y2 println(x0) println(x1) println(x2) From 34bc5c4d8bc3ba302bcee5098e07e60a07ffe04c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:11:40 +0200 Subject: [PATCH 06/62] Pretype nested transparent applications This lets us write HList concat in the most trivial way possible. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 23 +++- tests/run/typelevel.scala | 120 ++++++++++++++++-- 2 files changed, 130 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 4b069c437a5d..1fa9707c2be4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -616,7 +616,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree }} - val inlineTyper = if (meth.isTransparentMethod) new InlineTyper else new InlineReTyper + val inlineTyper = if (meth.isTransparentMethod) new TransparentTyper else new InlineReTyper val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope // The complete translation maps references to `this` and parameters to @@ -644,7 +644,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { expansion } - trace(i"inlining $call\n, BINDINGS =\n${bindingsBuf.toList}%\n%\nEXPANSION =\n$expansion", inlining, show = true) { + trace(i"inlining $call", inlining, show = true) { // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -652,8 +652,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** All bindings in `bindingsBuf` except bindings of inlineable closures */ val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) - inlining.println(i"original bindings = $bindings%\n%") - inlining.println(i"original expansion = $expansion1") + if (ctx.settings.verbose.value) { + inlining.println(i"original bindings = $bindings%\n%") + inlining.println(i"original expansion = $expansion1") + } val (finalBindings, finalExpansion) = dropUnusedDefs(bindings, expansion1) @@ -745,13 +747,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } /** A full typer used for transparent methods */ - private class InlineTyper extends Typer with InlineTyping { + private class TransparentTyper extends Typer with InlineTyping { override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = tree.splice match { case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs case _ => super.typedTypedSplice(tree) } + + /** Pre-type any nested calls to transparent methods. Otherwise the declared result type + * of these methods can influence constraints + */ + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { + def typeTransparent(tree: untpd.Tree): untpd.Tree = + if (tree.symbol.isTransparentMethod) untpd.TypedSplice(typed(tree)) + else tree + val tree1 = tree.args.mapConserve(typeTransparent) + super.typedApply(untpd.cpy.Apply(tree)(tree.fun, tree1), pt) + } } /** A re-typer used for inlined methods */ diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 737943b59753..b88c562010cb 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -1,19 +1,51 @@ -object Test extends App { - trait Nat +trait Nat { + def toInt: Int +} - case object Z extends Nat - type Z = Z.type - case class S[N <: Nat](n: Nat) extends Nat +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +trait HList { + def length: Int + def head: Any + def tail: HList + transparent def isEmpty: Boolean = + length == 0 +} + +// () +case object HNil extends HList { + transparent def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +// (H, T) +case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} - abstract class HasResult[T] { type Result = T } - case class ToNat[+T](val value: T) extends HasResult[T] +case class ToNat[T](val value: T) { + type Result = T +} + +object Test extends App { + type HNil = HNil.type + type Z = Z.type transparent def ToNat(inline n: Int): ToNat[Nat] = if n == 0 then new ToNat(Z) else { val n1 = ToNat(n - 1) - new ToNat[S[n1.Result]](S(n1.value)) + new ToNat(S(n1.value)) } val x0 = ToNat(0) @@ -28,4 +60,76 @@ object Test extends App { println(x0) println(x1) println(x2) + transparent val i0 = y0.toInt + val j0: 0 = i0 + transparent val i2 = y2.toInt + val j2: 2 = i2 + + transparent def concat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, concat(xs.tail, ys)) + + val xs = HCons(1, HCons("a", HNil)) + + val r0 = concat(HNil, HNil) + val r1 = concat(HNil, xs) + val r2 = concat(xs, HNil) + val r3 = concat(xs, xs) + + val r4 = concat(HNil, HCons(1, HCons("a", HNil))) + val r5 = concat(HCons(1, HCons("a", HNil)) , HNil) + val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) + + transparent def size(xs: HList): Nat = + if xs.isEmpty then Z + else S(size(xs.tail)) + + transparent def sizeDefensive(xs: HList): Nat = xs.isEmpty match { + case true => Z + case false => S(sizeDefensive(xs.tail)) + } + + val s0 = size(HNil) + val s1 = size(xs) + transparent val l0 = HNil.length + val l0a: 0 = l0 + transparent val l1 = xs.length + val l1a: 2 = l1 + + transparent def index(xs: HList, inline idx: Int): Any = + if idx == 0 then xs.head + else index(xs.tail, idx - 1) + + val s2 = index(xs, 0) + val ss2: Int = s2 + val s3 = index(xs, 1) + var ss3: String = s3 + def s4 = index(xs, 2) + def ss4: Nothing = s4 + val s5 = index(xs, xs.length - 1) + val ss5: String = s5 +/* + transparent def toInt1[T]: Int = type T match { + case Z => 0 + case S[type N] => toInt[N] + 1 + } + + transparent def toInt1[T]: Nat = implicit match { + case C[type T, type U], T =:= U => 0 + case T <:< S[type N] => toInt[N] + 1 + } +*/ +/** Does not work yet: + + implicit class HListDeco(transparent val xs: HList) { + transparent def ++ (ys: HList) = concat(xs, ys) + } + + val rr0 = HNil ++ HNil + val rr1 = HNil ++ xs + val rr2 = xs ++ HNil + val rr3 = xs ++ xs + val rr3a: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = rr3 + +*/ } \ No newline at end of file From d7a3430158d01f5ec51f9724d2af48af3b50d625 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:12:26 +0200 Subject: [PATCH 07/62] Better error position when inline call limit exceeded It used to be the position of the last inline call, which is not very helpful. Now it is the position of the first inline call, which started the recursive inline expansion. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 3 ++- tests/neg/power.scala | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 1fa9707c2be4..770ebcf284c9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -384,7 +384,8 @@ object Inliner { tree, i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, |Maybe this is caused by a recursive inline method? - |You can use -Xmax:inlines to change the limit.""" + |You can use -Xmax:inlines to change the limit.""", + (tree :: enclosingInlineds).last.pos ) /** Replace `Inlined` node by a block that contains its bindings and expansion */ diff --git a/tests/neg/power.scala b/tests/neg/power.scala index d5098fc215ed..91a38a825b22 100644 --- a/tests/neg/power.scala +++ b/tests/neg/power.scala @@ -4,11 +4,11 @@ object Test { if (n == 0) 1.0 else if (n == 1) x else { - val y = power(x, n / 2) // error: maximal number of inlines exceeded + val y = power(x, n / 2) if (n % 2 == 0) y * y else y * y * x } def main(args: Array[String]): Unit = { - println(power(2.0, args.length)) + println(power(2.0, args.length)) // error: maximal number of inlines exceeded } } From 097043f43c54e49f2d1172c2c341e26c8d492602 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:25:36 +0200 Subject: [PATCH 08/62] Some refactorings to Inliner and Typer --- .../src/dotty/tools/dotc/typer/Inliner.scala | 20 ++++++++++--------- .../src/dotty/tools/dotc/typer/Typer.scala | 13 ++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 770ebcf284c9..093d6f136c17 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -635,7 +635,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // Note: Substituting new symbols does not automatically lead to good prefixes // if the previous symbol was owned by a class. That's why we need to set the type // of `idef` explicitly. It would be nice if substituters were smarter, but - // it seems non-trivial to come up with rules that work in + // it seems non-trivial to come up with rules that work in all cases. inlineCtx.enter(idef.symbol) } expansion @@ -694,6 +694,14 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ trait InlineTyping extends Typer { + protected def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { @@ -751,10 +759,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private class TransparentTyper extends Typer with InlineTyping { override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tree.splice match { - case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs - case _ => super.typedTypedSplice(tree) - } + tryInline(tree.splice) `orElse` super.typedTypedSplice(tree) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints @@ -785,10 +790,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = - tree.asInstanceOf[tpd.Tree] match { - case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs"); rhs - case _ => super.typedIdent(tree, pt) - } + tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.hasType, tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 69afc643404d..806fc86626fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2378,12 +2378,13 @@ class Typer extends Namer else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) checkInlineConformant(tree, isFinal = false, "argument to inline parameter") - if (Inliner.hasBodyToInline(tree.symbol) && - !ctx.owner.ownersIterator.exists(_.isInlineableMethod) && - !ctx.settings.YnoInline.value && - !ctx.isAfterTyper && - !ctx.reporter.hasErrors) - readaptSimplified(Inliner.inlineCall(tree, pt)) + def suppressInline = + ctx.owner.ownersIterator.exists(_.isInlineableMethod) || + ctx.settings.YnoInline.value || + ctx.isAfterTyper || + ctx.reporter.hasErrors + if (Inliner.hasBodyToInline(tree.symbol) && !suppressInline) + readaptSimplified(Inliner.inlineCall(tree, pt)) else if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, From 4aeeed6fa3e8eda85303d9a373f14c530bc12f4b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:26:51 +0200 Subject: [PATCH 09/62] Reduce projections of data type fields in inliner Reduce terms equivalent to `new C(args).x` to `arg_i` if `x` refers to parameter `i` of class `C` and `C`'s constructor does not have side effects. A class is pure for the purpose of reducing projections in inlinig if none of its baseclasses has an initializer. To make this robust wrt compilation order, we need to move computation of NoInits flags from Typer to the class completer. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 105 +++++++++++++++++- .../src/dotty/tools/dotc/typer/Typer.scala | 1 - tests/neg/typelevel.scala | 58 ++++++++++ tests/run/typelevel.scala | 32 +++--- tests/run/typelevel1.scala | 52 +++++++++ 5 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 tests/neg/typelevel.scala create mode 100644 tests/run/typelevel1.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 093d6f136c17..97f6e0319df4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -41,6 +41,15 @@ object Inliner { def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = methPart(tree).putAttachment(ContextualImplicit, ()) + /** A key to be used in a context property that provides a map from enclosing implicit + * value bindings to their right hand sides. + */ + private val InlineBindings = new Property.Key[MutableSymbolMap[Tree]] + + /** A map from the symbols of all enclosing inline value bindings to their right hand sides */ + def inlineBindings(implicit ctx: Context): MutableSymbolMap[Tree] = + ctx.property(InlineBindings).get + class InlineAccessors extends AccessProxies { /** If an inline accessor name wraps a unique inline name, this is taken as indication @@ -288,7 +297,7 @@ object Inliner { case _: Ident | _: This => //println(i"leaf: $tree at ${tree.pos}") if (isExternal(tree.symbol)) { - inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") typeAtPos(tree.pos.toSynthetic) = tree.tpe } case _: Select if tree.symbol.name.is(InlineAccessorName) => @@ -378,7 +387,13 @@ object Inliner { def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors - if (ctx.reporter.hasErrors) tree else new Inliner(tree, body).inlined(pt) + if (ctx.reporter.hasErrors) tree + else { + val inlinerCtx = + if (ctx.property(InlineBindings).isDefined) ctx + else ctx.fresh.setProperty(InlineBindings, newMutableSymbolMap[Tree]) + new Inliner(tree, body)(inlinerCtx).inlined(pt) + } } else errorTree( tree, @@ -645,14 +660,35 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { expansion } + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + * Also, set position to the one of the inline call. + */ + def normalizeBinding(binding: ValOrDefDef)(implicit ctx: Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + inlineBindings(inlineCtx).put(binding.symbol, rhs1) + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) + } + case _ => + binding + } + binding1.withPos(call.pos) + } + trace(i"inlining $call", inlining, show = true) { + /** All bindings in `bindingsBuf` */ + val bindings = bindingsBuf.toList.map(normalizeBinding) + // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) - /** All bindings in `bindingsBuf` except bindings of inlineable closures */ - val bindings = bindingsBuf.toList.map(_.withPos(call.pos)) - if (ctx.settings.verbose.value) { inlining.println(i"original bindings = $bindings%\n%") inlining.println(i"original expansion = $expansion1") @@ -664,6 +700,60 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } + /** An extractor for terms equivalent to `new C(args)`, returning the class `C` + * and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + */ + object NewInstance { + def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix) => (cls, reduced, bindings ::: prefix) + } + tree match { + case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => + Some((tpt.tpe.classSymbol, args, Nil)) + case Ident(_) => + inlineBindings.get(tree.symbol).flatMap(unapply) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isPureExpr(tree) => + unapplyLet(stats, expr) + case _ => + None + } + } + } + + /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` + * where class `C` does not have initialization code and `x` is a parameter corresponding + * to one of the arguments `args`, the corresponding argument, otherwise `tree` itself. + */ + def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { + if (meth.isTransparentMethod) { + if (ctx.debug) inlining.println(i"try reduce projection $tree") + tree match { + case Select(NewInstance(cls, args, prefix), field) if cls.isNoInitsClass => + def matches(param: Symbol, selection: Symbol): Boolean = + param == selection || { + selection.name match { + case InlineAccessorName(underlying) => + param.name == underlying && selection.info.isInstanceOf[ExprType] + case _ => + false + } + } + val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) + if (idx >= 0 && idx < args.length) { + inlining.println(i"projecting $tree -> ${args(idx)}") + return seq(prefix, args(idx)) + } + case _ => + } + } + tree + } + /** An extractor for references to inlineable arguments. These are : * - by-value arguments marked with `inline` * - all by-name arguments @@ -759,7 +849,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private class TransparentTyper extends Typer with InlineTyping { override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - tryInline(tree.splice) `orElse` super.typedTypedSplice(tree) + reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = + reduceProjection(super.typedSelect(tree, pt)) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 806fc86626fb..4313343034ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2252,7 +2252,6 @@ class Typer extends Namer } val args = eraseErasedArgs(implicitArgs(wtp.paramInfos)) - def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => arg.tpe match { diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala new file mode 100644 index 000000000000..f75c425a4e9d --- /dev/null +++ b/tests/neg/typelevel.scala @@ -0,0 +1,58 @@ +trait HList { + def length: Int + def head: Any + def tail: HList + transparent def isEmpty: Boolean = + length == 0 +} + +// () +case object HNil extends HList { + transparent def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +// (H, T) +case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +object Test { + transparent def concat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, concat(xs.tail, ys)) + + class Deco(private val as: HList) { + transparent def ++ (bs: HList) = concat(as, bs) + } + + class Deco0(val as: HList) { + println("HI") + transparent def ++ (bs: HList) = concat(as, bs) + } + + class Eff { + println("HI") + } + class Deco1(val as: HList) extends Eff { + transparent def ++ (bs: HList) = concat(as, bs) + } + + // Test that selections from impure classes cannot be projected away + + val rr = new Deco(HCons(1, HNil)) ++ HNil + val rra: HCons[Int, HNil.type] = rr // ok + val rr2 = new Deco2(HCons(1, HNil)) ++ HNil + val rr2a: HCons[Int, HNil.type] = rr2 // ok + val rr0 = new Deco0(HCons(1, HNil)) ++ HNil + val rr0a: HCons[Int, HNil.type] = rr0 // error (type error because no inline) + val rr1 = new Deco1(HCons(1, HNil)) ++ HNil + val rr1a: HCons[Int, HNil.type] = rr1 // error (type error because no inline) + + class Deco2(val as: HList) extends java.lang.Cloneable with java.lang.Comparable[Deco2] { + transparent def ++ (bs: HList) = concat(as, bs) + } +} \ No newline at end of file diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index b88c562010cb..7181301d75cf 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -27,7 +27,8 @@ case object HNil extends HList { } // (H, T) -case class HCons[H, T <: HList](hd: H, tl: T) extends HList { +@annotation.showAsInfix(true) +case class HCons [H, T <: HList](hd: H, tl: T) extends HList { transparent def length = 1 + tl.length def head: H = this.hd def tail: T = this.tl @@ -65,6 +66,10 @@ object Test extends App { transparent val i2 = y2.toInt val j2: 2 = i2 + class HListDeco(private val as: HList) extends AnyVal { + transparent def :: [H] (a: H) = HCons(a, as) + transparent def ++ (bs: HList) = concat(as, bs) + } transparent def concat(xs: HList, ys: HList): HList = if xs.isEmpty then ys else HCons(xs.head, concat(xs.tail, ys)) @@ -108,6 +113,18 @@ object Test extends App { def ss4: Nothing = s4 val s5 = index(xs, xs.length - 1) val ss5: String = s5 + + + //val ys = 1 :: "a" :: HNil + + transparent implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) + + val rr0 = new HListDeco(HNil).++(HNil) + val rr1 = HNil ++ xs + val rr2 = xs ++ HNil + val rr3 = xs ++ xs + val rr3a: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = rr3 + /* transparent def toInt1[T]: Int = type T match { case Z => 0 @@ -119,17 +136,4 @@ object Test extends App { case T <:< S[type N] => toInt[N] + 1 } */ -/** Does not work yet: - - implicit class HListDeco(transparent val xs: HList) { - transparent def ++ (ys: HList) = concat(xs, ys) - } - - val rr0 = HNil ++ HNil - val rr1 = HNil ++ xs - val rr2 = xs ++ HNil - val rr3 = xs ++ xs - val rr3a: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = rr3 - -*/ } \ No newline at end of file diff --git a/tests/run/typelevel1.scala b/tests/run/typelevel1.scala new file mode 100644 index 000000000000..3a490b596384 --- /dev/null +++ b/tests/run/typelevel1.scala @@ -0,0 +1,52 @@ + +trait HList { + def length: Int + def head: Any + def tail: HList + + transparent def isEmpty: Boolean = length == 0 +} + +case object HNil extends HList { + transparent def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class :: [H, T <: HList] (hd: H, tl: T) extends HList { + transparent def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +object Test extends App { + type HNil = HNil.type + + class HListDeco(val as: HList) extends AnyVal { + transparent def :: [H] (a: H): HList = new :: (a, as) + transparent def ++ (bs: HList): HList = concat(as, bs) + transparent def apply(idx: Int): Any = index(as, idx) + } + + transparent implicit def hlistDeco(xs: HList): HListDeco = new HListDeco(xs) + + transparent def concat[T1, T2](xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else new ::(xs.head, concat(xs.tail, ys)) + + val xs = 1 :: "a" :: "b" :: HNil + val ys = true :: 1.0 :: HNil + val zs = concat(xs, ys) + + val control: Int :: String :: String :: Boolean :: Double :: HNil = zs + + transparent def index(xs: HList, idx: Int): Any = + if idx == 0 then xs.head + else index(xs.tail, idx - 1) + + val zs0 = index(zs, 0) + val zs1 = zs(1) + val zs2 = zs(2) + val zs3 = zs(3) + def zs4 = zs(4) +} \ No newline at end of file From 5896f2872f99f7238a01b59b360052ca5d5c24db Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:27:17 +0200 Subject: [PATCH 10/62] Replace inlined pure constant expressions by literals Since the expression is inlined, we will never need its constituents for hyperlinking. So we don't lose editing functionality by replacing the epxression with a literal. Because of other reductions we might end up with large constant expressions that are worth collapsing. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 97f6e0319df4..75d5a30eb047 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -841,7 +841,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree } - betaReduce(super.typedApply(tree, pt)) + constToLiteral(betaReduce(super.typedApply(tree, pt))) } } @@ -852,7 +852,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = - reduceProjection(super.typedSelect(tree, pt)) + constToLiteral(reduceProjection(super.typedSelect(tree, pt))) /** Pre-type any nested calls to transparent methods. Otherwise the declared result type * of these methods can influence constraints diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 4313343034ff..70a17dde0d9a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2122,7 +2122,7 @@ class Typer extends Namer adapt1(tree, pt, locked) } - def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { + final def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { adapt(tree, pt, ctx.typerState.ownedVars) } From dc5c3a075701fab7cfdca394ad2ebec163e8d31c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:30:47 +0200 Subject: [PATCH 11/62] Refine handling of result types of transparent methods 1. Inline transparent methods even if result type does not match Inlining transparent methods can improve the result type, so we should do this anyway even if the original result type does not match. 2. Don't constrain result type of inlineable transparent methods We need to inline the method in this case before constraining the result type. This more systematic solution eliminates an annoying special case for typing Apply nodes in the inliner. --- .../dotty/tools/dotc/typer/Applications.scala | 18 ++++++------ .../dotty/tools/dotc/typer/Inferencing.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 29 +++++++++++-------- .../dotty/tools/dotc/typer/ProtoTypes.scala | 6 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 24 ++++++--------- tests/run/typelevel.scala | 10 ++++++- 6 files changed, 51 insertions(+), 38 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bcf5493396b0..46fc35eac331 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -243,7 +243,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => case methType: MethodType => // apply the result type constraint, unless method type is dependent val resultApprox = resultTypeApprox(methType) - if (!constrainResult(resultApprox, resultType)) + if (!constrainResult(methRef.symbol, resultApprox, resultType)) if (ctx.typerState.isCommittable) // defer the problem until after the application; // it might be healed by an implicit conversion @@ -708,7 +708,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => // help sharpen the inferred parameter types for the argument function literal(s). // This tweak is needed to make i1378 compile. if (tree.args.exists(untpd.isFunctionWithUnknownParamType(_))) - if (!constrainResult(fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) + if (!constrainResult(tree.symbol, fun1.tpe.widen, proto.derivedFunProto(resultType = pt))) typr.println(i"result failure for $tree with type ${fun1.tpe.widen}, expected = $pt") /** Type application where arguments come from prototype, and no implicits are inserted */ @@ -1280,12 +1280,12 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * section conforms to the expected type `resultType`? If `resultType` * is a `IgnoredProto`, pick the underlying type instead. */ - def resultConforms(alt: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { - case IgnoredProto(ignored) => resultConforms(alt, ignored) + def resultConforms(altSym: Symbol, altType: Type, resultType: Type)(implicit ctx: Context): Boolean = resultType match { + case IgnoredProto(ignored) => resultConforms(altSym, altType, ignored) case _: ValueType => - alt.widen match { - case tp: PolyType => resultConforms(constrained(tp).resultType, resultType) - case tp: MethodType => constrainResult(tp.resultType, resultType) + altType.widen match { + case tp: PolyType => resultConforms(altSym, constrained(tp).resultType, resultType) + case tp: MethodType => constrainResult(altSym, tp.resultType, resultType) case _ => true } case _ => true @@ -1304,9 +1304,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * do they prune much, on average. */ def adaptByResult(chosen: TermRef) = pt match { - case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen, pt.resultType)) => + case pt: FunProto if !ctx.test(implicit ctx => resultConforms(chosen.symbol, chosen, pt.resultType)) => val conformingAlts = alts.filter(alt => - (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt, pt.resultType))) + (alt ne chosen) && ctx.test(implicit ctx => resultConforms(alt.symbol, alt, pt.resultType))) conformingAlts match { case Nil => chosen case alt2 :: Nil => alt2 diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index f0611878b666..dfab0ca08fa2 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -408,7 +408,7 @@ trait Inferencing { this: Typer => val resultAlreadyConstrained = tree.isInstanceOf[Apply] || tree.tpe.isInstanceOf[MethodOrPoly] if (!resultAlreadyConstrained) - constrainResult(tree.tpe, pt) + constrainResult(tree.symbol, tree.tpe, pt) // This is needed because it could establish singleton type upper bounds. See i2998.scala. val tp = tree.tpe.widen diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 75d5a30eb047..43e7b9eaa77f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -376,7 +376,23 @@ object Inliner { def bodyToInline(sym: SymDenotation)(implicit ctx: Context): Tree = sym.unforcedAnnotation(defn.BodyAnnot).get.tree - /** Try to inline a call to a `inline` method. Fail with error if the maximal + /** Should call with method `meth` be inlined in this context? */ + def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { + + def suppressInline = + ctx.owner.ownersIterator.exists(_.isInlineableMethod) || + ctx.settings.YnoInline.value || + ctx.isAfterTyper || + ctx.reporter.hasErrors + + hasBodyToInline(meth) && !suppressInline + } + + /** Is `meth` a transparent method that should be inlined in this context? */ + def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = + meth.isTransparentMethod && isInlineable(meth) + + /** Try to inline a call to a `@inline` method. Fail with error if the maximal * inline depth is exceeded. * * @param tree The call to inline @@ -853,17 +869,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = constToLiteral(reduceProjection(super.typedSelect(tree, pt))) - - /** Pre-type any nested calls to transparent methods. Otherwise the declared result type - * of these methods can influence constraints - */ - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { - def typeTransparent(tree: untpd.Tree): untpd.Tree = - if (tree.symbol.isTransparentMethod) untpd.TypedSplice(typed(tree)) - else tree - val tree1 = tree.args.mapConserve(typeTransparent) - super.typedApply(untpd.cpy.Apply(tree)(tree.fun, tree1), pt) - } } /** A re-typer used for inlined methods */ diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 5a32fcf19364..f38fb6db6e37 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -71,6 +71,12 @@ object ProtoTypes { if (!res) ctx.typerState.resetConstraintTo(savedConstraint) res } + + /** Constrain result unless `meth` is a transparent method in an inlineable context. + * In the latter case we should inline before constraining the result. + */ + def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = + Inliner.isTransparentInlineable(meth) || constrainResult(mt, pt) } object NoViewsAllowed extends Compatibility { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 70a17dde0d9a..85e242347bed 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2114,12 +2114,10 @@ class Typer extends Namer * If all this fails, error * Parameters as for `typedUnadapted`. */ - def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = /*>|>*/ track("adapt") /*<|<*/ { - def showWithType(x: Any) = x match { - case tree: tpd.Tree @unchecked => i"$tree of type ${tree.tpe}" - case _ => String.valueOf(x) + def adapt(tree: Tree, pt: Type, locked: TypeVars)(implicit ctx: Context): Tree = track("adapt") { + trace(i"adapting $tree to $pt", typr, show = true) { + adapt1(tree, pt, locked) } - adapt1(tree, pt, locked) } final def adapt(tree: Tree, pt: Type)(implicit ctx: Context): Tree = { @@ -2359,7 +2357,7 @@ class Typer extends Namer missingArgs(wtp) } - def adaptNoArgsOther(wtp: Type) = { + def adaptNoArgsOther(wtp: Type): Tree = { ctx.typeComparer.GADTused = false if (defn.isImplicitFunctionClass(wtp.underlyingClassRef(refinementOK = false).classSymbol) && !untpd.isImplicitClosure(tree) && @@ -2374,17 +2372,13 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } + else if (Inliner.isInlineable(tree.symbol) && + (tree.symbol.isTransparentMethod || tree.tpe <:< pt)) + readaptSimplified(Inliner.inlineCall(tree, pt)) else if (tree.tpe <:< pt) { if (pt.hasAnnotation(defn.InlineParamAnnot)) checkInlineConformant(tree, isFinal = false, "argument to inline parameter") - def suppressInline = - ctx.owner.ownersIterator.exists(_.isInlineableMethod) || - ctx.settings.YnoInline.value || - ctx.isAfterTyper || - ctx.reporter.hasErrors - if (Inliner.hasBodyToInline(tree.symbol) && !suppressInline) - readaptSimplified(Inliner.inlineCall(tree, pt)) - else if (ctx.typeComparer.GADTused && pt.isValueType) + if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, // if the expected type is a supertype of the GADT bound. It would be good to come @@ -2416,7 +2410,7 @@ class Typer extends Namer def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) lazy val functionExpected = defn.isFunctionType(ptNorm) - lazy val resultMatch = constrainResult(wtp, followAlias(pt)) + lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 7181301d75cf..a137b554a91a 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -125,6 +125,14 @@ object Test extends App { val rr3 = xs ++ xs val rr3a: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = rr3 + transparent def f(c: Boolean): Nat = { + def g[X <: Nat](x: X): X = x + g(if (c) Z else S(Z)) + } + + val f1: Z = f(true) + val f2: S[Z] = f(false) + /* transparent def toInt1[T]: Int = type T match { case Z => 0 @@ -132,7 +140,7 @@ object Test extends App { } transparent def toInt1[T]: Nat = implicit match { - case C[type T, type U], T =:= U => 0 + case C[type T, type U], T =:= U => case T <:< S[type N] => toInt[N] + 1 } */ From de8f2fd390f7ab3c206703611e41d682179f88c3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 15:08:30 +0200 Subject: [PATCH 12/62] Don't drop impure arguments when reducing projections --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 7 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 17 ++- tests/run/reduce-projections.check | 59 +++++++++ tests/run/reduce-projections.scala | 122 ++++++++++++++++++ 4 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 tests/run/reduce-projections.check create mode 100644 tests/run/reduce-projections.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 974df76eb501..a8f60aa5572d 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1043,14 +1043,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else (trees.head.tpe eq trees1.head.tpe) && sameTypes(trees.tail, trees1.tail) } - def evalOnce(tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = { - if (isIdempotentExpr(tree)) within(tree) + def letBindUnless(level: TreeInfo.PurityLevel, tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = { + if (exprPurity(tree) >= level) within(tree) else { val vdef = SyntheticValDef(TempResultName.fresh(), tree) Block(vdef :: Nil, within(Ident(vdef.namedType))) } } + def evalOnce(tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = + letBindUnless(TreeInfo.Idempotent, tree)(within) + def runtimeCall(name: TermName, args: List[Tree])(implicit ctx: Context): Tree = { Ident(defn.ScalaRuntimeModule.requiredMethod(name).termRef).appliedToArgs(args) } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 43e7b9eaa77f..a7a0047e7f44 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -28,6 +28,7 @@ import transform.TypeUtils._ import reporting.trace import util.Positions.Position import util.Property +import ast.TreeInfo object Inliner { import tpd._ @@ -743,7 +744,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` * where class `C` does not have initialization code and `x` is a parameter corresponding - * to one of the arguments `args`, the corresponding argument, otherwise `tree` itself. + * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation + * of impure arguments, otherwise `tree` itself. */ def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { if (meth.isTransparentMethod) { @@ -761,8 +763,17 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } val idx = cls.asClass.paramAccessors.indexWhere(matches(_, tree.symbol)) if (idx >= 0 && idx < args.length) { - inlining.println(i"projecting $tree -> ${args(idx)}") - return seq(prefix, args(idx)) + def collectImpure(from: Int, end: Int) = + (from until end).filterNot(i => isPureExpr(args(i))).toList.map(args) + val leading = collectImpure(0, idx) + val trailing = collectImpure(idx + 1, args.length) + val arg = args(idx) + val argInPlace = + if (trailing.isEmpty) arg + else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) + val reduced = seq(prefix, seq(leading, argInPlace)) + inlining.println(i"projecting $tree -> ${reduced}") + return reduced } case _ => } diff --git a/tests/run/reduce-projections.check b/tests/run/reduce-projections.check new file mode 100644 index 000000000000..b09296cec78b --- /dev/null +++ b/tests/run/reduce-projections.check @@ -0,0 +1,59 @@ +1 +2 +3 +4 +1 +1 +2 +3 +4 +2 +1 +2 +3 +4 +3 +1 +2 +3 +4 +4 +=== +2 +3 +4 +1 +1 +3 +4 +2 +1 +2 +4 +3 +1 +2 +3 +4 +=== +2 +3 +1 +3 +2 +2 +3 +2 +3 +4 +=== +2 +3 +1 +3 +2 +2 +3 +2 +3 +4 diff --git a/tests/run/reduce-projections.scala b/tests/run/reduce-projections.scala new file mode 100644 index 000000000000..fc4099100404 --- /dev/null +++ b/tests/run/reduce-projections.scala @@ -0,0 +1,122 @@ + +class C(val x1: Int, val x2: Int, val x3: Int, val x4: Int) + +object Test { + + class D(n: Int) { + println(n) + def result = n + } + object O2 extends D(2) + object O2a extends D(2) + object O2b extends D(2) + object O3 extends D(3) + object O3a extends D(3) + object O3b extends D(3) + + transparent def f(): Unit = { + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x1) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x2) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x3) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x4) + println("===") + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x1) + println(new C( + { println(1); 1 }, + { 2 }, + { println(3); 3 }, + { println(4); 4 } + ).x2) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { 3 }, + { println(4); 4 } + ).x3) + println(new C( + { println(1); 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x4) + println("===") + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x1) + println(new C( + { 1 }, + { 2 }, + { println(3); 3 }, + { 4 } + ).x2) + println(new C( + { 1 }, + { println(2); 2 }, + { 3 }, + { 4 } + ).x3) + println(new C( + { 1 }, + { println(2); 2 }, + { println(3); 3 }, + { 4 } + ).x4) + println("===") + println(new C( + { 1 }, + O2.result, + O3.result, + { 4 } + ).x1) + println(new C( + { 1 }, + { 2 }, + O3a.result, + { 4 } + ).x2) + println(new C( + { 1 }, + O2a.result, + { 3 }, + { 4 } + ).x3) + println(new C( + { 1 }, + O2b.result, + O3b.result, + { 4 } + ).x4) + } + + def main(args: Array[String]): Unit = { + f() + } +} \ No newline at end of file From 632abc1dc20bb07cf3633781e37c6bb566e144f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 15:10:01 +0200 Subject: [PATCH 13/62] Exclude members of Predef from implicit inline environment Members of Predef are effectively global. It's therefore surprising that they should be part of the envrionment that comes with a transparent function. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 29 ++++++++++--------- tests/run/typelevel.scala | 14 +++++++++ 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index a7a0047e7f44..83650e0e863c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -40,7 +40,8 @@ object Inliner { private val ContextualImplicit = new Property.StickyKey[Unit] def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = - methPart(tree).putAttachment(ContextualImplicit, ()) + if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) + methPart(tree).putAttachment(ContextualImplicit, ()) /** A key to be used in a context property that provides a map from enclosing implicit * value bindings to their right hand sides. @@ -819,6 +820,19 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { EmptyTree } + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { + tpe match { + case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => + tpe.info match { + case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) + case info: ConstantType if tpe.symbol.isStable => return info + case _ => + } + case _ => + } + super.ensureAccessible(tpe, superAccess, pos) + } + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { @@ -885,19 +899,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A re-typer used for inlined methods */ private class InlineReTyper extends ReTyper with InlineTyping { - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { - tpe match { - case tpe: NamedType if !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => - tpe.info match { - case TypeAlias(alias) => return ensureAccessible(alias, superAccess, pos) - case info: ConstantType if tpe.symbol.isStable => return info - case _ => - } - case _ => - } - super.ensureAccessible(tpe, superAccess, pos) - } - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index a137b554a91a..00b06895cc74 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -133,6 +133,20 @@ object Test extends App { val f1: Z = f(true) val f2: S[Z] = f(false) + transparent def mapHead[T, R](t: T)(implicit fh: T => R): R = fh(t) + transparent def map(xs: HList): HList = { + + if (xs.isEmpty) HNil + else HCons(mapHead(xs.head), map(xs.tail)) + } + + implicit def mapInt: Int => Boolean = (i: Int) => i < 23 + implicit val mapString: String => Int = (s: String) => s.length + implicit val mapBoolean: Boolean => String = (b: Boolean) => if(b) "yes" else "no" + + val res = map(HCons(23, HCons("foo", HCons(true, HNil)))) + val res1: Boolean `HCons` (Int `HCons` (String `HCons` HNil)) = res + /* transparent def toInt1[T]: Int = type T match { case Z => 0 From cbb757311f904ea7de107aa0e1cb5b80f15865ed Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:37:38 +0200 Subject: [PATCH 14/62] Inline -> Transparent - Make all inlining during typer be transparent. - Drop inline modifier - Update reference docs So far, this is just an update of the basic inline feature, with renamings everywhere. Typelevel programming with `transparent` is still missing. --- bench/tests/power-macro/PowerMacro.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 4 +- compiler/src/dotty/tools/dotc/ast/Trees.scala | 1 + compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 - .../dotty/tools/dotc/core/Annotations.scala | 2 +- .../dotty/tools/dotc/core/Definitions.scala | 4 +- .../src/dotty/tools/dotc/core/Flags.scala | 28 +- .../tools/dotc/core/SymDenotations.scala | 5 - .../src/dotty/tools/dotc/core/Types.scala | 10 +- .../dotc/core/tasty/PositionPickler.scala | 8 +- .../tools/dotc/core/tasty/TastyFormat.scala | 7 +- .../tools/dotc/core/tasty/TreePickler.scala | 4 +- .../tools/dotc/core/tasty/TreeUnpickler.scala | 12 - .../dotty/tools/dotc/parsing/Parsers.scala | 6 +- .../dotty/tools/dotc/parsing/Scanners.scala | 4 +- .../src/dotty/tools/dotc/parsing/Tokens.scala | 14 +- .../src/dotty/tools/dotc/quoted/Toolbox.scala | 2 +- .../reporting/diagnostic/ErrorMessageID.java | 6 +- .../dotc/reporting/diagnostic/messages.scala | 18 +- .../src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- .../tools/dotc/tastyreflect/FlagSet.scala | 4 +- .../tools/dotc/transform/AccessProxies.scala | 4 +- .../dotc/transform/ExtensionMethods.scala | 2 +- .../dotc/transform/Literalize.scala.disabled | 2 +- .../dotty/tools/dotc/transform/Memoize.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 8 +- .../tools/dotc/transform/ReifyQuotes.scala | 32 +- .../dotty/tools/dotc/transform/Splicer.scala | 8 +- .../src/dotty/tools/dotc/typer/Checking.scala | 25 +- .../dotty/tools/dotc/typer/Implicits.scala | 6 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 107 ++-- .../src/dotty/tools/dotc/typer/Namer.scala | 10 +- .../dotty/tools/dotc/typer/ProtoTypes.scala | 15 +- .../src/dotty/tools/dotc/typer/Typer.scala | 47 +- .../backend/jvm/InlineBytecodeTests.scala | 2 +- .../dotc/parsing/ModifiersParsingTest.scala | 8 +- .../dotc/reporting/ErrorMessagesTests.scala | 8 +- docs/docs/internals/syntax.md | 4 +- .../reference/dropped/weak-conformance.md | 2 +- docs/docs/reference/inline.md | 90 ++-- .../reference/principled-meta-programming.md | 30 +- docs/sidebar.yml | 2 +- .../src/scala/annotation/internal/Body.scala | 2 +- .../annotation/internal/InlineParam.scala | 6 - .../internal/TransparentParam.scala | 6 + library/src/scala/quoted/Expr.scala | 4 +- library/src/scala/quoted/QuoteError.scala | 2 +- library/src/scala/tasty/FlagSet.scala | 4 +- library/src/scala/tasty/TopLevelSplice.scala | 2 +- .../src/scala/tasty/util/ShowSourceCode.scala | 2 +- .../inline/changes/B1.scala | 2 +- .../inline/changes/B2.scala | 2 +- .../inline/changes/B3.scala | 2 +- tests/neg/i1568.scala | 2 +- tests/neg/i1605.scala | 2 +- tests/neg/i2006.scala | 4 +- tests/neg/i2421.scala | 14 +- tests/neg/i2564.scala | 2 +- tests/neg/i2564b.scala | 2 +- tests/neg/i2901.scala | 2 +- tests/neg/i3900.scala | 10 +- tests/neg/i4433.scala | 2 +- tests/neg/inlineAccess/C_1.scala | 2 +- tests/neg/inlinevals.scala | 22 +- tests/neg/power.scala | 2 +- tests/neg/quote-MacroOverride.scala | 4 +- tests/neg/quote-error-2/Macro_1.scala | 2 +- tests/neg/quote-error/Macro_1.scala | 2 +- tests/neg/quote-exception/Macro_1.scala | 2 +- tests/neg/quote-interpolator-core-old.scala | 6 +- tests/neg/quote-macro-splice.scala | 8 +- tests/neg/quote-non-static-macro.scala | 8 +- tests/neg/quote-run-in-macro-1/quoted_1.scala | 2 +- tests/neg/quote-run-in-macro-2/quoted_1.scala | 2 +- tests/neg/tasty-macro-assert/quoted_1.scala | 2 +- tests/pickling/i2166.scala | 2 +- tests/pickling/i3608.scala | 2 +- tests/pickling/i4006.scala | 2 +- tests/pickling/i4006b.scala | 2 +- tests/pickling/i4006c.scala | 2 +- tests/pos/SI-7060.scala | 2 +- tests/pos/depfuntype.scala | 2 +- tests/pos/harmonize.scala | 4 +- tests/pos/i1570.decompiled | 4 +- tests/pos/i1570.scala | 4 +- tests/pos/i1891.scala | 2 +- tests/pos/i1990.scala | 2 +- tests/pos/i1990a.scala | 2 +- tests/pos/i2056.scala | 2 +- tests/pos/i2980.scala | 2 +- tests/pos/i3050.scala | 4 +- tests/pos/i3082.scala | 2 +- tests/pos/i3129.scala | 4 +- tests/pos/i3130a.scala | 2 +- tests/pos/i3130b.scala | 2 +- tests/pos/i3130c.scala | 2 +- tests/pos/i3130d.scala | 2 +- tests/pos/i3488.scala | 2 +- tests/pos/i3597.scala | 2 +- tests/pos/i3608.scala | 2 +- tests/pos/i3633.scala | 2 +- tests/pos/i3636.scala | 4 +- tests/pos/i3873.scala | 2 +- tests/pos/i3898/quoted_1.scala | 2 +- tests/pos/i3898b/quoted_1.scala | 2 +- tests/pos/i3898c/quoted_1.scala | 2 +- tests/pos/i3912-1/i3912_1.scala | 2 +- tests/pos/i3912-2/i3912_1.scala | 2 +- tests/pos/i3912-3/i3912_1.scala | 2 +- tests/pos/i4023/Macro_1.scala | 2 +- tests/pos/i4023b/Macro_1.scala | 2 +- tests/pos/i4023c/Macro_1.scala | 2 +- tests/pos/i4322.scala | 6 +- tests/pos/i4493-b.scala | 2 +- tests/pos/i4493-c.scala | 2 +- tests/pos/i4493.scala | 2 +- tests/pos/i4514.scala | 2 +- tests/pos/i4586.scala | 2 +- tests/pos/i4590.scala | 4 +- tests/pos/inline-access-levels/A_1.scala | 2 +- tests/pos/inline-apply.scala | 2 +- tests/pos/inline-i1773.scala | 2 +- tests/pos/inline-i2570.scala | 2 +- tests/pos/inline-named-typeargs.scala | 2 +- tests/pos/inline-t2425.scala | 2 +- tests/pos/inline-t9232a.scala | 2 +- tests/pos/inline-t9232b.scala | 2 +- tests/pos/inlineAccesses.scala | 4 +- tests/pos/inliner2.scala | 2 +- tests/pos/macro-with-array/Macro_1.scala | 46 +- tests/pos/macro-with-type/Macro_1.scala | 2 +- tests/pos/pos_valueclasses/t5853.scala | 4 +- tests/pos/power-macro/Macro_1.scala | 2 +- tests/pos/quote-0.scala | 4 +- tests/pos/quote-assert/quoted_2.scala | 2 +- tests/pos/quote-lift-inline-params-b.scala | 2 +- .../quote-lift-inline-params/Macro_1.scala | 2 +- tests/pos/quote-nested-object/Macro_1.scala | 4 +- tests/pos/rbtree.scala | 4 +- tests/pos/reference/inlines.scala | 5 +- tests/pos/sealed-final.scala | 2 +- tests/pos/simpleInline.decompiled | 2 +- tests/pos/simpleInline.scala | 2 +- tests/pos/t6157.scala | 2 +- tests/pos/t6562.scala | 4 +- tests/pos/tasty/definitions.scala | 2 +- tests/run-with-compiler/i3876-d.scala | 2 +- tests/run/dead-code-elimination.scala | 2 +- tests/run/genericValueClass.scala | 4 +- tests/run/i1569.scala | 2 +- tests/run/i1990b.scala | 2 +- tests/run/i2077.scala | 2 +- tests/run/i2360.scala | 2 +- tests/run/i2895.scala | 2 +- tests/run/i2895a.scala | 2 +- tests/run/i4431/quoted_1.scala | 2 +- tests/run/i4455/Macro_1.scala | 4 +- tests/run/i4492/quoted_1.scala | 2 +- tests/run/i4496b.scala | 6 +- tests/run/i4754.scala | 2 +- tests/run/inline-implicits.check | 1 - tests/run/inline-implicits.scala | 26 - tests/run/inline-object.check | 1 - tests/run/inline-object.scala | 14 - tests/run/inline.check | 11 - tests/run/inline/Test_2.scala | 23 - tests/run/inline/inlines_1.scala | 59 --- tests/run/inlineAccess/C_1.scala | 7 - tests/run/inlineAccess/Test_2.scala | 7 - tests/run/inlineArrowAssoc.scala | 24 - tests/run/inlineByName.scala | 37 -- tests/run/inlineForeach.check | 137 ----- tests/run/inlineForeach.scala | 67 --- tests/run/inlinePower.check | 2 - tests/run/inlinePower/Test_2.scala | 9 - tests/run/inlinePower/power_1.scala | 12 - tests/run/inlinePrivates.scala | 36 -- tests/run/inlineProtected.scala | 22 - tests/run/inlinedAssign.scala | 24 - tests/run/lst-transparent/Lst.scala | 496 ------------------ tests/run/lst-transparent/LstTest.scala | 329 ------------ tests/run/lst/Lst.scala | 18 +- tests/run/lst/LstTest.scala | 2 +- tests/run/nats.scala-deptypes | 4 +- tests/run/quote-and-splice/Macros_1.scala | 12 +- tests/run/quote-force/quoted_1.scala | 2 +- tests/run/quote-impure-by-name/quoted_1.scala | 2 +- .../quote-indexed-map-by-name/quoted_1.scala | 2 +- .../run/quote-inline-function/quoted_1.scala | 4 +- tests/run/quote-sep-comp/Macro_1.scala | 2 +- tests/run/quote-simple-macro/quoted_1.scala | 2 +- .../quote-splice-interpret-1/Macro_1.scala | 2 +- tests/run/tasty-custom-show/quoted_1.scala | 2 +- tests/run/tasty-eval/quoted_1.scala | 4 +- tests/run/tasty-extractors-1/quoted_1.scala | 2 +- tests/run/tasty-extractors-2/quoted_1.scala | 2 +- tests/run/tasty-extractors-3/quoted_1.scala | 2 +- .../quoted_1.scala | 2 +- .../quoted_1.scala | 2 +- .../tasty-extractors-owners/quoted_1.scala | 2 +- .../run/tasty-extractors-types/quoted_1.scala | 2 +- tests/run/tasty-getfile/Macro_1.scala | 2 +- tests/run/tasty-indexed-map/quoted_1.scala | 2 +- tests/run/tasty-linenumber/quoted_1.scala | 2 +- tests/run/tasty-location/quoted_1.scala | 2 +- tests/run/tasty-macro-assert/quoted_1.scala | 2 +- tests/run/tasty-positioned/quoted_1.scala | 2 +- tests/run/transparent.check | 2 + tests/run/transparent/Test_2.scala | 7 +- tests/run/transparent/inlines_1.scala | 19 +- tests/run/transparentAssign.scala | 2 +- tests/run/typelevel.scala | 4 +- tests/run/typelevel3.scala | 60 +++ 213 files changed, 609 insertions(+), 1865 deletions(-) delete mode 100644 library/src/scala/annotation/internal/InlineParam.scala create mode 100644 library/src/scala/annotation/internal/TransparentParam.scala delete mode 100644 tests/run/inline-implicits.check delete mode 100644 tests/run/inline-implicits.scala delete mode 100644 tests/run/inline-object.check delete mode 100644 tests/run/inline-object.scala delete mode 100644 tests/run/inline.check delete mode 100644 tests/run/inline/Test_2.scala delete mode 100644 tests/run/inline/inlines_1.scala delete mode 100644 tests/run/inlineAccess/C_1.scala delete mode 100644 tests/run/inlineAccess/Test_2.scala delete mode 100644 tests/run/inlineArrowAssoc.scala delete mode 100644 tests/run/inlineByName.scala delete mode 100644 tests/run/inlineForeach.check delete mode 100644 tests/run/inlineForeach.scala delete mode 100644 tests/run/inlinePower.check delete mode 100644 tests/run/inlinePower/Test_2.scala delete mode 100644 tests/run/inlinePower/power_1.scala delete mode 100644 tests/run/inlinePrivates.scala delete mode 100644 tests/run/inlineProtected.scala delete mode 100644 tests/run/inlinedAssign.scala delete mode 100644 tests/run/lst-transparent/Lst.scala delete mode 100644 tests/run/lst-transparent/LstTest.scala create mode 100644 tests/run/typelevel3.scala diff --git a/bench/tests/power-macro/PowerMacro.scala b/bench/tests/power-macro/PowerMacro.scala index 31d8f77987d6..cc8ac09b7d2a 100644 --- a/bench/tests/power-macro/PowerMacro.scala +++ b/bench/tests/power-macro/PowerMacro.scala @@ -2,7 +2,7 @@ import scala.quoted.Expr object PowerMacro { - inline def power(inline n: Long, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Long, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Long, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 2516d4bf406e..fc359edc0eba 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -464,7 +464,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * Singleton type bounds (see SIP 23). Presumably * * object O1 { val x: Singleton = 42; println("43") } - * object O2 { inline val x = 42; println("43") } + * object O2 { transparent val x = 42; println("43") } * * should behave differently. * @@ -475,7 +475,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * O2.x = 42 * * Revisit this issue once we have implemented `inline`. Then we can demand - * purity of the prefix unless the selection goes to an inline val. + * purity of the prefix unless the selection goes to a transparent val. * * Note: This method should be applied to all term tree nodes that are not literals, * that can be idempotent, and that can have constant types. So far, only nodes diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a100c14cd57c..bcb7d0171098 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -589,6 +589,7 @@ object Trees { case class Inlined[-T >: Untyped] private[ast] (call: tpd.Tree, bindings: List[MemberDef[T]], expansion: Tree[T]) extends Tree[T] { type ThisTree[-T >: Untyped] = Inlined[T] + override def initialPos = call.pos } /** A type tree that represents an existing or inferred type */ diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bc806a47bd03..75ca533616a9 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -130,8 +130,6 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Lazy() extends Mod(Flags.Lazy) - case class Inline() extends Mod(Flags.Inline) - case class Transparent() extends Mod(Flags.Transparent) case class Enum() extends Mod(Flags.Enum) diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 3d66c876f6b7..ade035224780 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -57,7 +57,7 @@ object Annotations { } /** An annotation indicating the body of a right-hand side, - * typically of an inline method. Treated specially in + * typically of a transparent method. Treated specially in * pickling/unpickling and TypeTreeMaps */ abstract class BodyAnnotation extends Annotation { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 357d6a60b2be..e920cce39ba1 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -730,8 +730,8 @@ class Definitions { def ImplicitNotFoundAnnot(implicit ctx: Context) = ImplicitNotFoundAnnotType.symbol.asClass lazy val ForceInlineAnnotType = ctx.requiredClassRef("scala.forceInline") def ForceInlineAnnot(implicit ctx: Context) = ForceInlineAnnotType.symbol.asClass - lazy val InlineParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.InlineParam") - def InlineParamAnnot(implicit ctx: Context) = InlineParamAnnotType.symbol.asClass + lazy val TransparentParamAnnotType = ctx.requiredClassRef("scala.annotation.internal.TransparentParam") + def TransparentParamAnnot(implicit ctx: Context) = TransparentParamAnnotType.symbol.asClass lazy val InvariantBetweenAnnotType = ctx.requiredClassRef("scala.annotation.internal.InvariantBetween") def InvariantBetweenAnnot(implicit ctx: Context) = InvariantBetweenAnnotType.symbol.asClass lazy val MigrationAnnotType = ctx.requiredClassRef("scala.annotation.migration") diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 2fc48608dfe6..540b9cbe373f 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -326,8 +326,8 @@ object Flags { /** A method that has default params */ final val DefaultParameterized = termFlag(27, "") - /** Symbol is inlined */ - final val Inline = commonFlag(29, "inline") + /** Labelled with `transparent` modifier */ + final val Transparent = commonFlag(29, "transparent") /** Symbol is defined by a Java class */ final val JavaDefined = commonFlag(30, "") @@ -363,9 +363,6 @@ object Flags { /** Symbol is a Java default method */ final val DefaultMethod = termFlag(38, "") - /** Labelled with `transparent` modifier */ - final val Transparent = termFlag(39, "transparent") - /** Symbol is an enum class or enum case (if used with case) */ final val Enum = commonFlag(40, "") @@ -439,7 +436,7 @@ object Flags { /** Flags representing source modifiers */ final val SourceModifierFlags = - commonFlags(Private, Protected, Abstract, Final, Inline, Transparent, + commonFlags(Private, Protected, Abstract, Final, Transparent, Sealed, Case, Implicit, Override, AbsOverride, Lazy, JavaStatic, Erased) /** Flags representing modifiers that can appear in trees */ @@ -460,7 +457,7 @@ object Flags { Scala2ExistentialCommon | Mutable.toCommonFlags | Touched | JavaStatic | CovariantOrOuter | ContravariantOrLabel | CaseAccessor.toCommonFlags | NonMember | ImplicitCommon | Permanent | Synthetic | - SuperAccessorOrScala2x | Inline | Transparent.toCommonFlags + SuperAccessorOrScala2x | Transparent /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class @@ -551,8 +548,8 @@ object Flags { /** Either method or lazy or deferred */ final val MethodOrLazyOrDeferred = Method | Lazy | Deferred - /** Labeled `private`, `final`, `inline`, or `transparent` */ - final val EffectivelyFinal = Private | Final | Inline | Transparent.toCommonFlags + /** Labeled `private`, `final`, or `transparent` */ + final val EffectivelyFinal = Private | Final | Transparent /** A private method */ final val PrivateMethod = allOf(Private, Method) @@ -560,14 +557,11 @@ object Flags { /** A private accessor */ final val PrivateAccessor = allOf(Private, Accessor) - /** An inline method */ - final val InlineMethod = allOf(Inline, Method) - - /** An transparent method */ + /** A transparent method */ final val TransparentMethod = allOf(Transparent, Method) - /** An inline parameter */ - final val InlineParam = allOf(Inline, Param) + /** A transparent parameter */ + final val TransparentParam = allOf(Transparent, Param) /** An enum case */ final val EnumCase = allOf(Enum, Case) @@ -593,8 +587,8 @@ object Flags { /** A deferred type member or parameter (these don't have right hand sides) */ final val DeferredOrTypeParam = Deferred | TypeParam - /** value that's final, inline, or transparent */ - final val FinalOrInlineOrTransparent = Final | Inline | Transparent.toCommonFlags + /** value that's final or transparent */ + final val FinalOrTransparent = Final | Transparent /** A covariant type parameter instance */ final val LocalCovariant = allOf(Local, Covariant) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 7253da7dcd0b..c1ab3f9d3cc2 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -788,14 +788,9 @@ object SymDenotations { def isSkolem: Boolean = name == nme.SKOLEM - def isInlinedMethod(implicit ctx: Context): Boolean = - is(InlineMethod, butNot = Accessor) - def isTransparentMethod(implicit ctx: Context): Boolean = is(TransparentMethod, butNot = AccessorOrSynthetic) - def isInlineableMethod(implicit ctx: Context) = isInlinedMethod || isTransparentMethod - /** ()T and => T types should be treated as equivalent for this symbol. * Note: For the moment, we treat Scala-2 compiled symbols as loose matching, * because the Scala library does not always follow the right conventions. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 2040cc6377d9..9b293c01791a 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2969,18 +2969,18 @@ object Types { abstract class MethodTypeCompanion extends TermLambdaCompanion[MethodType] { self => /** Produce method type from parameter symbols, with special mappings for repeated - * and inline parameters: + * and transparent parameters: * - replace @repeated annotations on Seq or Array types by types - * - add @inlineParam to inline call-by-value parameters + * - add @inlineParam to transparent call-by-value parameters */ def fromSymbols(params: List[Symbol], resultType: Type)(implicit ctx: Context) = { - def translateInline(tp: Type): Type = tp match { + def translateTransparent(tp: Type): Type = tp match { case _: ExprType => tp - case _ => AnnotatedType(tp, Annotation(defn.InlineParamAnnot)) + case _ => AnnotatedType(tp, Annotation(defn.TransparentParamAnnot)) } def paramInfo(param: Symbol) = { val paramType = param.info.annotatedToRepeated - if (param.is(Inline)) translateInline(paramType) else paramType + if (param.is(Transparent)) translateTransparent(paramType) else paramType } apply(params.map(_.name.asTermName))( diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index 7e25c80c942d..c3aa124928ae 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -13,7 +13,7 @@ import collection.mutable import TastyBuffer._ import util.Positions._ -class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr]) { +class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Addr]) { val buf = new TastyBuffer(5000) pickler.newSection("Positions", buf) import buf._ @@ -62,8 +62,8 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr } def traverse(x: Any): Unit = x match { - case x: Tree @unchecked => - val pos = if (x.isInstanceOf[MemberDef]) x.pos else x.pos.toSynthetic + case x: untpd.Tree => + val pos = if (x.isInstanceOf[untpd.MemberDef]) x.pos else x.pos.toSynthetic if (pos.exists && (pos != x.initialPos.toSynthetic || alwaysNeedsPos(x))) { addrOfTree(x) match { case Some(addr) if !pickledIndices.contains(addr.index) => @@ -75,7 +75,7 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr } //else if (x.pos.exists) println(i"skipping $x") x match { - case x: MemberDef @unchecked => + case x: untpd.MemberDef @unchecked => for (ann <- x.symbol.annotations) traverse(ann.tree) case _ => } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index de1e04fcadf2..5d948e1a1948 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -181,9 +181,8 @@ Standard-Section: "ASTs" TopLevelStat* ERASED LAZY OVERRIDE - INLINE TRANSPARENT - MACRO // inline method containing toplevel splices + MACRO // transparent method containing toplevel splices STATIC // mapped to static Java member OBJECT // an object or its class TRAIT // a trait @@ -296,7 +295,7 @@ object TastyFormat { final val IMPLICIT = 13 final val LAZY = 14 final val OVERRIDE = 15 - final val INLINE = 16 + final val TRANSPARENT = 17 final val STATIC = 18 final val OBJECT = 19 @@ -473,7 +472,6 @@ object TastyFormat { | ERASED | LAZY | OVERRIDE - | INLINE | TRANSPARENT | MACRO | STATIC @@ -530,7 +528,6 @@ object TastyFormat { case ERASED => "ERASED" case LAZY => "LAZY" case OVERRIDE => "OVERRIDE" - case INLINE => "INLINE" case TRANSPARENT => "TRANSPARENT" case MACRO => "MACRO" case STATIC => "STATIC" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index b1e6b87e9a78..83aaebd08793 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -600,7 +600,6 @@ class TreePickler(pickler: TastyPickler) { if (flags.is(Final, butNot = Module)) writeByte(FINAL) if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) - if (flags is Inline) writeByte(INLINE) if (flags is Transparent) writeByte(TRANSPARENT) if (flags is Macro) writeByte(MACRO) if (flags is JavaStatic) writeByte(STATIC) @@ -637,8 +636,7 @@ class TreePickler(pickler: TastyPickler) { // a different toplevel class, it is impossible to pickle a reference to it. // Such annotations will be reconstituted when unpickling the child class. // See tests/pickling/i3149.scala - case _ => ann.symbol == defn.BodyAnnot && owner.isInlinedMethod - // bodies of inlined (but not transparent) methods are reconstituted automatically when unpickling + case _ => false } def pickleAnnotation(owner: Symbol, ann: Annotation)(implicit ctx: Context) = diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 432b95af686a..1827e39dd644 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -549,12 +549,6 @@ class TreeUnpickler(reader: TastyReader, sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(localContext(sym)) } - else if (sym.isInlinedMethod) - sym.addAnnotation(LazyBodyAnnotation { ctx0 => - implicit val ctx: Context = localContext(sym)(ctx0).addMode(Mode.ReadPositions) - // avoids space leaks by not capturing the current context - forkAt(rhsStart).readTerm() - }) goto(start) sym } @@ -590,7 +584,6 @@ class TreeUnpickler(reader: TastyReader, case ERASED => addFlag(Erased) case LAZY => addFlag(Lazy) case OVERRIDE => addFlag(Override) - case INLINE => addFlag(Inline) case TRANSPARENT => addFlag(Transparent) case MACRO => addFlag(Macro) case STATIC => addFlag(JavaStatic) @@ -741,11 +734,6 @@ class TreeUnpickler(reader: TastyReader, def readRhs(implicit ctx: Context) = if (nothingButMods(end)) EmptyTree - else if (sym.isInlinedMethod) - // The body of an inline method is stored in an annotation, so no need to unpickle it again - new Trees.Lazy[Tree] { - def complete(implicit ctx: Context) = typer.Inliner.bodyToInline(sym) - } else readLater(end, rdr => ctx => rdr.readTerm()(ctx.retractMode(Mode.InSuperCall))) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f631104d80aa..2c18b41d5e01 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1679,7 +1679,6 @@ object Parsers { case FINAL => Mod.Final() case IMPLICIT => Mod.Implicit() case ERASED => Mod.Erased() - case INLINE => Mod.Inline() case TRANSPARENT => Mod.Transparent() case LAZY => Mod.Lazy() case OVERRIDE => Mod.Override() @@ -1791,7 +1790,6 @@ object Parsers { */ def annot() = adjustStart(accept(AT)) { - if (in.token == INLINE) in.token = BACKQUOTED_IDENT // allow for now ensureApplied(parArgumentExprss(wrapNew(simpleType()))) } @@ -1888,7 +1886,7 @@ object Parsers { addMod(mods, mod) } else { - if (!(mods.flags &~ (ParamAccessor | Inline)).isEmpty) + if (!(mods.flags &~ (ParamAccessor | Transparent)).isEmpty) syntaxError("`val' or `var' expected") if (firstClauseOfCaseClass) mods else mods | PrivateLocal @@ -1896,7 +1894,7 @@ object Parsers { } } else { - if (in.token == INLINE) mods = addModifier(mods) + if (in.token == TRANSPARENT) mods = addModifier(mods) mods = atPos(start) { mods | Param } } atPos(start, nameStart) { diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index 68443e5b2125..7aa2dc4d36eb 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -198,7 +198,9 @@ object Scanners { private def handleMigration(keyword: Token): Token = if (!isScala2Mode) keyword - else if (keyword == INLINE) treatAsIdent() + else if ( keyword == ENUM + || keyword == ERASED + || keyword == TRANSPARENT) treatAsIdent() else keyword diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 17b3e055f418..2c2ff229544e 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -91,8 +91,9 @@ abstract class TokensCommon { //final val LAZY = 59; enter(LAZY, "lazy") //final val THEN = 60; enter(THEN, "then") //final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate - //final val INLINE = 62; enter(INLINE, "inline") - //final val ENUM = 63; enter(ENUM, "enum") + //final val TRANSPARENT = 62; enter(TRANSPARENT, "transparent") + //final val ENUM = 63; enter(ENUM, "enum") + //final val ERASED = 64; enter(ERASED, "erased") /** special symbols */ final val COMMA = 70; enter(COMMA, "','") @@ -175,10 +176,9 @@ object Tokens extends TokensCommon { final val LAZY = 59; enter(LAZY, "lazy") final val THEN = 60; enter(THEN, "then") final val FORSOME = 61; enter(FORSOME, "forSome") // TODO: deprecate - final val INLINE = 62; enter(INLINE, "inline") - final val TRANSPARENT = 63; enter(TRANSPARENT, "transparent") - final val ENUM = 64; enter(ENUM, "enum") - final val ERASED = 65; enter(ERASED, "erased") + final val TRANSPARENT = 62; enter(TRANSPARENT, "transparent") + final val ENUM = 63; enter(ENUM, "enum") + final val ERASED = 64; enter(ERASED, "erased") /** special symbols */ final val NEWLINE = 78; enter(NEWLINE, "end of statement", "new line") @@ -227,7 +227,7 @@ object Tokens extends TokensCommon { final val defIntroTokens = templateIntroTokens | dclIntroTokens final val localModifierTokens = BitSet( - ABSTRACT, FINAL, SEALED, IMPLICIT, INLINE, TRANSPARENT, LAZY, ERASED) + ABSTRACT, FINAL, SEALED, IMPLICIT, TRANSPARENT, LAZY, ERASED) final val accessModifierTokens = BitSet( PRIVATE, PROTECTED) diff --git a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala index 72d35f6159d8..827157f5f3e2 100644 --- a/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala +++ b/compiler/src/dotty/tools/dotc/quoted/Toolbox.scala @@ -17,7 +17,7 @@ object Toolbox { case expr: LiftedExpr[T] => expr.value case expr: TastyTreeExpr[Tree] @unchecked => - throw new Exception("Cannot call `Expr.run` on an `Expr` that comes from an inline macro argument.") + throw new Exception("Cannot call `Expr.run` on an `Expr` that comes from a transparent macro argument.") case _ => synchronized(driver.run(expr, settings)) } diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java index 87136eda5ebe..8233e3305d2a 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/ErrorMessageID.java @@ -90,7 +90,7 @@ public enum ErrorMessageID { OnlyCaseClassOrCaseObjectAllowedID, ExpectedClassOrObjectDefID, AnonymousFunctionMissingParamTypeID, - SuperCallsNotAllowedInlineID, + SuperCallsNotAllowedTransparentID, ModifiersNotAllowedID, WildcardOnTypeArgumentNotAllowedOnNewID, FunctionTypeNeedsNonEmptyParameterListID, @@ -98,7 +98,7 @@ public enum ErrorMessageID { DuplicatePrivateProtectedQualifierID, ExpectedStartOfTopLevelDefinitionID, MissingReturnTypeWithReturnStatementID, - NoReturnFromInlineID, + NoReturnFromTransparentID, ReturnOutsideMethodDefinitionID, UncheckedTypePatternID, ExtendFinalClassID, @@ -125,7 +125,7 @@ public enum ErrorMessageID { UnableToEmitSwitchID, MissingCompanionForStaticID, PolymorphicMethodMissingTypeInParentID, - ParamsNoInlineID, + ParamsNoTransparentID, JavaSymbolIsNotAValueID, DoubleDeclarationID, MatchCaseOnlyNullWarningID, diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 818e38ffb4b7..92722b3a4ea6 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1692,10 +1692,10 @@ object messages { val explanation = "" } - case class SuperCallsNotAllowedInline(symbol: Symbol)(implicit ctx: Context) - extends Message(SuperCallsNotAllowedInlineID) { + case class SuperCallsNotAllowedTransparent(symbol: Symbol)(implicit ctx: Context) + extends Message(SuperCallsNotAllowedTransparentID) { val kind = "Syntax" - val msg = s"super call not allowed in inline $symbol" + val msg = s"super call not allowed in transparent $symbol" val explanation = "Method inlining prohibits calling superclass methods, as it may lead to confusion about which super is being called." } @@ -1743,10 +1743,10 @@ object messages { hl"you have to provide either ${"class"}, ${"trait"}, ${"object"}, or ${"enum"} definitions after qualifiers" } - case class NoReturnFromInline(owner: Symbol)(implicit ctx: Context) - extends Message(NoReturnFromInlineID) { + case class NoReturnFromTransparent(owner: Symbol)(implicit ctx: Context) + extends Message(NoReturnFromTransparentID) { val kind = "Syntax" - val msg = hl"no explicit ${"return"} allowed from inline $owner" + val msg = hl"no explicit ${"return"} allowed from transparent $owner" val explanation = hl"""Methods marked with ${"inline"} modifier may not use ${"return"} statements. |Instead, you should rely on the last expression's value being @@ -2054,10 +2054,10 @@ object messages { |polymorphic methods.""" } - case class ParamsNoInline(owner: Symbol)(implicit ctx: Context) - extends Message(ParamsNoInlineID) { + case class ParamsNoTransparent(owner: Symbol)(implicit ctx: Context) + extends Message(ParamsNoTransparentID) { val kind = "Syntax" - val msg = hl"""${"inline"} modifier cannot be used for a ${owner.showKind} parameter""" + val msg = hl"""${"transparent"} modifier cannot be used for a ${owner.showKind} parameter""" val explanation = "" } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4dcd282852c3..4418a5f4f518 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -590,7 +590,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder val annots = new mutable.ListBuffer[api.Annotation] if (Inliner.hasBodyToInline(s)) { - // FIXME: If the body of an inline method changes, all the reverse + // FIXME: If the body of a transparent method changes, all the reverse // dependencies of this method need to be recompiled. sbt has no way // of tracking method bodies, so as a hack we include the pretty-printed // typed tree of the method as part of the signature we send to sbt. diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala index 27f56edb6e2c..3c553e5de0e8 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FlagSet.scala @@ -14,7 +14,7 @@ class FlagSet(flags: Flags.FlagSet) extends scala.tasty.FlagSet { def isErased: Boolean = flags.is(Erased) def isLazy: Boolean = flags.is(Lazy) def isOverride: Boolean = flags.is(Override) - def isInline: Boolean = flags.is(Inline) + def isTransparent: Boolean = flags.is(Transparent) def isMacro: Boolean = flags.is(Macro) def isStatic: Boolean = flags.is(JavaStatic) def isObject: Boolean = flags.is(Module) @@ -45,7 +45,7 @@ class FlagSet(flags: Flags.FlagSet) extends scala.tasty.FlagSet { if (isErased) flags += "erased" if (isLazy) flags += "lazy" if (isOverride) flags += "override" - if (isInline) flags += "inline" + if (isTransparent) flags += "transparent" if (isMacro) flags += "macro" if (isStatic) flags += "javaStatic" if (isObject) flags += "object" diff --git a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala index fa3c9efbfa9a..bffd84779544 100644 --- a/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala +++ b/compiler/src/dotty/tools/dotc/transform/AccessProxies.scala @@ -145,7 +145,7 @@ abstract class AccessProxies { def accessorIfNeeded(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos) + ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) tree // TODO: create a proper accessor for the private constructor } else useAccessor(tree) @@ -162,7 +162,7 @@ object AccessProxies { def recur(cls: Symbol): Symbol = if (!cls.exists) NoSymbol else if (cls.derivesFrom(accessed.owner) || - cls.companionModule.moduleClass == accessed.owner) cls + cls.companionModule.moduleClass == accessed.owner) cls else recur(cls.owner) recur(ctx.owner) } diff --git a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala index 02d072bf6a86..198118e43f03 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala @@ -21,7 +21,7 @@ import Decorators._ import SymUtils._ /** - * Perform Step 1 in the inline classes SIP: Creates extension methods for all + * Perform Step 1 in the transparent classes SIP: Creates extension methods for all * methods in a value class, except parameter or super accessors, or constructors. * * Additionally, for a value class V, let U be the underlying type after erasure. We add diff --git a/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled index 8d2b06e24462..1b5d3d51a449 100644 --- a/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled +++ b/compiler/src/dotty/tools/dotc/transform/Literalize.scala.disabled @@ -38,7 +38,7 @@ class Literalize extends MiniPhase { thisTransform => * Singleton type bounds (see SIP 23). Presumably * * object O1 { val x: Singleton = 42; println("43") } - * object O2 { inline val x = 42; println("43") } + * object O2 { transparent val x = 42; println("43") } * * should behave differently. * diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index aee354a077c7..28dc2e8dd8fc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -105,7 +105,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => cpy.installAfter(thisPhase) } - val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Inline) + val NoFieldNeeded = Lazy | Deferred | JavaDefined | (if (ctx.settings.YnoInline.value) EmptyFlags else Transparent) def erasedBottomTree(sym: Symbol) = { if (sym eq defn.NothingClass) Throw(Literal(Constant(null))) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 85e9af414d60..0781f958e375 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -9,7 +9,7 @@ import Types._, Contexts._, Names._, Flags._, DenotTransformers._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ -import reporting.diagnostic.messages.{ImportRenamedTwice, NotAMember, SuperCallsNotAllowedInline} +import reporting.diagnostic.messages._ object PostTyper { val name = "posttyper" @@ -40,7 +40,7 @@ object PostTyper { * * (10) Adds Child annotations to all sealed classes * - * (11) Minimizes `call` fields of `Inline` nodes to just point to the toplevel + * (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel * class from which code was inlined. * * The reason for making this a macro transform is that some functions (in particular @@ -156,7 +156,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } - /** 1. If we are an an inline method but not in a nested quote, mark the inline method + /** 1. If we are in a transparent method but not in a nested quote, mark the transparent method * as a macro. * * 2. If selection is a quote or splice node, record that fact in the current compilation unit. @@ -165,7 +165,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isInlineableMethod) c.owner.setFlag(Macro) + else if (c.owner.isTransparentMethod) c.owner.setFlag(Macro) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) if (sym.isSplice || sym.isQuote) { diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index e70a2985f5ff..57360d71ce2e 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -59,14 +59,14 @@ import dotty.tools.dotc.core.quoted._ * and then performs the same transformation on `'{ ... x1$1.unary_~ ... x2$1.unary_~ ...}`. * * - * For inline macro definitions we assume that we have a single ~ directly as the RHS. + * For transparent macro definitions we assume that we have a single ~ directly as the RHS. * We will transform the definition from * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... } + * transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Z = ~{ ... T1 ... x ... '(y) ... } * ``` * to * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { + * transparent def foo[T1, ...] (transparent x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { * val T1$1 = args(0).asInstanceOf[Type[T1]] * ... * val x1$1 = args(0).asInstanceOf[X] @@ -76,8 +76,8 @@ import dotty.tools.dotc.core.quoted._ * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } * } * ``` - * Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are - * passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled + * Where `transparent` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are + * passed as their actual runtime value. See `isStage0Value`. Other `transparent` arguments such as functions are handled * like `y1: Y`. * * Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`. @@ -243,7 +243,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } /** Does the level of `sym` match the current level? - * An exception is made for inline vals in macros. These are also OK if their level + * An exception is made for transparent vals in macros. These are also OK if their level * is one higher than the current level, because on execution such values * are constant expression trees and we can pull out the constant from the tree. */ @@ -255,7 +255,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { !sym.is(Param) || levelOK(sym.owner) } - /** Issue a "splice outside quote" error unless we ar in the body of an inline method */ + /** Issue a "splice outside quote" error unless we ar in the body of a transparent method */ def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit = ctx.error(i"splice outside quotes", pos) @@ -432,7 +432,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { /** If inside a quote, split the body of the splice into a core and a list of embedded quotes * and make a hole from these parts. Otherwise issue an error, unless we - * are in the body of an inline method. + * are in the body of a transparent method. */ private def splice(splice: Select)(implicit ctx: Context): Tree = { if (level > 1) { @@ -604,7 +604,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { tree.rhs match { case InlineSplice(_) => if (!tree.symbol.isStatic) - ctx.error("Inline macro method must be a static method.", tree.pos) + ctx.error("Transparent macro method must be a static method.", tree.pos) markDef(tree) val reifier = nested(isQuote = true) reifier.transform(tree) // Ignore output, we only need the its embedding @@ -614,12 +614,12 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { cpy.DefDef(tree)(tpt = TypeTree(macroReturnType), rhs = lambda) case _ => ctx.error( - """Malformed inline macro. + """Malformed transparent macro. | |Expected the ~ to be at the top of the RHS: - | inline def foo(...): Int = ~impl(...) + | transparent def foo(...): Int = ~impl(...) |or - | inline def foo(...): Int = ~{ + | transparent def foo(...): Int = ~{ | val x = 1 | impl(... x ...) | } @@ -632,7 +632,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } } - /** Takes a reference to an inline parameter `tree` and lifts it to an Expr */ + /** Takes a reference to an transparent parameter `tree` and lifts it to an Expr */ private def liftInlineParamValue(tree: Tree)(implicit ctx: Context): Tree = { val tpSym = tree.tpe.widenDealias.classSymbol @@ -651,7 +651,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean = - (sym.is(Inline) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) || + (sym.is(Transparent) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)) || sym == defn.TastyTopLevelSplice_tastyContext // intrinsic value at stage 0 private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { @@ -677,9 +677,9 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = { /** Transforms the return type of - * inline def foo(...): X = ~(...) + * transparent def foo(...): X = ~(...) * to - * inline def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ... + * transparent def foo(...): Seq[Any] => Expr[Any] = (args: Seq[Any]) => ... */ def transform(tp: Type): Type = tp match { case tp: MethodType => MethodType(tp.paramNames, tp.paramInfos, transform(tp.resType)) diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 98f8949bb2ad..3c121c0517c7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -63,9 +63,9 @@ object Splicer { def liftArgs(tpe: Type, args: List[List[Tree]]): List[Any] = tpe match { case tp: MethodType => val args1 = args.head.zip(tp.paramInfos).map { - case (arg: Literal, tp) if tp.hasAnnotation(defn.InlineParamAnnot) => arg.const.value + case (arg: Literal, tp) if tp.hasAnnotation(defn.TransparentParamAnnot) => arg.const.value case (arg, tp) => - assert(!tp.hasAnnotation(defn.InlineParamAnnot)) + assert(!tp.hasAnnotation(defn.TransparentParamAnnot)) // Replace argument by its binding val arg1 = bindMap.getOrElse(arg, arg) new scala.quoted.Exprs.TastyTreeExpr(arg1) @@ -154,12 +154,12 @@ object Splicer { try clazz.getMethod(name.toString, paramClasses: _*) catch { case _: NoSuchMethodException => - val msg = s"Could not find inline macro method ${clazz.getCanonicalName}.$name with parameters $paramClasses$extraMsg" + val msg = s"Could not find transparent macro method ${clazz.getCanonicalName}.$name with parameters $paramClasses$extraMsg" throw new StopInterpretation(msg, pos) } } - private def extraMsg = ". The most common reason for that is that you cannot use inline macro implementations in the same compilation run that defines them" + private def extraMsg = ". The most common reason for that is that you cannot use transparent macro implementations in the same compilation run that defines them" private def stopIfRuntimeException[T](thunk: => T): T = { try thunk diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 47893a9390c1..026d0ceb392a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -369,8 +369,8 @@ object Checking { if (!ok && !sym.is(Synthetic)) fail(i"modifier `$flag` is not allowed for this definition") - if (sym.is(Inline) && ((sym.is(ParamAccessor) && sym.owner.isClass) || sym.is(TermParam) && sym.owner.isClassConstructor)) - fail(ParamsNoInline(sym.owner)) + if (sym.is(Transparent) && ((sym.is(ParamAccessor) && sym.owner.isClass) || sym.is(TermParam) && sym.owner.isClassConstructor)) + fail(ParamsNoTransparent(sym.owner)) if (sym.is(ImplicitCommon)) { if (sym.owner.is(Package)) @@ -396,18 +396,17 @@ object Checking { fail(OnlyClassesCanHaveDeclaredButUndefinedMembers(sym)) checkWithDeferred(Private) checkWithDeferred(Final) - checkWithDeferred(Inline) + checkWithDeferred(Transparent) } if (sym.isValueClass && sym.is(Trait) && !sym.isRefinementClass) fail(CannotExtendAnyVal(sym)) checkCombination(Final, Sealed) checkCombination(Private, Protected) checkCombination(Abstract, Override) - checkCombination(Lazy, Inline) - checkCombination(Module, Inline) - checkCombination(Transparent, Inline) + checkCombination(Lazy, Transparent) + checkCombination(Module, Transparent) checkNoConflict(Lazy, ParamAccessor, s"parameter may not be `lazy`") - if (sym.is(Inline)) checkApplicable(Inline, sym.isTerm && !sym.is(Mutable | Module)) + if (sym.is(Transparent)) checkApplicable(Transparent, sym.isTerm && !sym.is(Mutable | Module)) if (sym.is(Lazy)) checkApplicable(Lazy, !sym.is(Method | Mutable)) if (sym.isType && !sym.is(Deferred)) for (cls <- sym.allOverriddenSymbols.filter(_.isClass)) { @@ -666,16 +665,16 @@ trait Checking { } } - /** Check that `tree` can be marked `inline` */ - def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { - // final vals can be marked inline even if they're not pure, see Typer#patchFinalVals + /** Check that `tree` can right hand-side or argument to `transparent` value or parameter. */ + def checkTransparentConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { + // final vals can be marked transparent even if they're not pure, see Typer#patchFinalVals val purityLevel = if (isFinal) Idempotent else Pure tree.tpe match { - case tp: TermRef if tp.symbol.is(InlineParam) => // ok + case tp: TermRef if tp.symbol.is(TransparentParam) => // ok case tp => tp.widenTermRefExpr match { case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok case _ => - val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isInlineableMethod) + val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isTransparentMethod) if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) } } @@ -942,7 +941,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionDefOK(sym: Symbol)(implicit ctx: Context): Unit = () override def checkImplicitConversionUseOK(sym: Symbol, pos: Position)(implicit ctx: Context): Unit = () override def checkFeasibleParent(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp - override def checkInlineConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context) = () + override def checkTransparentConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context) = () override def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 6c8e362ba0b8..c1107a5803b3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -99,9 +99,9 @@ object Implicits { mt.isImplicitMethod || mt.paramInfos.length != 1 || !ctx.test(implicit ctx => - argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head), null, Set.empty)) + argType relaxed_<:< wildApprox(widenSingleton(mt.paramInfos.head))) case rtp => - discardForView(wildApprox(rtp, null, Set.empty), argType) + discardForView(wildApprox(rtp), argType) } case tpw: TermRef => false // can't discard overloaded refs @@ -900,7 +900,7 @@ trait Implicits { self: Typer => } /** The expected type where parameters and uninstantiated typevars are replaced by wildcard types */ - val wildProto = implicitProto(pt, wildApprox(_, null, Set.empty)) + val wildProto = implicitProto(pt, wildApprox(_)) val isNot = wildProto.classSymbol == defn.NotClass diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 83650e0e863c..775d48d3b13a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -84,9 +84,7 @@ object Inliner { def postTransform(tree: Tree)(implicit ctx: Context) = tree match { case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => - val setter = useSetter(lhs) - if (inlineSym.isTransparentMethod) tree // just generate a setter, but don't integrate it in the tree - else cpy.Apply(tree)(setter, rhs :: Nil) + cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) case _ => tree } @@ -104,7 +102,7 @@ object Inliner { def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { case tree: RefTree if needsAccessor(tree.symbol) => if (tree.symbol.isConstructor) { - ctx.error("Implementation restriction: cannot use private constructors in inline methods", tree.pos) + ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) tree // TODO: create a proper accessor for the private constructor } else useAccessor(tree) @@ -124,11 +122,11 @@ object Inliner { * private[inlines] def next[U](y: U): (T, U) = (x, y) * } * class TestPassing { - * inline def foo[A](x: A): (A, Int) = { + * transparent def foo[A](x: A): (A, Int) = { * val c = new C[A](x) * c.next(1) * } - * inline def bar[A](x: A): (A, String) = { + * transparent def bar[A](x: A): (A, String) = { * val c = new C[A](x) * c.next("") * } @@ -215,7 +213,7 @@ object Inliner { def makeInlineable(tree: Tree)(implicit ctx: Context) = { val inlineSym = ctx.owner if (inlineSym.owner.isTerm) - // Inline methods in local scopes can only be called in the scope they are defined, + // Transparent methods in local scopes can only be called in the scope they are defined, // so no accessors are needed for them. tree else @@ -229,9 +227,9 @@ object Inliner { sym != inlineMethod && (!sym.is(Param) || sym.owner != inlineMethod) - /** Register inline info for given inline method `sym`. + /** Register inline info for given transparent method `sym`. * - * @param sym The symbol denotatioon of the inline method for which info is registered + * @param sym The symbol denotatioon of the transparent method for which info is registered * @param treeExpr A function that computes the tree to be inlined, given a context * This tree may still refer to non-public members. * @param ctx The context to use for evaluating `treeExpr`. It needs @@ -251,9 +249,7 @@ object Inliner { val typedBody = if (ctx.reporter.hasErrors) rawBody else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) - val inlineableBody = - if (inlined.isInlinedMethod) typedBody - else addReferences(inlined, originalBody, typedBody) + val inlineableBody = addReferences(inlined, originalBody, typedBody) inlining.println(i"Body to inline for $inlined: $inlineableBody") inlineableBody }) @@ -278,9 +274,10 @@ object Inliner { // Maps from positions to external reference types and inline selector names. object referenced extends TreeTraverser { val typeAtPos = mutable.Map[Position, Type]() - val nameAtPos = mutable.Map[Position, Name]() + val accessorAtPos = mutable.Map[Position, Symbol]() val implicitSyms = mutable.Set[Symbol]() val implicitRefs = new mutable.ListBuffer[Tree] + def registerIfContextualImplicit(tree: Tree) = tree match { case tree: RefTree if tree.removeAttachment(ContextualImplicit).isDefined && @@ -294,6 +291,12 @@ object Inliner { } case _ => } + + def registerAccessor(tree: Tree) = { + inlining.println(i"accessor: $tree at ${tree.pos}") + accessorAtPos(tree.pos.toSynthetic) = tree.symbol + } + def traverse(tree: Tree)(implicit ctx: Context): Unit = { tree match { case _: Ident | _: This => @@ -302,9 +305,17 @@ object Inliner { if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") typeAtPos(tree.pos.toSynthetic) = tree.tpe } - case _: Select if tree.symbol.name.is(InlineAccessorName) => - inlining.println(i"accessor: $tree at ${tree.pos}") - nameAtPos(tree.pos.toSynthetic) = tree.symbol.name + case _: Select => + tree.symbol.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => return // was already recorded in Apply + case InlineAccessorName(_) => registerAccessor(tree) + case _ => + } + case Apply(_: RefTree | _: TypeApply, receiver :: Nil) => + tree.symbol.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => registerAccessor(tree) + case _ => + } case _ => } registerIfContextualImplicit(tree) @@ -316,14 +327,30 @@ object Inliner { // The untyped tree transform that applies the tweaks object addRefs extends untpd.UntypedTreeMap { override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = { + def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match { case Some(tpe) => untpd.TypedSplice(tree.withType(tpe)) case none => tree } - def adjustName(name: Name) = referenced.nameAtPos.get(tree.pos.toSynthetic) match { - case Some(n) => n - case none => name - } + + def adjustForAccessor(ref: untpd.RefTree) = + referenced.accessorAtPos.get(ref.pos.toSynthetic) match { + case Some(acc) => + def accessorRef = untpd.TypedSplice(tpd.ref(acc)) + acc.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => + // In this case we are seeing a pair like this: + // untyped typed + // t.x inline$x(t) + // Drop the selection, since it is part of the accessor + val Select(qual, _) = ref + untpd.Apply(accessorRef, qual :: Nil) + case _ => + accessorRef + } + case none => ref + } + def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match { case tree @ Ident(name1) => referenced.typeAtPos.get(tree.pos.startPos) match { @@ -335,14 +362,25 @@ object Inliner { } case tree => tree } + + def isAccessorLHS(lhs: untpd.Tree): Boolean = lhs match { + case lhs: untpd.Apply => isAccessorLHS(lhs.fun) + case lhs: untpd.TypeApply => isAccessorLHS(lhs.fun) + case lhs: untpd.RefTree => lhs.name.is(InlineAccessorName) + case untpd.TypedSplice(lhs1) => lhs1.symbol.name.is(InlineAccessorName) + case _ => false + } + val tree1 = super.transform(tree) tree1 match { case This(_) => adjustLeaf(tree1) - case Ident(name) => - adjustQualifier(adjustLeaf(cpy.Ident(tree1)(adjustName(name)))) - case Select(pre, name) => - cpy.Select(tree1)(pre, adjustName(name)) + case tree1: untpd.Ident => + adjustQualifier(adjustLeaf(adjustForAccessor(tree1))) + case tree1: untpd.Select => + adjustForAccessor(tree1) + case Assign(lhs, rhs) if isAccessorLHS(lhs) => + cpy.Apply(tree1)(lhs, rhs :: Nil) case tree: untpd.DerivedTypeTree => inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}") untpd.TypedSplice(ctx.typer.typed(tree)) @@ -366,11 +404,11 @@ object Inliner { seq(implicitBindings, untpdSplice) } - /** `sym` has an inline method with a known body to inline (note: definitions coming + /** `sym` has a transparent method with a known body to inline (note: definitions coming * from Scala2x class files might be `@forceInline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isInlineableMethod && sym.hasAnnotation(defn.BodyAnnot) + sym.isTransparentMethod && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) @@ -382,7 +420,7 @@ object Inliner { def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { def suppressInline = - ctx.owner.ownersIterator.exists(_.isInlineableMethod) || + ctx.owner.ownersIterator.exists(_.isTransparentMethod) || ctx.settings.YnoInline.value || ctx.isAfterTyper || ctx.reporter.hasErrors @@ -394,7 +432,7 @@ object Inliner { def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = meth.isTransparentMethod && isInlineable(meth) - /** Try to inline a call to a `@inline` method. Fail with error if the maximal + /** Try to inline a call to a transparent method. Fail with error if the maximal * inline depth is exceeded. * * @param tree The call to inline @@ -416,7 +454,7 @@ object Inliner { else errorTree( tree, i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, - |Maybe this is caused by a recursive inline method? + |Maybe this is caused by a recursive transparent method? |You can use -Xmax:inlines to change the limit.""", (tree :: enclosingInlineds).last.pos ) @@ -443,7 +481,7 @@ object Inliner { /** Produces an inlined version of `call` via its `inlined` method. * * @param call the original call to an `inline` method - * @param rhsToInline the body of the inline method that replaces the call. + * @param rhsToInline the body of the transparent method that replaces the call. */ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { import tpd._ @@ -456,7 +494,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // Make sure all type arguments to the call are fully determined for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) - /** A map from parameter names of the inline method to references of the actual arguments. + /** A map from parameter names of the transparent method to references of the actual arguments. * For a type argument this is the full argument type. * For a value argument, it is a reference to either the argument value * (if the argument is a pure expression of singleton type), or to `val` or `def` acting @@ -464,7 +502,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ private val paramBinding = new mutable.HashMap[Name, Type] - /** A map from references to (type and value) parameters of the inline method + /** A map from references to (type and value) parameters of the transparent method * to their corresponding argument or proxy references, as given by `paramBinding`. */ private val paramProxy = new mutable.HashMap[Type, Type] @@ -505,7 +543,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { bindingsBuf: mutable.ListBuffer[ValOrDefDef]): ValOrDefDef = { val argtpe = arg.tpe.dealiasKeepAnnots val isByName = paramtp.dealias.isInstanceOf[ExprType] - val inlineFlag = if (paramtp.hasAnnotation(defn.InlineParamAnnot)) Inline else EmptyFlags + val inlineFlag = if (paramtp.hasAnnotation(defn.TransparentParamAnnot)) Transparent else EmptyFlags val (bindingFlags, bindingType) = if (isByName) (Method, ExprType(argtpe.widen)) else (inlineFlag, argtpe.widen) @@ -708,6 +746,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) if (ctx.settings.verbose.value) { + inlining.println(i"to inline = $rhsToInline") inlining.println(i"original bindings = $bindings%\n%") inlining.println(i"original expansion = $expansion1") } @@ -791,7 +830,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = if (paramProxies.contains(tree.typeOpt)) bindingsBuf.find(_.name == tree.name) match { - case Some(vdef: ValDef) if vdef.symbol.is(Inline) => + case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) case Some(ddef: DefDef) => Some(ddef.rhs.changeOwner(ddef.symbol, ctx.owner)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 03f2d4280b41..ff3c5f7507e7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -754,13 +754,13 @@ class Namer { typer: Typer => val ann = Annotation.deferred(cls, implicit ctx => typedAnnotation(annotTree)) sym.addAnnotation(ann) if (cls == defn.ForceInlineAnnot && sym.is(Method, butNot = Accessor)) - sym.setFlag(Inline) + sym.setFlag(Transparent) } case _ => } private def addInlineInfo(sym: Symbol) = original match { - case original: untpd.DefDef if sym.isInlineableMethod => + case original: untpd.DefDef if sym.isTransparentMethod => Inliner.registerInlineInfo( sym, original.rhs, @@ -1061,7 +1061,7 @@ class Namer { typer: Typer => ctx.defContext(sym).denotNamed(original) def paramProto(paramss: List[List[Type]], idx: Int): Type = paramss match { case params :: paramss1 => - if (idx < params.length) wildApprox(params(idx), null, Set.empty) + if (idx < params.length) wildApprox(params(idx)) else paramProto(paramss1, idx - params.length) case nil => WildcardType @@ -1075,7 +1075,7 @@ class Namer { typer: Typer => // println(s"final inherited for $sym: ${inherited.toString}") !!! // println(s"owner = ${sym.owner}, decls = ${sym.owner.info.decls.show}") - def isInline = sym.is(FinalOrInlineOrTransparent, butNot = Method | Mutable) + def isInline = sym.is(FinalOrTransparent, butNot = Method | Mutable) // Widen rhs type and eliminate `|' but keep ConstantTypes if // definition is inline (i.e. final in Scala2) and keep module singleton types @@ -1111,7 +1111,7 @@ class Namer { typer: Typer => if (sym.is(Final, butNot = Method)) { val tp = lhsType if (tp.isInstanceOf[ConstantType]) - tp // keep constant types that fill in for a non-constant (to be revised when inline has landed). + tp // keep constant types that fill in for a non-constant (to be revised when transparent has landed). else inherited } else inherited diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index f38fb6db6e37..2aa2eb0423e0 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -72,11 +72,16 @@ object ProtoTypes { res } - /** Constrain result unless `meth` is a transparent method in an inlineable context. - * In the latter case we should inline before constraining the result. + /** Constrain result with special case if `meth` is a transparent method in an inlineable context. + * In that case, we should always succeed and not constrain type parameters in the expected type, + * because the actual return type can be a subtype of the currently known return type. + * However, we should constrain parameters of the declared return type. This distinction is + * achieved by replacing expected type parameters with wildcards. */ def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = - Inliner.isTransparentInlineable(meth) || constrainResult(mt, pt) + if (Inliner.isTransparentInlineable(meth)) + constrainResult(mt, wildApprox(pt)) || true + else constrainResult(mt, pt) } object NoViewsAllowed extends Compatibility { @@ -541,7 +546,7 @@ object ProtoTypes { /** Approximate occurrences of parameter types and uninstantiated typevars * by wildcard types. */ - final def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { + private def wildApprox(tp: Type, theMap: WildApproxMap, seen: Set[TypeParamRef])(implicit ctx: Context): Type = tp match { case tp: NamedType => // default case, inlined for speed if (tp.symbol.isStatic || (tp.prefix `eq` NoPrefix)) tp else tp.derivedSelect(wildApprox(tp.prefix, theMap, seen)) @@ -610,6 +615,8 @@ object ProtoTypes { .mapOver(tp) } + final def wildApprox(tp: Type)(implicit ctx: Context): Type = wildApprox(tp, null, Set.empty) + @sharable object AssignProto extends UncachedGroundType with MatchAlways private[ProtoTypes] class WildApproxMap(val seen: Set[TypeParamRef])(implicit ctx: Context) extends TypeMap { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 85e242347bed..5458f052b6e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -476,9 +476,9 @@ class Typer extends Namer case pt: SelectionProto if pt.name == nme.CONSTRUCTOR => true case _ => false } - val enclosingInlineable = ctx.owner.ownersIterator.findSymbol(_.isInlineableMethod) - if (enclosingInlineable.exists && !Inliner.isLocal(qual1.symbol, enclosingInlineable)) - ctx.error(SuperCallsNotAllowedInline(enclosingInlineable), tree.pos) + val enclosingTransparent = ctx.owner.ownersIterator.findSymbol(_.isTransparentMethod) + if (enclosingTransparent.exists && !Inliner.isLocal(qual1.symbol, enclosingTransparent)) + ctx.error(SuperCallsNotAllowedTransparent(enclosingTransparent), tree.pos) pt match { case pt: SelectionProto if pt.name.isTypeName => qual1 // don't do super references for types; they are meaningless anyway @@ -1072,8 +1072,8 @@ class Typer extends Namer (EmptyTree, WildcardType) } else if (owner != cx.outer.owner && owner.isRealMethod) { - if (owner.isInlineableMethod) - (EmptyTree, errorType(NoReturnFromInline(owner), tree.pos)) + if (owner.isTransparentMethod) + (EmptyTree, errorType(NoReturnFromTransparent(owner), tree.pos)) else if (!owner.isCompleted) (EmptyTree, errorType(MissingReturnTypeWithReturnStatement(owner), tree.pos)) else { @@ -1377,8 +1377,8 @@ class Typer extends Namer case rhs => normalizeErasedRhs(typedExpr(rhs, tpt1.tpe), sym) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) - if (sym.is(Inline, butNot = DeferredOrTermParamOrAccessor)) - checkInlineConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of inline $sym") + if (sym.is(Transparent, butNot = DeferredOrTermParamOrAccessor)) + checkTransparentConformant(rhs1, isFinal = sym.is(Final), em"right-hand side of transparent $sym") patchIfLazy(vdef1) patchFinalVals(vdef1) vdef1 @@ -1393,7 +1393,7 @@ class Typer extends Namer patch(Position(toUntyped(vdef).pos.start), "@volatile ") } - /** Adds inline to final vals with idempotent rhs + /** Adds transparent to final vals with idempotent rhs * * duplicating scalac behavior: for final vals that have rhs as constant, we do not create a field * and instead return the value. This seemingly minor optimization has huge effect on initialization @@ -1409,7 +1409,7 @@ class Typer extends Namer } val sym = vdef.symbol sym.info match { - case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Inline) + case info: ConstantType if isFinalInlinableVal(sym) && !ctx.settings.YnoInline.value => sym.setFlag(Transparent) case _ => } } @@ -1440,8 +1440,7 @@ class Typer extends Namer if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) - // Overwrite inline body to make sure it is not evaluated twice - if (sym.isInlineableMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) + if (sym.isTransparentMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1910,17 +1909,10 @@ class Typer extends Namer case none => typed(mdef) match { case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => - if (mdef1.symbol.isInlinedMethod) { - buf += inlineExpansion(mdef1) - // replace body with expansion, because it will be used as inlined body - // from separately compiled files - the original BodyAnnotation is not kept. - } - else { - assert(mdef1.symbol.isTransparentMethod, mdef.symbol) - Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed, - buf += mdef1 // but keep original definition, since inline-expanded code - // is pickled in this case. - } + assert(mdef1.symbol.isTransparentMethod, mdef.symbol) + Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed, + buf += mdef1 // but keep original definition, since inline-expanded code + // is pickled in this case. case mdef1 => import untpd.modsDeco mdef match { @@ -1952,7 +1944,7 @@ class Typer extends Namer checkEnumCompanions(traverse(stats)(localCtx), enumContexts) } - /** Given an inline method `mdef`, the method rewritten so that its body + /** Given a transparent method `mdef`, the method rewritten so that its body * uses accessors to access non-public members. * Overwritten in Retyper to return `mdef` unchanged. */ @@ -2372,12 +2364,13 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } - else if (Inliner.isInlineable(tree.symbol) && - (tree.symbol.isTransparentMethod || tree.tpe <:< pt)) + else if (Inliner.isInlineable(tree.symbol)) { + tree.tpe <:< wildApprox(pt) readaptSimplified(Inliner.inlineCall(tree, pt)) + } else if (tree.tpe <:< pt) { - if (pt.hasAnnotation(defn.InlineParamAnnot)) - checkInlineConformant(tree, isFinal = false, "argument to inline parameter") + if (pt.hasAnnotation(defn.TransparentParamAnnot)) + checkTransparentConformant(tree, isFinal = false, "argument to transparent parameter") if (ctx.typeComparer.GADTused && pt.isValueType) // Insert an explicit cast, so that -Ycheck in later phases succeeds. // I suspect, but am not 100% sure that this might affect inferred types, diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index 464b427c8993..b639adbfc66b 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -8,7 +8,7 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def inlineUnit = { val source = """ |class Foo { - | inline def foo: Int = 1 + | transparent def foo: Int = 1 | @forceInline def bar: Int = 1 | | def meth1: Unit = foo diff --git a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala index 32e8535e179a..c560d1905cff 100644 --- a/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala +++ b/compiler/test/dotty/tools/dotc/parsing/ModifiersParsingTest.scala @@ -120,7 +120,7 @@ class ModifiersParsingTest { | final val c = ??? | | abstract override def f: Boolean - | inline def g(n: Int) = ??? + | transparent def g(n: Int) = ??? | } """.stripMargin @@ -128,12 +128,12 @@ class ModifiersParsingTest { assert(source.field("b").modifiers == List(Mod.Lazy(), Mod.Private())) assert(source.field("c").modifiers == List(Mod.Final())) assert(source.field("f").modifiers == List(Mod.Abstract(), Mod.Override())) - assert(source.field("g").modifiers == List(Mod.Inline())) + assert(source.field("g").modifiers == List(Mod.Transparent())) } @Test def paramDef = { - var source: Tree = "def f(inline a: Int) = ???" - assert(source.defParam(0).modifiers == List(Mod.Inline())) + var source: Tree = "def f(transparent a: Int) = ???" + assert(source.defParam(0).modifiers == List(Mod.Transparent())) source = "def f(implicit a: Int, b: Int) = ???" assert(source.defParam(0).modifiers == List(Mod.Implicit())) diff --git a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala index 3fad2c2bc2a8..50cf7f0860df 100644 --- a/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala +++ b/compiler/test/dotty/tools/dotc/reporting/ErrorMessagesTests.scala @@ -826,7 +826,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { |} | |class B extends A { - | inline def bar(): Unit = super.foo() + | transparent def bar(): Unit = super.foo() |} """.stripMargin } @@ -834,7 +834,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { implicit val ctx: Context = ictx assertMessageCount(1, messages) val err :: Nil = messages - val SuperCallsNotAllowedInline(symbol) = err + val SuperCallsNotAllowedTransparent(symbol) = err assertEquals("method bar", symbol.show) } @@ -935,7 +935,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { @Test def noReturnInInline = checkMessagesAfter(FrontEnd.name) { """class BadFunction { - | inline def usesReturn: Int = { return 42 } + | transparent def usesReturn: Int = { return 42 } |} """.stripMargin }.expect { (ictx, messages) => @@ -943,7 +943,7 @@ class ErrorMessagesTests extends ErrorMessagesTest { assertMessageCount(1, messages) - val NoReturnFromInline(method) :: Nil = messages + val NoReturnFromTransparent(method) :: Nil = messages assertEquals("method usesReturn", method.show) } diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index f275cbcd497d..00d7524e86bf 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -263,14 +263,14 @@ ClsParamClauses ::= {ClsParamClause} [[nl] ‘(’ [FunArgMods] ClsParams ‘ ClsParamClause ::= [nl] ‘(’ [ClsParams] ‘)’ ClsParams ::= ClsParam {‘,’ ClsParam} ClsParam ::= {Annotation} ValDef(mods, id, tpe, expr) -- point of mods on val/var - [{Modifier} (‘val’ | ‘var’) | ‘inline’] Param + [{Modifier} (‘val’ | ‘var’) | ‘transparent’] Param Param ::= id ‘:’ ParamType [‘=’ Expr] | INT DefParamClauses ::= {DefParamClause} [[nl] ‘(’ [FunArgMods] DefParams ‘)’] DefParamClause ::= [nl] ‘(’ [DefParams] ‘)’ DefParams ::= DefParam {‘,’ DefParam} -DefParam ::= {Annotation} [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefParam ::= {Annotation} [‘transparent’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. ``` ### Bindings and Imports diff --git a/docs/docs/reference/dropped/weak-conformance.md b/docs/docs/reference/dropped/weak-conformance.md index 0c796f9e2cd9..a6c8d68d678c 100644 --- a/docs/docs/reference/dropped/weak-conformance.md +++ b/docs/docs/reference/dropped/weak-conformance.md @@ -66,7 +66,7 @@ assigning a type to a constant expression. The new rule is: __Examples:__ - inline val b = 33 + transparent val b = 33 def f(): Int = b + 1 List(b, 33, 'a') : List[Int] List(b, 33, 'a', f()) : List[Int] diff --git a/docs/docs/reference/inline.md b/docs/docs/reference/inline.md index 372bbeda73c1..56400170c8a9 100644 --- a/docs/docs/reference/inline.md +++ b/docs/docs/reference/inline.md @@ -1,20 +1,20 @@ --- layout: doc-page -title: Inline +title: Transparent --- -`inline` is a new modifier that guarantees that a definition will be +`transparent` is a new modifier that guarantees that a definition will be inlined at the point of use. Example: object Config { - inline val logging = false + transparent val logging = false } object Logger { private var indent = 0 - inline def log[T](msg: => String)(op: => T): T = + transparent def log[T](msg: => String)(op: => T): T = if (Config.logging) { println(s"${" " * indent}start $msg") indent += 1 @@ -26,15 +26,15 @@ inlined at the point of use. Example: else op } -The `Config` object contains a definition of an `inline` value +The `Config` object contains a definition of an `transparent` value `logging`. This means that `logging` is treated as a constant value, equivalent to its right-hand side `false`. The right-hand side of such -an inline val must itself be a [constant +an transparent val must itself be a [constant expression](#the-definition-of-constant-expression). Used in this way, -`inline` is equivalent to Java and Scala 2's `final`. `final` meaning +`transparent` is equivalent to Java and Scala 2's `final`. `final` meaning "constant" is still supported in Dotty, but will be phased out. -The `Logger` object contains a definition of an `inline` method `log`. +The `Logger` object contains a definition of an `transparent` method `log`. This method will always be inlined at the point of call. In the inlined code, an if-then-else with a constant condition will be @@ -57,29 +57,43 @@ If `Config.logging == false`, this will be rewritten to } Note that the arguments corresponding to the parameters `msg` and `op` -of the inline method `log` are defined before the inlined body (which +of the transparent method `log` are defined before the inlined body (which is in this case simply `op`). By-name parameters of the inlined method correspond to `def` bindings whereas by-value parameters correspond to `val` bindings. So if `log` was defined like this: - inline def log[T](msg: String)(op: => T): T = ... + transparent def log[T](msg: String)(op: => T): T = ... we'd get val msg = s"factorial($n)" -instead. This behavior is designed so that calling an inline method is +instead. This behavior is designed so that calling a transparent method is semantically the same as calling a normal method: By-value arguments are evaluated before the call whereas by-name arguments are evaluated each time they are referenced. As a consequence, it is often -preferable to make arguments of inline methods by-name in order to +preferable to make arguments of transparent methods by-name in order to avoid unnecessary evaluations. -Inline methods can be recursive. For instance, when called with a constant +For instance, here is how we can define a zero-overhead `foreach` method +that translates into a straightforward while loop without any indirection or +overhead: + + transparent def foreach(op: => Int => Unit): Unit = { + var i = from + while (i < end) { + op(i) + i += 1 + } + } + +By contrast, if `op` is a call-by-value parameter, it would be evaluated separately as a closure. + +Transparent methods can be recursive. For instance, when called with a constant exponent `n`, the following method for `power` will be implemented by -straight inline code without any loop or recursion. +straight transparent code without any loop or recursion. - inline def power(x: Double, n: Int): Double = + transparent def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { @@ -96,44 +110,26 @@ straight inline code without any loop or recursion. // val y3 = y2 * x // ^5 // y3 * y3 // ^10 -Parameters of inline methods can themselves be marked `inline`. This means -that the argument of an inline method is itself inlined in the inlined body of -the method. Using this scheme, we can define a zero-overhead `foreach` method -that translates into a straightforward while loop without any indirection or -overhead: - - inline def foreach(inline op: Int => Unit): Unit = { - var i = from - while (i < end) { - op(i) - i += 1 - } - } +Parameters of transparent methods can themselves be marked `transparent`. This means +that actual arguments to these parameters must be constant expressions. ### Relationship to `@inline`. -Existing Scala defines a `@inline` annotation which is used as a hint -for the backend to inline. For most purposes, this annotation is -superseded by the `inline` modifier. The modifier is more powerful -than the annotation: Expansion is guaranteed instead of best effort, +Scala also defines a `@inline` annotation which is used as a hint +for the backend to inline. The `transparent` modifier is a more powerful +option: Expansion is guaranteed instead of best effort, it happens in the frontend instead of in the backend, and it also applies -to method arguments and recursive methods. - -Since `inline` is now a keyword, it would be a syntax error to write -`@inline`. However, one can still refer to the annotation by putting -it in backticks, i.e. - - @`inline` def ... +to recursive methods. -The Dotty compiler ignores `@inline` annotated definitions. To cross -compile between both Dotty and Scalac, we introduce a new `@forceInline` -annotation which is equivalent to the new `inline` modifier. Note that -Scala 2 ignores the `@forceInLine` annotation, and one must use both -annotations to inline across the two compilers (i.e. `@forceInline @inline`). +To cross compile between both Dotty and Scalac, we introduce a new `@forceInline` +annotation which is equivalent to the new `transparent` modifier. Note that +Scala 2 ignores the `@forceInline` annotation, so one must use both +annotations to guarantee inlining for Dotty and at the same time hint inlining +for Scala 2 (i.e. `@forceInline @inline`). ### The definition of constant expression -Right-hand sides of inline values and of arguments for inline parameters +Right-hand sides of transparent values and of arguments for transparent parameters must be constant expressions in the sense defined by the [SLS § 6.24](https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#constant-expressions), including "platform-specific" extensions such as constant folding of @@ -141,5 +137,5 @@ pure numeric computations. ### Reference -For more info, see [PR #1492](https://github.com/lampepfl/dotty/pull/1492) and -[Scala SIP 28](http://docs.scala-lang.org/sips/pending/inline-meta.html). +For more info, see [PR #4768](https://github.com/lampepfl/dotty/pull/4768), which explains how +transparent methods can be used for typelevel programming and code specialization. diff --git a/docs/docs/reference/principled-meta-programming.md b/docs/docs/reference/principled-meta-programming.md index a3b972618e97..f4b14fcc9b17 100644 --- a/docs/docs/reference/principled-meta-programming.md +++ b/docs/docs/reference/principled-meta-programming.md @@ -19,14 +19,14 @@ operations: quotation and splicing. Quotation is expressed as `'(...)` or `'{...}` for expressions (both forms are equivalent) and as `'[...]` for types. Splicing is expressed as a prefix `~` operator. -For example, the code below presents an inline function `assert` +For example, the code below presents a transparent function `assert` which calls at compile-time a method `assertImpl` with a boolean expression tree as argument. `assertImpl` evaluates the expression and prints it again in an error message if it evaluates to `false`. import scala.quoted._ - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -220,10 +220,10 @@ is handled by the compiler, using the algorithm sketched above. ### Example Expansion -Assume an `Array` class with an inline `map` method that forwards to a macro implementation. +Assume an `Array` class with a transparent `map` method that forwards to a macro implementation. class Array[T] { - inline def map[U](f: T => U): Array[U] = ~ Macros.mapImpl[T, U]('[U], '(this), '(f)) + transparent def map[U](f: T => U): Array[U] = ~ Macros.mapImpl[T, U]('[U], '(this), '(f)) } Here’s the definition of the `mapImpl` macro, which takes quoted types and expressions to a quoted expression: @@ -303,11 +303,11 @@ Here’s an application of `map` and how it rewrites to optimized code: ys } -### Relationship with Inline and Macros +### Relationship with Transparent and Macros Seen by itself, principled meta-programming looks more like a framework for staging than one for compile-time meta programming with -macros. But combined with Dotty’s `inline` it can be turned into a +macros. But combined with Dotty’s `transparent` feature it can be turned into a compile-time system. The idea is that macro elaboration can be understood as a combination of a macro library and a quoted program. For instance, here’s the `assert` macro again together with a @@ -315,7 +315,7 @@ program that calls `assert`. object Macros { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -363,7 +363,7 @@ If `program` is treated as a quoted expression, the call to program are conceptualized as local definitions. But what about the call from `assert` to `assertImpl`? Here, we need a -tweak of the typing rules. An inline function such as `assert` that +tweak of the typing rules. A transparent function such as `assert` that contains a splice operation outside an enclosing quote is called a _macro_. Macros are supposed to be expanded in a subsequent phase, i.e. in a quoted context. Therefore, they are also type checked as if @@ -372,13 +372,13 @@ they were in a quoted context. For instance, the definition of the call from `assert` to `assertImpl` phase-correct, even if we assume that both definitions are local. -The second role of `inline` in Dotty is to mark a `val` that is +The second role of `transparent` in Dotty is to mark a `val` that is either a constant or is a parameter that will be a constant when instantiated. This aspect is also important for macro expansion. To illustrate this, consider an implementation of the `power` function that makes use of a statically known exponent: - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) private def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) @@ -390,13 +390,13 @@ The reference to `n` as an argument in `~powerCode(n, '(x))` is not phase-consistent, since `n` appears in a splice without an enclosing quote. Normally that would be a problem because it means that we need the _value_ of `n` at compile time, which is not available for general -parameters. But since `n` is an inline parameter of a macro, we know +parameters. But since `n` is a transparent parameter of a macro, we know that at the macro’s expansion point `n` will be instantiated to a constant, so the value of `n` will in fact be known at this point. To reflect this, we loosen the phase consistency requirements as follows: - - If `x` is an inline value (or an inline parameter of an inline + - If `x` is a transparent value (or a transparent parameter of a transparent function) of type Boolean, Byte, Short, Int, Long, Float, Double, Char or String, it can be accessed in all contexts where the number of splices minus the number of quotes between use and definition @@ -433,7 +433,7 @@ Providing an interpreter for the full language is quite difficult, and it is even more difficult to make that interpreter run efficiently. So we currently impose the following restrictions on the use of splices. - 1. A top-level splice must appear in an inline function (turning that function + 1. A top-level splice must appear in a transparent function (turning that function into a macro) 2. The splice must call a previously compiled method. @@ -762,14 +762,14 @@ is studied [separately](simple-smp.md). The meta-programming framework as presented and currently implemented is quite restrictive in that it does not allow for the inspection of quoted expressions and types. It’s possible to work around this by providing all necessary -information as normal, unquoted inline parameters. But we would gain +information as normal, unquoted transparent parameters. But we would gain more flexibility by allowing for the inspection of quoted code with pattern matching. This opens new possibilities. For instance, here is a version of `power` that generates the multiplications directly if the exponent is statically known and falls back to the dynamic implementation of power otherwise. - inline def power(n: Int, x: Double): Double = ~{ + transparent def power(n: Int, x: Double): Double = ~{ '(n) match { case Constant(n1) => powerCode(n1, '(x)) case _ => '{ dynamicPower(n, x) } diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 8b448fb7ac01..d6d7cc681dfc 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -43,7 +43,7 @@ sidebar: url: docs/reference/multiversal-equality.html - title: Trait Parameters url: docs/reference/trait-parameters.html - - title: Inline + - title: Inlining with Transparent url: docs/reference/inline.html - title: Meta Programming url: docs/reference/principled-meta-programming.html diff --git a/library/src/scala/annotation/internal/Body.scala b/library/src/scala/annotation/internal/Body.scala index b6aa0c0fb616..abc5ba1b7682 100644 --- a/library/src/scala/annotation/internal/Body.scala +++ b/library/src/scala/annotation/internal/Body.scala @@ -3,6 +3,6 @@ package scala.annotation.internal import scala.annotation.Annotation /** The class associated with a `BodyAnnotation`, which indicates - * an inline method's right hand side + * a transparent method's right hand side */ final class Body() extends Annotation diff --git a/library/src/scala/annotation/internal/InlineParam.scala b/library/src/scala/annotation/internal/InlineParam.scala deleted file mode 100644 index 0b3649e89da9..000000000000 --- a/library/src/scala/annotation/internal/InlineParam.scala +++ /dev/null @@ -1,6 +0,0 @@ -package scala.annotation.internal - -import scala.annotation.Annotation - -/** An annotation produced by Namer to indicate an inline parameter */ -final class InlineParam() extends Annotation diff --git a/library/src/scala/annotation/internal/TransparentParam.scala b/library/src/scala/annotation/internal/TransparentParam.scala new file mode 100644 index 000000000000..1560fa073449 --- /dev/null +++ b/library/src/scala/annotation/internal/TransparentParam.scala @@ -0,0 +1,6 @@ +package scala.annotation.internal + +import scala.annotation.Annotation + +/** An annotation produced by Namer to indicate a transparent parameter */ +final class TransparentParam() extends Annotation diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index 4c87ca4a5b7f..12ef1915e933 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -7,7 +7,7 @@ sealed abstract class Expr[T] { /** Evaluate the contents of this expression and return the result. * - * May throw a FreeVariableError on expressions that came from an inline macro. + * May throw a FreeVariableError on expressions that came from a transparent macro. */ final def run(implicit toolbox: Toolbox): T = toolbox.run(this) @@ -44,7 +44,7 @@ object Exprs { /** An Expr backed by a tree. Only the current compiler trees are allowed. * - * These expressions are used for arguments of inline macros. They contain and actual tree + * These expressions are used for arguments of transparent macros. They contain and actual tree * from the program that is being expanded by the macro. * * May contain references to code defined outside this TastyTreeExpr instance. diff --git a/library/src/scala/quoted/QuoteError.scala b/library/src/scala/quoted/QuoteError.scala index 8db441d90b04..1b800240fc44 100644 --- a/library/src/scala/quoted/QuoteError.scala +++ b/library/src/scala/quoted/QuoteError.scala @@ -1,6 +1,6 @@ package scala.quoted -/** Throwing this error in the implementation of an inline macro +/** Throwing this error in the implementation of a transparent macro * will result in a compilation error with the given message. */ class QuoteError(message: String) extends Throwable(message) diff --git a/library/src/scala/tasty/FlagSet.scala b/library/src/scala/tasty/FlagSet.scala index 571a0ffa564b..0acfffa3bfc6 100644 --- a/library/src/scala/tasty/FlagSet.scala +++ b/library/src/scala/tasty/FlagSet.scala @@ -10,8 +10,8 @@ trait FlagSet { def isErased: Boolean def isLazy: Boolean def isOverride: Boolean - def isInline: Boolean - def isMacro: Boolean // inline method containing toplevel splices + def isTransparent: Boolean + def isMacro: Boolean // transparent method containing toplevel splices def isStatic: Boolean // mapped to static Java member def isObject: Boolean // an object or its class (used for a ValDef or a ClassDef extends Modifier respectively) def isTrait: Boolean // a trait (used for a ClassDef) diff --git a/library/src/scala/tasty/TopLevelSplice.scala b/library/src/scala/tasty/TopLevelSplice.scala index 9d5392860bd7..034f211c0c2a 100644 --- a/library/src/scala/tasty/TopLevelSplice.scala +++ b/library/src/scala/tasty/TopLevelSplice.scala @@ -3,5 +3,5 @@ package scala.tasty /** Context in a top level ~ at inline site, intrinsically as `import TopLevelSplice._` */ object TopLevelSplice { /** Compiler tasty context available in a top level ~ at inline site */ - implicit def tastyContext: Tasty = throw new Exception("Not in inline macro.") + implicit def tastyContext: Tasty = throw new Exception("Not in transparent macro.") } diff --git a/library/src/scala/tasty/util/ShowSourceCode.scala b/library/src/scala/tasty/util/ShowSourceCode.scala index 7d3d7bd479a4..41600488d159 100644 --- a/library/src/scala/tasty/util/ShowSourceCode.scala +++ b/library/src/scala/tasty/util/ShowSourceCode.scala @@ -247,7 +247,7 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty val flags = ddef.flags if (flags.isImplicit) this += "implicit " - if (flags.isInline) this += "inline " + if (flags.isTransparent) this += "transparent " if (flags.isOverride) this += "override " printProtectedOrPrivate(ddef) diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala index fc714b93b0a9..1032753814f2 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B1.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Int = + transparent def getInline: Int = A.get } diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala index 9a3abb0d9792..ca7395adf16d 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B2.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Double = + transparent def getInline: Double = A.get } diff --git a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala index 2fffbde1bbf9..a121f6672300 100644 --- a/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala +++ b/sbt-dotty/sbt-test/source-dependencies/inline/changes/B3.scala @@ -1,4 +1,4 @@ object B { - inline def getInline: Int = + transparent def getInline: Int = sys.error("This is an expected failure when running C") } diff --git a/tests/neg/i1568.scala b/tests/neg/i1568.scala index a260c530b927..2e3b2dae2685 100644 --- a/tests/neg/i1568.scala +++ b/tests/neg/i1568.scala @@ -1,3 +1,3 @@ object Test { - inline def foo(n: Int) = foo(n) // error: cyclic reference + transparent def foo(n: Int) = foo(n) // error: cyclic reference } diff --git a/tests/neg/i1605.scala b/tests/neg/i1605.scala index f5fd9f29bde5..0515ec751df0 100644 --- a/tests/neg/i1605.scala +++ b/tests/neg/i1605.scala @@ -1,5 +1,5 @@ object Test { def foo = inlineMe - inline def inlineMe = 1 + x2233 // error + transparent def inlineMe = 1 + x2233 // error } diff --git a/tests/neg/i2006.scala b/tests/neg/i2006.scala index f1b48b011384..910d5fc1e2f6 100644 --- a/tests/neg/i2006.scala +++ b/tests/neg/i2006.scala @@ -1,7 +1,7 @@ object Test { - inline def foo(f: ImplicitFunction1[Int, Int]): AnyRef = f // error - inline def bar(f: ImplicitFunction1[Int, Int]) = f // error + transparent def foo(f: ImplicitFunction1[Int, Int]): AnyRef = f // error + transparent def bar(f: ImplicitFunction1[Int, Int]) = f // error def main(args: Array[String]) = { foo(implicit thisTransaction => 43) diff --git a/tests/neg/i2421.scala b/tests/neg/i2421.scala index 759145119e7b..ff2ef194b430 100644 --- a/tests/neg/i2421.scala +++ b/tests/neg/i2421.scala @@ -1,10 +1,10 @@ -inline object Foo // OK (error would be detected later, in PostTyper) -inline class Bar // error: modifier(s) `inline' incompatible with type definition -inline abstract class Baz // error: modifier(s) `inline' incompatible with type definition -inline trait Qux // error: modifier(s) `inline' incompatible with type definition +transparent object Foo // OK (error would be detected later, in PostTyper) +transparent class Bar // error: modifier(s) `transparent' incompatible with type definition +transparent abstract class Baz // error: modifier(s) `transparent' incompatible with type definition +transparent trait Qux // error: modifier(s) `transparent' incompatible with type definition object Quux { - inline type T // error: modifier(s) `inline' incompatible with type definition - inline var x: Int = 42 // error: modifier(s) `inline' incompatible with var definition - inline lazy val y: Int = 43 // error: modifier(s) `inline' incompatible with lazy val definition + transparent type T // error: modifier(s) `transparent' incompatible with type definition + transparent var x: Int = 42 // error: modifier(s) `transparent' incompatible with var definition + transparent lazy val y: Int = 43 // error: modifier(s) `transparent' incompatible with lazy val definition } diff --git a/tests/neg/i2564.scala b/tests/neg/i2564.scala index 4d8072f99297..f0f63dc4b642 100644 --- a/tests/neg/i2564.scala +++ b/tests/neg/i2564.scala @@ -1,4 +1,4 @@ object Foo { - inline def bar = new Bar // error + transparent def bar = new Bar // error class Bar private[Foo]() } diff --git a/tests/neg/i2564b.scala b/tests/neg/i2564b.scala index 508f0ed9a8b8..48e230654bb8 100644 --- a/tests/neg/i2564b.scala +++ b/tests/neg/i2564b.scala @@ -1,3 +1,3 @@ class Foo private() { - inline def foo = new Foo // error + transparent def foo = new Foo // error } diff --git a/tests/neg/i2901.scala b/tests/neg/i2901.scala index 9c9ff616d205..a1f13dfa4ee1 100644 --- a/tests/neg/i2901.scala +++ b/tests/neg/i2901.scala @@ -2,7 +2,7 @@ trait Foo { def foo = 4 } object Bar extends Foo { - inline def bar = super[Foo].foo // error + transparent def bar = super[Foo].foo // error } object Main { Bar.bar diff --git a/tests/neg/i3900.scala b/tests/neg/i3900.scala index c64bd6bca71b..7439610e9696 100644 --- a/tests/neg/i3900.scala +++ b/tests/neg/i3900.scala @@ -1,7 +1,7 @@ -class Foo(inline val i: Int) // error -case class Foo2(inline val i: Int) // error -class Foo3(inline val i: Int) extends AnyVal // error -trait Foo4(inline val i: Int) // error +class Foo(transparent val i: Int) // error +case class Foo2(transparent val i: Int) // error +class Foo3(transparent val i: Int) extends AnyVal // error +trait Foo4(transparent val i: Int) // error class Foo5() { - def this(inline x: Int) = this() // error + def this(transparent x: Int) = this() // error } diff --git a/tests/neg/i4433.scala b/tests/neg/i4433.scala index 49237a6e8ac5..4f8226f55e64 100644 --- a/tests/neg/i4433.scala +++ b/tests/neg/i4433.scala @@ -1,6 +1,6 @@ object Foo { - inline def g(inline p: Int => Boolean): Boolean = ~{ + transparent def g(transparent p: Int => Boolean): Boolean = ~{ if(p(5)) '(true) // error else '(false) } diff --git a/tests/neg/inlineAccess/C_1.scala b/tests/neg/inlineAccess/C_1.scala index 9d34fa3f0f1a..f35ef60c88d3 100644 --- a/tests/neg/inlineAccess/C_1.scala +++ b/tests/neg/inlineAccess/C_1.scala @@ -1,7 +1,7 @@ package p private class D class C { - inline def inl(): Unit = { + transparent def inl(): Unit = { val d = new D() // error (when inlined): not accessible } } diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala index 6c8c4eaf6327..52a2caa67907 100644 --- a/tests/neg/inlinevals.scala +++ b/tests/neg/inlinevals.scala @@ -1,29 +1,29 @@ object Test { - def power(x: Double, inline n: Int): Double = ??? + def power(x: Double, transparent n: Int): Double = ??? - inline val N = 10 + transparent val N = 10 def X = 20 - inline inline val twice = 30 // error: repeated modifier + transparent transparent val twice = 30 // error: repeated modifier - class C(inline x: Int, private inline val y: Int) { // error // error - inline val foo: Int // error: abstract member may not be inline - inline def bar: Int // error: abstract member may not be inline + class C(transparent x: Int, private transparent val y: Int) { // error // error + transparent val foo: Int // error: abstract member may not be inline + transparent def bar: Int // error: abstract member may not be inline } power(2.0, N) // ok, since it's a by-name parameter - power(2.0, X) // error: argument to inline parameter must be a constant expression + power(2.0, X) // error: argument to transparent parameter must be a constant expression - inline val M = X // error: rhs must be constant expression + transparent val M = X // error: rhs must be constant expression - inline val xs = List(1, 2, 3) // error: must be a constant expression + transparent val xs = List(1, 2, 3) // error: must be a constant expression - def f(inline xs: List[Int]) = xs + def f(transparent xs: List[Int]) = xs f(List(1, 2, 3)) // error: must be a constant expression - def byname(inline f: => String): Int = ??? // ok + def byname(transparent f: => String): Int = ??? // ok byname("hello" ++ " world") diff --git a/tests/neg/power.scala b/tests/neg/power.scala index 91a38a825b22..8b17717495a1 100644 --- a/tests/neg/power.scala +++ b/tests/neg/power.scala @@ -1,6 +1,6 @@ object Test { - inline def power(x: Double, n: Int): Double = + transparent def power(x: Double, n: Int): Double = if (n == 0) 1.0 else if (n == 1) x else { diff --git a/tests/neg/quote-MacroOverride.scala b/tests/neg/quote-MacroOverride.scala index cfc0a7ebb374..8863ba82191e 100644 --- a/tests/neg/quote-MacroOverride.scala +++ b/tests/neg/quote-MacroOverride.scala @@ -2,11 +2,11 @@ object Test { abstract class A { def f(): Unit - inline def g(): Unit = () + transparent def g(): Unit = () } object B extends A { - inline def f() = ~('()) // error: may not override + transparent def f() = ~('()) // error: may not override override def g() = () // error: may not override } diff --git a/tests/neg/quote-error-2/Macro_1.scala b/tests/neg/quote-error-2/Macro_1.scala index 0cce8284f871..dd65339495db 100644 --- a/tests/neg/quote-error-2/Macro_1.scala +++ b/tests/neg/quote-error-2/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = '(println(~msg(b))) diff --git a/tests/neg/quote-error/Macro_1.scala b/tests/neg/quote-error/Macro_1.scala index d6fbeb9a9ed9..aae163486c95 100644 --- a/tests/neg/quote-error/Macro_1.scala +++ b/tests/neg/quote-error/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = if (b) '(println("foo(true)")) else QuoteError("foo cannot be called with false") diff --git a/tests/neg/quote-exception/Macro_1.scala b/tests/neg/quote-exception/Macro_1.scala index 256f482db18c..33e9eab33715 100644 --- a/tests/neg/quote-exception/Macro_1.scala +++ b/tests/neg/quote-exception/Macro_1.scala @@ -1,7 +1,7 @@ import quoted._ object Macro_1 { - inline def foo(inline b: Boolean): Unit = ~fooImpl(b) + transparent def foo(transparent b: Boolean): Unit = ~fooImpl(b) def fooImpl(b: Boolean): Expr[Unit] = if (b) '(println("foo(true)")) else ??? diff --git a/tests/neg/quote-interpolator-core-old.scala b/tests/neg/quote-interpolator-core-old.scala index 43eeef3ff95f..5ad1f7a87e57 100644 --- a/tests/neg/quote-interpolator-core-old.scala +++ b/tests/neg/quote-interpolator-core-old.scala @@ -5,9 +5,9 @@ import scala.quoted._ object FInterpolation { implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal { - inline def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any): String = ~fInterpolation(sc, Seq('(arg1))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any, arg2: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2))) // error: Inline macro method must be a static method + transparent def ff(arg1: Any, arg2: Any, arg3: Any): String = ~fInterpolation(sc, Seq('(arg1), '(arg2), '(arg3))) // error: Inline macro method must be a static method // ... } diff --git a/tests/neg/quote-macro-splice.scala b/tests/neg/quote-macro-splice.scala index e854a76a77f7..52c7042c32d0 100644 --- a/tests/neg/quote-macro-splice.scala +++ b/tests/neg/quote-macro-splice.scala @@ -2,22 +2,22 @@ import scala.quoted._ object Test { - inline def foo1: Int = { // error + transparent def foo1: Int = { // error println() ~impl(1.toExpr) } - inline def foo2: Int = { // error + transparent def foo2: Int = { // error ~impl(1.toExpr) ~impl(2.toExpr) } - inline def foo3: Int = { // error + transparent def foo3: Int = { // error val a = 1 ~impl('(a)) } - inline def foo4: Int = { // error + transparent def foo4: Int = { // error ~impl('(1)) 1 } diff --git a/tests/neg/quote-non-static-macro.scala b/tests/neg/quote-non-static-macro.scala index bf90545bb3b4..32600eae7bb7 100644 --- a/tests/neg/quote-non-static-macro.scala +++ b/tests/neg/quote-non-static-macro.scala @@ -1,18 +1,18 @@ import scala.quoted._ class Foo { - inline def foo: Unit = ~Foo.impl // error + transparent def foo: Unit = ~Foo.impl // error object Bar { - inline def foo: Unit = ~Foo.impl // error + transparent def foo: Unit = ~Foo.impl // error } } object Foo { class Baz { - inline def foo: Unit = ~impl // error + transparent def foo: Unit = ~impl // error } object Quox { - inline def foo: Unit = ~Foo.impl + transparent def foo: Unit = ~Foo.impl } def impl: Expr[Unit] = '() } diff --git a/tests/neg/quote-run-in-macro-1/quoted_1.scala b/tests/neg/quote-run-in-macro-1/quoted_1.scala index f54a4fe00888..aa9191e1167f 100644 --- a/tests/neg/quote-run-in-macro-1/quoted_1.scala +++ b/tests/neg/quote-run-in-macro-1/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foo(i: => Int): Int = ~{ + transparent def foo(i: => Int): Int = ~{ val y: Int = ('(i)).run y.toExpr } diff --git a/tests/neg/quote-run-in-macro-2/quoted_1.scala b/tests/neg/quote-run-in-macro-2/quoted_1.scala index f54a4fe00888..aa9191e1167f 100644 --- a/tests/neg/quote-run-in-macro-2/quoted_1.scala +++ b/tests/neg/quote-run-in-macro-2/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foo(i: => Int): Int = ~{ + transparent def foo(i: => Int): Int = ~{ val y: Int = ('(i)).run y.toExpr } diff --git a/tests/neg/tasty-macro-assert/quoted_1.scala b/tests/neg/tasty-macro-assert/quoted_1.scala index 66076341fc2d..8900e1538fbe 100644 --- a/tests/neg/tasty-macro-assert/quoted_1.scala +++ b/tests/neg/tasty-macro-assert/quoted_1.scala @@ -11,7 +11,7 @@ object Asserts { object Ops - inline def macroAssert(cond: Boolean): Unit = + transparent def macroAssert(cond: Boolean): Unit = ~impl('(cond))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(cond: Expr[Boolean])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/pickling/i2166.scala b/tests/pickling/i2166.scala index f796031d009d..9b4addb2436b 100644 --- a/tests/pickling/i2166.scala +++ b/tests/pickling/i2166.scala @@ -1,5 +1,5 @@ object Test { - inline def f = "" match { case _ => false } + transparent def f = "" match { case _ => false } def main(args: Array[String]): Unit = f } \ No newline at end of file diff --git a/tests/pickling/i3608.scala b/tests/pickling/i3608.scala index 2751d5d017dd..f4131874cbbc 100644 --- a/tests/pickling/i3608.scala +++ b/tests/pickling/i3608.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = new Bar + transparent def inlineMeth: Unit = new Bar } class Bar } diff --git a/tests/pickling/i4006.scala b/tests/pickling/i4006.scala index ef86dbb052f1..485fdbe59d55 100644 --- a/tests/pickling/i4006.scala +++ b/tests/pickling/i4006.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } finally println("Hello") + transparent def foo: Int = try { 1 } finally println("Hello") foo } diff --git a/tests/pickling/i4006b.scala b/tests/pickling/i4006b.scala index 3c17a6522ac4..286d8a64435a 100644 --- a/tests/pickling/i4006b.scala +++ b/tests/pickling/i4006b.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } catch { case _ => 4 } finally println("Hello") + transparent def foo: Int = try { 1 } catch { case _ => 4 } finally println("Hello") foo } diff --git a/tests/pickling/i4006c.scala b/tests/pickling/i4006c.scala index 9233ccdfc922..fd1951008f20 100644 --- a/tests/pickling/i4006c.scala +++ b/tests/pickling/i4006c.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = try { 1 } catch { case _ => 4 } + transparent def foo: Int = try { 1 } catch { case _ => 4 } foo } diff --git a/tests/pos/SI-7060.scala b/tests/pos/SI-7060.scala index d6f172d45283..7e0eab453b61 100644 --- a/tests/pos/SI-7060.scala +++ b/tests/pos/SI-7060.scala @@ -1,6 +1,6 @@ object Test { - inline final def mbarray_apply_minibox(array: Any, tag: Byte): Long = + transparent final def mbarray_apply_minibox(array: Any, tag: Byte): Long = if (tag == 0) { array.asInstanceOf[Array[Long]](0) } else diff --git a/tests/pos/depfuntype.scala b/tests/pos/depfuntype.scala index 308b7aecc58d..43afbb0c434b 100644 --- a/tests/pos/depfuntype.scala +++ b/tests/pos/depfuntype.scala @@ -19,7 +19,7 @@ object Test { // Reproduced here because the one from DottyPredef is lacking a Tasty tree and // therefore can't be inlined when testing non-bootstrapped. // But inlining `implicitly` is vital to make the definition of `ifun` below work. - inline final def implicitly[T](implicit ev: T): T = ev + transparent final def implicitly[T](implicit ev: T): T = ev type IDF = implicit (x: C) => x.M diff --git a/tests/pos/harmonize.scala b/tests/pos/harmonize.scala index afbbeb058bae..ea0f60c867c2 100644 --- a/tests/pos/harmonize.scala +++ b/tests/pos/harmonize.scala @@ -3,7 +3,7 @@ object Test { def main(args: Array[String]) = { val x = true val n = 1 - inline val nn = 2 + transparent val nn = 2 val y = if (x) 'A' else n val z: Int = y @@ -41,7 +41,7 @@ object Test { } - inline val b = 33 + transparent val b = 33 def f(): Int = b + 1 val a1 = Array(b, 33, 'a') val b1: Array[Int] = a1 diff --git a/tests/pos/i1570.decompiled b/tests/pos/i1570.decompiled index 3f4d040e376b..31051997bd70 100644 --- a/tests/pos/i1570.decompiled +++ b/tests/pos/i1570.decompiled @@ -1,5 +1,5 @@ /** Decompiled from out/posTestFromTasty/pos/i1570/Test.class */ object Test { - inline def foo(n: scala.Int): scala.Int = Test.bar(n) - inline def bar(n: scala.Int): scala.Int = n + transparent def foo(n: scala.Int): scala.Int = Test.bar(n) + transparent def bar(n: scala.Int): scala.Int = n } diff --git a/tests/pos/i1570.scala b/tests/pos/i1570.scala index c2e4fd01b02d..c3fa7538d800 100644 --- a/tests/pos/i1570.scala +++ b/tests/pos/i1570.scala @@ -1,4 +1,4 @@ object Test { - inline def foo(inline n: Int) = bar(n) - inline def bar(inline n: Int) = n + transparent def foo(transparent n: Int) = bar(n) + transparent def bar(transparent n: Int) = n } diff --git a/tests/pos/i1891.scala b/tests/pos/i1891.scala index 794b27a35e8e..d994e6d4118e 100644 --- a/tests/pos/i1891.scala +++ b/tests/pos/i1891.scala @@ -4,7 +4,7 @@ object Test { type T2[A, B] = CC2[A, B] class ArrowAssoc[A](val self: A) { - inline def f[B](y: B): CC2[A, B] = new CC2(self, y) + transparent def f[B](y: B): CC2[A, B] = new CC2(self, y) } def foo = (new ArrowAssoc(1)).f(2) diff --git a/tests/pos/i1990.scala b/tests/pos/i1990.scala index 77cea0af73d3..432b17f253fe 100644 --- a/tests/pos/i1990.scala +++ b/tests/pos/i1990.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { new Bar } } diff --git a/tests/pos/i1990a.scala b/tests/pos/i1990a.scala index f6f95ee364ed..d01cd8460019 100644 --- a/tests/pos/i1990a.scala +++ b/tests/pos/i1990a.scala @@ -1,6 +1,6 @@ class A { self => class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { println(self) } } diff --git a/tests/pos/i2056.scala b/tests/pos/i2056.scala index c4d020fb6281..7a19810ec46b 100644 --- a/tests/pos/i2056.scala +++ b/tests/pos/i2056.scala @@ -1,5 +1,5 @@ object Test { - inline def crash() = { + transparent def crash() = { try { println("hi") } catch { diff --git a/tests/pos/i2980.scala b/tests/pos/i2980.scala index b28b5614c921..7512c35bfed1 100644 --- a/tests/pos/i2980.scala +++ b/tests/pos/i2980.scala @@ -3,7 +3,7 @@ trait Foo { } object Foo { - inline def foo: Foo = new Foo { + transparent def foo: Foo = new Foo { def apply[~>[_,_]](x: Int ~> Int): Int ~> Int = x } diff --git a/tests/pos/i3050.scala b/tests/pos/i3050.scala index 799dbdc1a0dc..4366dc7887fd 100644 --- a/tests/pos/i3050.scala +++ b/tests/pos/i3050.scala @@ -1,10 +1,10 @@ object Test { - inline def openImpl(): Int = + transparent def openImpl(): Int = Some(42) match { case Some(i) => i } def open(): Int = openImpl() - inline def openImpl2(): Int = + transparent def openImpl2(): Int = Some(42) match { case None => 42 } def open2(): Int = openImpl2() diff --git a/tests/pos/i3082.scala b/tests/pos/i3082.scala index eece42fa92aa..3b4b285bc2fb 100644 --- a/tests/pos/i3082.scala +++ b/tests/pos/i3082.scala @@ -1,6 +1,6 @@ object Test { private def foo(arg1: Int): Int = { - inline def bar: Int = foo(0) + transparent def bar: Int = foo(0) if (arg1 == 0) 0 else bar } assert(foo(11) == 0) diff --git a/tests/pos/i3129.scala b/tests/pos/i3129.scala index a864a03a204c..85b248b52428 100644 --- a/tests/pos/i3129.scala +++ b/tests/pos/i3129.scala @@ -1,5 +1,5 @@ object companions2 { - inline def foo() = { + transparent def foo() = { class C { println(C.p) } @@ -15,7 +15,7 @@ class A { class B { private def getAncestor2(p: A): A = p - private inline def getAncestor(p: A): A = { + private transparent def getAncestor(p: A): A = { p.b.getAncestor(p) } } diff --git a/tests/pos/i3130a.scala b/tests/pos/i3130a.scala index b752986a044e..c3e2b0115e50 100644 --- a/tests/pos/i3130a.scala +++ b/tests/pos/i3130a.scala @@ -5,6 +5,6 @@ object O { class D(val x: Int) { class DD() object DD { - inline def apply() = x // new DD() + transparent def apply() = x // new DD() } } diff --git a/tests/pos/i3130b.scala b/tests/pos/i3130b.scala index 09f12ff0441c..ac2d63766eec 100644 --- a/tests/pos/i3130b.scala +++ b/tests/pos/i3130b.scala @@ -1,6 +1,6 @@ class Outer { trait F { def f(): Int } - inline def inner: F = { + transparent def inner: F = { class InnerClass(x: Int) extends F { def this() = this(3) def f() = x diff --git a/tests/pos/i3130c.scala b/tests/pos/i3130c.scala index 39b31a81e088..70610330588a 100644 --- a/tests/pos/i3130c.scala +++ b/tests/pos/i3130c.scala @@ -6,7 +6,7 @@ trait Test { trait TreeBuilder { val global: Global - inline def set(tree: global.Tree) = {} + transparent def set(tree: global.Tree) = {} } val nsc: Global diff --git a/tests/pos/i3130d.scala b/tests/pos/i3130d.scala index 1d375892cd8f..760955167dfb 100644 --- a/tests/pos/i3130d.scala +++ b/tests/pos/i3130d.scala @@ -1,6 +1,6 @@ class D(x: Int) { class DD { - inline def apply() = new DD() + transparent def apply() = new DD() } val inner = new DD } diff --git a/tests/pos/i3488.scala b/tests/pos/i3488.scala index 762ec4b7df30..2f4c1a3a3589 100644 --- a/tests/pos/i3488.scala +++ b/tests/pos/i3488.scala @@ -4,7 +4,7 @@ class Sett[A] { def incl(elem: A): Sett[A] = ??? - inline final def + (elem: A): Sett[A] = incl(elem) + transparent final def + (elem: A): Sett[A] = incl(elem) } object Sett { diff --git a/tests/pos/i3597.scala b/tests/pos/i3597.scala index 04e33975ea2c..fadd09047f14 100644 --- a/tests/pos/i3597.scala +++ b/tests/pos/i3597.scala @@ -1,3 +1,3 @@ object Test { - def bar(inline n: Int) = n + def bar(transparent n: Int) = n } diff --git a/tests/pos/i3608.scala b/tests/pos/i3608.scala index 2751d5d017dd..f4131874cbbc 100644 --- a/tests/pos/i3608.scala +++ b/tests/pos/i3608.scala @@ -1,6 +1,6 @@ class A { class Foo { - inline def inlineMeth: Unit = new Bar + transparent def inlineMeth: Unit = new Bar } class Bar } diff --git a/tests/pos/i3633.scala b/tests/pos/i3633.scala index a021e9ca077f..0eb758310026 100644 --- a/tests/pos/i3633.scala +++ b/tests/pos/i3633.scala @@ -1,4 +1,4 @@ class Test { - inline def foo = 1 + transparent def foo = 1 def test = -foo } diff --git a/tests/pos/i3636.scala b/tests/pos/i3636.scala index 9597bf25ba8b..768ff7a7ddec 100644 --- a/tests/pos/i3636.scala +++ b/tests/pos/i3636.scala @@ -1,11 +1,11 @@ trait Iterable[A] { def concat[B >: A](that: Iterable[B]): Iterable[B] = ??? - inline final def ++ [B >: A](that: Iterable[B]): Iterable[B] = concat(that) + transparent final def ++ [B >: A](that: Iterable[B]): Iterable[B] = concat(that) } class BitSet extends Iterable[Int] { def concat(that: Iterable[Int]): BitSet = ??? - inline final def ++ (that: Iterable[Int]): BitSet = concat(that) + transparent final def ++ (that: Iterable[Int]): BitSet = concat(that) } class Test { diff --git a/tests/pos/i3873.scala b/tests/pos/i3873.scala index ebb8b1cd70c9..850ef1687097 100644 --- a/tests/pos/i3873.scala +++ b/tests/pos/i3873.scala @@ -1,5 +1,5 @@ object Test { - inline def sum2(ys: List[Int]): Unit = { + transparent def sum2(ys: List[Int]): Unit = { ys.foldLeft(1) } val h1: ((List[Int]) => Unit) = sum2 diff --git a/tests/pos/i3898/quoted_1.scala b/tests/pos/i3898/quoted_1.scala index 61fa38781138..f59c55ac94f5 100644 --- a/tests/pos/i3898/quoted_1.scala +++ b/tests/pos/i3898/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(args: Any*): String = ~impl('(args)) + transparent def ff(args: Any*): String = ~impl('(args)) def impl(args: Expr[Seq[Any]]): Expr[String] = '("") } diff --git a/tests/pos/i3898b/quoted_1.scala b/tests/pos/i3898b/quoted_1.scala index fb2072ab4cb9..ca551862af67 100644 --- a/tests/pos/i3898b/quoted_1.scala +++ b/tests/pos/i3898b/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(x: Int, inline y: Int): String = ~impl('(x)) + transparent def ff(x: Int, transparent y: Int): String = ~impl('(x)) def impl(x: Expr[Int]): Expr[String] = '("") } diff --git a/tests/pos/i3898c/quoted_1.scala b/tests/pos/i3898c/quoted_1.scala index fb2072ab4cb9..ca551862af67 100644 --- a/tests/pos/i3898c/quoted_1.scala +++ b/tests/pos/i3898c/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff(x: Int, inline y: Int): String = ~impl('(x)) + transparent def ff(x: Int, transparent y: Int): String = ~impl('(x)) def impl(x: Expr[Int]): Expr[String] = '("") } diff --git a/tests/pos/i3912-1/i3912_1.scala b/tests/pos/i3912-1/i3912_1.scala index 80faaf976743..63c9565dacd7 100644 --- a/tests/pos/i3912-1/i3912_1.scala +++ b/tests/pos/i3912-1/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo(): Int = { ~impl() } + transparent def foo(): Int = { ~impl() } def impl(): Expr[Int] = '(1) } \ No newline at end of file diff --git a/tests/pos/i3912-2/i3912_1.scala b/tests/pos/i3912-2/i3912_1.scala index e3d1ba91df37..828d161e2609 100644 --- a/tests/pos/i3912-2/i3912_1.scala +++ b/tests/pos/i3912-2/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo2(): Unit = ~impl() + transparent def foo2(): Unit = ~impl() def impl(): Expr[Int] = '(1) } \ No newline at end of file diff --git a/tests/pos/i3912-3/i3912_1.scala b/tests/pos/i3912-3/i3912_1.scala index a544cfd26282..3d0a78eb491e 100644 --- a/tests/pos/i3912-3/i3912_1.scala +++ b/tests/pos/i3912-3/i3912_1.scala @@ -1,7 +1,7 @@ import scala.quoted._ object Macros { - inline def foo3(): Int = { + transparent def foo3(): Int = { { ~impl() } diff --git a/tests/pos/i4023/Macro_1.scala b/tests/pos/i4023/Macro_1.scala index b51c1de87b2e..a68cf3467f05 100644 --- a/tests/pos/i4023/Macro_1.scala +++ b/tests/pos/i4023/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T: Type](x: T): T = ~impl('(x)) + transparent def ff[T: Type](x: T): T = ~impl('(x)) def impl[T](x: Expr[T]): Expr[T] = x } diff --git a/tests/pos/i4023b/Macro_1.scala b/tests/pos/i4023b/Macro_1.scala index e440b4e1923a..e1d1559c649a 100644 --- a/tests/pos/i4023b/Macro_1.scala +++ b/tests/pos/i4023b/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T](implicit t: Type[T]): Int = ~impl[T] + transparent def ff[T](implicit t: Type[T]): Int = ~impl[T] def impl[T]: Expr[Int] = '(4) } diff --git a/tests/pos/i4023c/Macro_1.scala b/tests/pos/i4023c/Macro_1.scala index 7e5714514b8d..1f5521b974c7 100644 --- a/tests/pos/i4023c/Macro_1.scala +++ b/tests/pos/i4023c/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff[T](x: T): T = ~impl('(x), '[T]) + transparent def ff[T](x: T): T = ~impl('(x), '[T]) def impl[T](x: Expr[T], t: Type[T]): Expr[T] = '{ (~x): ~t } } diff --git a/tests/pos/i4322.scala b/tests/pos/i4322.scala index 70039b7a5e17..dcddc7da1e84 100644 --- a/tests/pos/i4322.scala +++ b/tests/pos/i4322.scala @@ -3,12 +3,12 @@ object Foo { private def x(n: String): Int = n.toInt - inline def foo: Int = x + x + x("22") + transparent def foo: Int = x + x + x("22") - inline def bar = { + transparent def bar = { x += 1 x += 1 } - inline def baz = { x += x("11") } + transparent def baz = { x += x("11") } } diff --git a/tests/pos/i4493-b.scala b/tests/pos/i4493-b.scala index fdc3b6cf7867..25b043cb56ad 100644 --- a/tests/pos/i4493-b.scala +++ b/tests/pos/i4493-b.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K](x: K): Unit = ~{ + transparent def succ[K](x: K): Unit = ~{ implicit val t: quoted.Type[K] = '[K] '(new Index[K]) } diff --git a/tests/pos/i4493-c.scala b/tests/pos/i4493-c.scala index 08327e5fa81a..0b81937d3714 100644 --- a/tests/pos/i4493-c.scala +++ b/tests/pos/i4493-c.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K]: Unit = ~{ + transparent def succ[K]: Unit = ~{ '(new Index[K]) } } diff --git a/tests/pos/i4493.scala b/tests/pos/i4493.scala index 4d125d31ec6e..3ea8f4f23d5f 100644 --- a/tests/pos/i4493.scala +++ b/tests/pos/i4493.scala @@ -1,6 +1,6 @@ class Index[K] object Index { - inline def succ[K]: Unit = ~{ + transparent def succ[K]: Unit = ~{ implicit val t: quoted.Type[K] = '[K] '(new Index[K]) } diff --git a/tests/pos/i4514.scala b/tests/pos/i4514.scala index 0da1e9af33f7..dcb17865cb3a 100644 --- a/tests/pos/i4514.scala +++ b/tests/pos/i4514.scala @@ -1,4 +1,4 @@ object Foo { - inline def foo[X](x: X): Unit = ~fooImpl('(x)) + transparent def foo[X](x: X): Unit = ~fooImpl('(x)) def fooImpl[X: quoted.Type](x: X): quoted.Expr[Unit] = '() } diff --git a/tests/pos/i4586.scala b/tests/pos/i4586.scala index 7181a1844f49..12ba8a8f7c94 100644 --- a/tests/pos/i4586.scala +++ b/tests/pos/i4586.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo1(f: => Int => Int): Int = f(7) + transparent def foo1(f: => Int => Int): Int = f(7) def bar1 = foo1(x => x + 1) } diff --git a/tests/pos/i4590.scala b/tests/pos/i4590.scala index 5ca26e197f2d..bf07b0e3a08c 100644 --- a/tests/pos/i4590.scala +++ b/tests/pos/i4590.scala @@ -1,7 +1,7 @@ package test class ArrayDeque { - inline def isResizeNecessary(len: Int) = len > ArrayDeque.StableSize + transparent def isResizeNecessary(len: Int) = len > ArrayDeque.StableSize } object ArrayDeque { @@ -9,7 +9,7 @@ object ArrayDeque { } class List { - inline def foo(x: List.Cons): Unit = { + transparent def foo(x: List.Cons): Unit = { x.next = this } } diff --git a/tests/pos/inline-access-levels/A_1.scala b/tests/pos/inline-access-levels/A_1.scala index 97f9392b17c1..6bbb3a44d173 100644 --- a/tests/pos/inline-access-levels/A_1.scala +++ b/tests/pos/inline-access-levels/A_1.scala @@ -4,7 +4,7 @@ object A { private var x: Int = 0 - inline def actOnX(f: Int => Int) = { + transparent def actOnX(f: Int => Int) = { x = f(x) } } diff --git a/tests/pos/inline-apply.scala b/tests/pos/inline-apply.scala index 6cdfe3b3ccb2..66a5c392512f 100644 --- a/tests/pos/inline-apply.scala +++ b/tests/pos/inline-apply.scala @@ -3,7 +3,7 @@ class Context object Test { def transform()(implicit ctx: Context) = { - inline def withLocalOwner[T](op: Context => T) = op(ctx) + transparent def withLocalOwner[T](op: Context => T) = op(ctx) withLocalOwner { implicit ctx => } diff --git a/tests/pos/inline-i1773.scala b/tests/pos/inline-i1773.scala index b24f8c34d2e1..adf601f3d69e 100644 --- a/tests/pos/inline-i1773.scala +++ b/tests/pos/inline-i1773.scala @@ -1,7 +1,7 @@ object Test { implicit class Foo(sc: StringContext) { object q { - inline def unapply(arg: Any): Option[(Any, Any)] = + transparent def unapply(arg: Any): Option[(Any, Any)] = Some((sc.parts(0), sc.parts(1))) } } diff --git a/tests/pos/inline-i2570.scala b/tests/pos/inline-i2570.scala index d68ae1dc8934..d5d4859d4829 100644 --- a/tests/pos/inline-i2570.scala +++ b/tests/pos/inline-i2570.scala @@ -1,4 +1,4 @@ object Test { - inline def sum2(ys: List[Int]): Int = (1 /: ys)(_ + _) + transparent def sum2(ys: List[Int]): Int = (1 /: ys)(_ + _) val h1: ((List[Int]) => Int) = sum2 } diff --git a/tests/pos/inline-named-typeargs.scala b/tests/pos/inline-named-typeargs.scala index 9d2c5b3f4af9..3d09f1af8c22 100644 --- a/tests/pos/inline-named-typeargs.scala +++ b/tests/pos/inline-named-typeargs.scala @@ -1,5 +1,5 @@ object t1 { - inline def construct[Elem, Coll[_]](xs: List[Elem]): Coll[Elem] = ??? + transparent def construct[Elem, Coll[_]](xs: List[Elem]): Coll[Elem] = ??? val xs3 = construct[Coll = List](List(1, 2, 3)) } diff --git a/tests/pos/inline-t2425.scala b/tests/pos/inline-t2425.scala index f5a1602a4a46..09f4e218ed48 100644 --- a/tests/pos/inline-t2425.scala +++ b/tests/pos/inline-t2425.scala @@ -1,5 +1,5 @@ object Test extends App { - inline def foo[T](bar: T) = { + transparent def foo[T](bar: T) = { bar match { case _ => () } diff --git a/tests/pos/inline-t9232a.scala b/tests/pos/inline-t9232a.scala index 41f34adbe026..2a9dcdb14cf6 100644 --- a/tests/pos/inline-t9232a.scala +++ b/tests/pos/inline-t9232a.scala @@ -1,7 +1,7 @@ final class Foo(val value: Int) object Foo { - inline def unapply(foo: Foo): Some[Int] = Some(foo.value) + transparent def unapply(foo: Foo): Some[Int] = Some(foo.value) } object Test { diff --git a/tests/pos/inline-t9232b.scala b/tests/pos/inline-t9232b.scala index 3cee6875097f..a833e8e62f7c 100644 --- a/tests/pos/inline-t9232b.scala +++ b/tests/pos/inline-t9232b.scala @@ -1,7 +1,7 @@ final class Foo(val value: Int) object Foo { - inline def unapplySeq(foo: Foo): Some[Seq[Int]] = Some(List(foo.value)) + transparent def unapplySeq(foo: Foo): Some[Seq[Int]] = Some(List(foo.value)) } sealed trait Tree diff --git a/tests/pos/inlineAccesses.scala b/tests/pos/inlineAccesses.scala index 0305e0f37168..53fbd5aa49b8 100644 --- a/tests/pos/inlineAccesses.scala +++ b/tests/pos/inlineAccesses.scala @@ -6,12 +6,12 @@ class C { private type T = C private var x = 0 - inline def f = { + transparent def f = { x += 1 x = x + 1 x } - inline def dup = new T + transparent def dup = new T } object Test { diff --git a/tests/pos/inliner2.scala b/tests/pos/inliner2.scala index 7b9099310a32..4d2acdc64d3e 100644 --- a/tests/pos/inliner2.scala +++ b/tests/pos/inliner2.scala @@ -3,7 +3,7 @@ // for inlining due to the bug. class A { private var debug = false - inline private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = + transparent private def ifelse[T](cond: => Boolean, ifPart: => T, elsePart: => T): T = if (cond) ifPart else elsePart final def bob1() = ifelse(debug, 1, 2) diff --git a/tests/pos/macro-with-array/Macro_1.scala b/tests/pos/macro-with-array/Macro_1.scala index a115aa3dc036..de42b4bb6726 100644 --- a/tests/pos/macro-with-array/Macro_1.scala +++ b/tests/pos/macro-with-array/Macro_1.scala @@ -1,29 +1,29 @@ object Macro { - inline def foo0(i: Int): Unit = ~{ '() } - inline def foo1(arr: Array[Boolean]): Unit = ~{ '() } - inline def foo2(arr: Array[Byte]): Unit = ~{ '() } - inline def foo3(arr: Array[Short]): Unit = ~{ '() } - inline def foo4(arr: Array[Int]): Unit = ~{ '() } - inline def foo5(arr: Array[Long]): Unit = ~{ '() } - inline def foo6(arr: Array[Float]): Unit = ~{ '() } - inline def foo7(arr: Array[Double]): Unit = ~{ '() } - inline def foo8(arr: Array[Char]): Unit = ~{ '() } - inline def foo9(arr: Array[Object]): Unit = ~{ '() } - inline def foo10(arr: Array[String]): Unit = ~{ '() } - inline def foo11[T](arr: Array[T]): Unit = ~{ '() } - inline def foo12(arr: Array[Array[Int]]): Unit = ~{ '() } - inline def foo13(arr: Array[Array[String]]): Unit = ~{ '() } - inline def foo14(arr: Array[Array[Array[Int]]]): Unit = ~{ '() } - inline def foo15(arr: Array[Any]): Unit = ~{ '() } - inline def foo16(arr: Array[AnyVal]): Unit = ~{ '() } - inline def foo17(arr: Array[AnyRef]): Unit = ~{ '() } - inline def foo18(arr: Array[Foo]): Unit = ~{ '() } - inline def foo19(arr: Array[Macro.type]): Unit = ~{ '() } - inline def foo20(arr: Array[Bar]): Unit = ~{ '() } - inline def foo21(arr: Array[Baz.type]): Unit = ~{ '() } - inline def foo22(arr: Array[Foo#A]): Unit = ~{ '() } + transparent def foo0(i: Int): Unit = ~{ '() } + transparent def foo1(arr: Array[Boolean]): Unit = ~{ '() } + transparent def foo2(arr: Array[Byte]): Unit = ~{ '() } + transparent def foo3(arr: Array[Short]): Unit = ~{ '() } + transparent def foo4(arr: Array[Int]): Unit = ~{ '() } + transparent def foo5(arr: Array[Long]): Unit = ~{ '() } + transparent def foo6(arr: Array[Float]): Unit = ~{ '() } + transparent def foo7(arr: Array[Double]): Unit = ~{ '() } + transparent def foo8(arr: Array[Char]): Unit = ~{ '() } + transparent def foo9(arr: Array[Object]): Unit = ~{ '() } + transparent def foo10(arr: Array[String]): Unit = ~{ '() } + transparent def foo11[T](arr: Array[T]): Unit = ~{ '() } + transparent def foo12(arr: Array[Array[Int]]): Unit = ~{ '() } + transparent def foo13(arr: Array[Array[String]]): Unit = ~{ '() } + transparent def foo14(arr: Array[Array[Array[Int]]]): Unit = ~{ '() } + transparent def foo15(arr: Array[Any]): Unit = ~{ '() } + transparent def foo16(arr: Array[AnyVal]): Unit = ~{ '() } + transparent def foo17(arr: Array[AnyRef]): Unit = ~{ '() } + transparent def foo18(arr: Array[Foo]): Unit = ~{ '() } + transparent def foo19(arr: Array[Macro.type]): Unit = ~{ '() } + transparent def foo20(arr: Array[Bar]): Unit = ~{ '() } + transparent def foo21(arr: Array[Baz.type]): Unit = ~{ '() } + transparent def foo22(arr: Array[Foo#A]): Unit = ~{ '() } class Bar object Baz diff --git a/tests/pos/macro-with-type/Macro_1.scala b/tests/pos/macro-with-type/Macro_1.scala index f6da5bf47794..f75c48270604 100644 --- a/tests/pos/macro-with-type/Macro_1.scala +++ b/tests/pos/macro-with-type/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macro { - inline def ff: Unit = ~impl('[Int]) + transparent def ff: Unit = ~impl('[Int]) def impl(t: Type[Int]): Expr[Unit] = '() } diff --git a/tests/pos/pos_valueclasses/t5853.scala b/tests/pos/pos_valueclasses/t5853.scala index b615cde71200..8edc734a4754 100644 --- a/tests/pos/pos_valueclasses/t5853.scala +++ b/tests/pos/pos_valueclasses/t5853.scala @@ -41,7 +41,7 @@ class Foo2 { object Arrow { implicit final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal { - inline def ->>[B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def ->>[B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) } def foo = 1 ->> 2 @@ -50,7 +50,7 @@ object Arrow { object SpecArrow { implicit final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal { - inline def ->> [@specialized(Int) B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def ->> [@specialized(Int) B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) } def foo = 1 ->> 2 diff --git a/tests/pos/power-macro/Macro_1.scala b/tests/pos/power-macro/Macro_1.scala index e279c7efd180..1881dcb467cc 100644 --- a/tests/pos/power-macro/Macro_1.scala +++ b/tests/pos/power-macro/Macro_1.scala @@ -3,7 +3,7 @@ import scala.quoted.Expr object PowerMacro { - inline def power(inline n: Long, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Long, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Long, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/pos/quote-0.scala b/tests/pos/quote-0.scala index ad413acba29a..cd90e9c24483 100644 --- a/tests/pos/quote-0.scala +++ b/tests/pos/quote-0.scala @@ -4,7 +4,7 @@ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = @@ -12,7 +12,7 @@ object Macros { def showExpr[T](expr: Expr[T]): Expr[String] = expr.toString.toExpr - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/pos/quote-assert/quoted_2.scala b/tests/pos/quote-assert/quoted_2.scala index 337892490574..6c14e51241f7 100644 --- a/tests/pos/quote-assert/quoted_2.scala +++ b/tests/pos/quote-assert/quoted_2.scala @@ -4,7 +4,7 @@ import Macros._ object Test { - inline def assert(expr: => Boolean): Unit = + transparent def assert(expr: => Boolean): Unit = ~ assertImpl('(expr)) diff --git a/tests/pos/quote-lift-inline-params-b.scala b/tests/pos/quote-lift-inline-params-b.scala index a5e4d797c083..66872399d155 100644 --- a/tests/pos/quote-lift-inline-params-b.scala +++ b/tests/pos/quote-lift-inline-params-b.scala @@ -1,6 +1,6 @@ import scala.quoted.Expr object Macro { - inline def foo(inline n: Int): Int = ~{ + transparent def foo(transparent n: Int): Int = ~{ import quoted.Liftable.{IntIsLiftable => _} '(n) } diff --git a/tests/pos/quote-lift-inline-params/Macro_1.scala b/tests/pos/quote-lift-inline-params/Macro_1.scala index a5e4d797c083..66872399d155 100644 --- a/tests/pos/quote-lift-inline-params/Macro_1.scala +++ b/tests/pos/quote-lift-inline-params/Macro_1.scala @@ -1,6 +1,6 @@ import scala.quoted.Expr object Macro { - inline def foo(inline n: Int): Int = ~{ + transparent def foo(transparent n: Int): Int = ~{ import quoted.Liftable.{IntIsLiftable => _} '(n) } diff --git a/tests/pos/quote-nested-object/Macro_1.scala b/tests/pos/quote-nested-object/Macro_1.scala index a24a6dd3f376..cda302351c2b 100644 --- a/tests/pos/quote-nested-object/Macro_1.scala +++ b/tests/pos/quote-nested-object/Macro_1.scala @@ -6,7 +6,7 @@ object Macro { object Implementation { - inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + transparent def plus(transparent n: Int, m: Int): Int = ~plus(n, '(m)) def plus(n: Int, m: Expr[Int]): Expr[Int] = if (n == 0) m @@ -14,7 +14,7 @@ object Macro { object Implementation2 { - inline def plus(inline n: Int, m: Int): Int = ~plus(n, '(m)) + transparent def plus(transparent n: Int, m: Int): Int = ~plus(n, '(m)) def plus(n: Int, m: Expr[Int]): Expr[Int] = if (n == 0) m diff --git a/tests/pos/rbtree.scala b/tests/pos/rbtree.scala index ffa44f743137..baba24a72eb7 100644 --- a/tests/pos/rbtree.scala +++ b/tests/pos/rbtree.scala @@ -457,11 +457,11 @@ object RedBlackTree { } object RedTree { - inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) + transparent def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new RedTree(key, value, left, right) def unapply[A, B](t: RedTree[A, B]) = Some((t.key, t.value, t.left, t.right)) } object BlackTree { - inline def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) + transparent def apply[A, B](key: A, value: B, left: Tree[A, B], right: Tree[A, B]) = new BlackTree(key, value, left, right) def unapply[A, B](t: BlackTree[A, B]) = Some((t.key, t.value, t.left, t.right)) } diff --git a/tests/pos/reference/inlines.scala b/tests/pos/reference/inlines.scala index c83ca72d3cbf..f87510d0c927 100644 --- a/tests/pos/reference/inlines.scala +++ b/tests/pos/reference/inlines.scala @@ -1,14 +1,14 @@ package inlines object Config { - inline val logging = false + transparent val logging = false } object Logger { private var indent = 0 - inline def log[T](msg: String)(op: => T): T = + transparent def log[T](msg: String)(op: => T): T = if (Config.logging) { println(s"${" " * indent}start $msg") indent += 1 @@ -22,7 +22,6 @@ object Logger { object Test { import Logger._ - def factorial(n: BigInt): BigInt = log(s"factorial($n)") { if (n == 0) 1 diff --git a/tests/pos/sealed-final.scala b/tests/pos/sealed-final.scala index e062888e2668..fc2c551c1518 100644 --- a/tests/pos/sealed-final.scala +++ b/tests/pos/sealed-final.scala @@ -1,5 +1,5 @@ sealed abstract class Foo { - inline def bar(x: Int) = x + 1 + transparent def bar(x: Int) = x + 1 } object Foo { def mkFoo(): Foo = new Baz2 diff --git a/tests/pos/simpleInline.decompiled b/tests/pos/simpleInline.decompiled index 492a0da6e777..d2a4ff00f1eb 100644 --- a/tests/pos/simpleInline.decompiled +++ b/tests/pos/simpleInline.decompiled @@ -1,6 +1,6 @@ /** Decompiled from out/posTestFromTasty/pos/simpleInline/Foo.class */ class Foo() { - inline def foo: scala.Int = 9 + transparent def foo: scala.Int = 9 def bar: scala.Int = { // inlined 9 } diff --git a/tests/pos/simpleInline.scala b/tests/pos/simpleInline.scala index 4cfab0209754..6710461f8a0d 100644 --- a/tests/pos/simpleInline.scala +++ b/tests/pos/simpleInline.scala @@ -1,4 +1,4 @@ class Foo { - inline def foo: Int = 9 + transparent def foo: Int = 9 def bar: Int = foo } diff --git a/tests/pos/t6157.scala b/tests/pos/t6157.scala index 89e503c78990..5a0a7f52fea5 100644 --- a/tests/pos/t6157.scala +++ b/tests/pos/t6157.scala @@ -12,7 +12,7 @@ import java.io.IOException object ErrorHandler { - inline def defaultIfIOException[T](default: => T)(closure: => T): T = { + transparent def defaultIfIOException[T](default: => T)(closure: => T): T = { try { closure } catch { diff --git a/tests/pos/t6562.scala b/tests/pos/t6562.scala index bf8ed8679c1e..2750c6f4b203 100644 --- a/tests/pos/t6562.scala +++ b/tests/pos/t6562.scala @@ -1,11 +1,11 @@ class Test { - inline def foo: Unit = { + transparent def foo: Unit = { def it = new {} (_: Any) => it } - inline private def bar: Unit = { + transparent private def bar: Unit = { def it = new {} (_: Any) => it } diff --git a/tests/pos/tasty/definitions.scala b/tests/pos/tasty/definitions.scala index d3d8a2c38e1e..95341ac8cfcf 100644 --- a/tests/pos/tasty/definitions.scala +++ b/tests/pos/tasty/definitions.scala @@ -236,7 +236,7 @@ object definitions { def isLazy: Boolean def isOverride: Boolean def isInline: Boolean - def isMacro: Boolean // inline method containing toplevel splices + def isMacro: Boolean // transparent method containing toplevel splices def isStatic: Boolean // mapped to static Java member def isObject: Boolean // an object or its class (used for a ValDef or a ClassDef extends Modifier respectively) def isTrait: Boolean // a trait (used for a ClassDef) diff --git a/tests/run-with-compiler/i3876-d.scala b/tests/run-with-compiler/i3876-d.scala index 3f11547d4522..153ac52e9d2b 100644 --- a/tests/run-with-compiler/i3876-d.scala +++ b/tests/run-with-compiler/i3876-d.scala @@ -13,5 +13,5 @@ object Test { println(f4(x).show) } - inline def inlineLambda: Int => Int = x => x + x + transparent def inlineLambda: Int => Int = x => x + x } \ No newline at end of file diff --git a/tests/run/dead-code-elimination.scala b/tests/run/dead-code-elimination.scala index f5331f47973d..61d7eecdf717 100644 --- a/tests/run/dead-code-elimination.scala +++ b/tests/run/dead-code-elimination.scala @@ -18,7 +18,7 @@ final class A { def f1 = true def f2 = true - inline def f3 = f1 || f2 + transparent def f3 = f1 || f2 class B { def f() = 1 to 10 foreach (_ => f3) } diff --git a/tests/run/genericValueClass.scala b/tests/run/genericValueClass.scala index 689e0e251e2f..e2fdb79cc69b 100644 --- a/tests/run/genericValueClass.scala +++ b/tests/run/genericValueClass.scala @@ -3,12 +3,12 @@ import scala.language.implicitConversions object Test extends dotty.runtime.LegacyApp { class ArrowAssocClass[A](val __leftOfArrow: A) extends AnyVal { - inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) + transparent def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y) def →[B](y: B): Tuple2[A, B] = ->(y) } { - inline implicit def ArrowAssoc[A](x: A): ArrowAssocClass[A] = new ArrowAssocClass(x) + transparent implicit def ArrowAssoc[A](x: A): ArrowAssocClass[A] = new ArrowAssocClass(x) val x = 1 -> "abc" println(x) } diff --git a/tests/run/i1569.scala b/tests/run/i1569.scala index 2c5dd4e5a0eb..ef64021e016e 100644 --- a/tests/run/i1569.scala +++ b/tests/run/i1569.scala @@ -1,5 +1,5 @@ object Test { - inline def foo(inline n: => Int) = n + n + transparent def foo(transparent n: => Int) = n + n def main(args: Array[String]): Unit = foo({ println("foo"); 42 }) } diff --git a/tests/run/i1990b.scala b/tests/run/i1990b.scala index 2460208dbf9f..63d6a5fd93c0 100644 --- a/tests/run/i1990b.scala +++ b/tests/run/i1990b.scala @@ -1,6 +1,6 @@ trait A { self => class Foo { - inline def inlineMeth: Unit = { + transparent def inlineMeth: Unit = { println(self) } } diff --git a/tests/run/i2077.scala b/tests/run/i2077.scala index 42b629b90a07..639dc4921472 100644 --- a/tests/run/i2077.scala +++ b/tests/run/i2077.scala @@ -1,5 +1,5 @@ object Test { - inline val x = true + transparent val x = true val y = if (x) 1 else 2 // reduced to val y = 1 def main(args: Array[String]): Unit = diff --git a/tests/run/i2360.scala b/tests/run/i2360.scala index d950d1c2d3dd..584cab6f4ce6 100644 --- a/tests/run/i2360.scala +++ b/tests/run/i2360.scala @@ -7,7 +7,7 @@ object Test { } object Foo extends Bar { - inline def foo: Int = bar + transparent def foo: Int = bar } class Bar { diff --git a/tests/run/i2895.scala b/tests/run/i2895.scala index 2bc2037ebaa7..e68b918c03b5 100644 --- a/tests/run/i2895.scala +++ b/tests/run/i2895.scala @@ -1,5 +1,5 @@ object Foo extends (Int => Int) { - inline def apply(x: Int): Int = impl(x) + transparent def apply(x: Int): Int = impl(x) def impl(x: Int): Int = x + 1 } diff --git a/tests/run/i2895a.scala b/tests/run/i2895a.scala index 72f6dd3d61db..a37ae2ce0943 100644 --- a/tests/run/i2895a.scala +++ b/tests/run/i2895a.scala @@ -3,7 +3,7 @@ trait Foo[A <: Foo[A]] { def next: A - inline final def once: A = next + transparent final def once: A = next def once1: A = once diff --git a/tests/run/i4431/quoted_1.scala b/tests/run/i4431/quoted_1.scala index 8f0be7b23ef7..ba4a4e743077 100644 --- a/tests/run/i4431/quoted_1.scala +++ b/tests/run/i4431/quoted_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macros { - inline def h(f: => Int => String): String = ~ '(f(42)) + transparent def h(f: => Int => String): String = ~ '(f(42)) } diff --git a/tests/run/i4455/Macro_1.scala b/tests/run/i4455/Macro_1.scala index 9ac3070ed986..840f3677a5b3 100644 --- a/tests/run/i4455/Macro_1.scala +++ b/tests/run/i4455/Macro_1.scala @@ -1,8 +1,8 @@ import scala.quoted._ object Macros { - inline def foo(inline i: Int): Int = ~bar('(i)) + transparent def foo(transparent i: Int): Int = ~bar('(i)) - inline def foo2(inline i: Int): Int = ~bar('(i + 1)) + transparent def foo2(transparent i: Int): Int = ~bar('(i + 1)) def bar(x: Expr[Int]): Expr[Int] = x } diff --git a/tests/run/i4492/quoted_1.scala b/tests/run/i4492/quoted_1.scala index 67e68d59c1e7..d41d07a29f4a 100644 --- a/tests/run/i4492/quoted_1.scala +++ b/tests/run/i4492/quoted_1.scala @@ -2,5 +2,5 @@ trait Index object Index { - inline def succ(prev: Index): Unit = ~{ '(println("Ok")) } + transparent def succ(prev: Index): Unit = ~{ '(println("Ok")) } } diff --git a/tests/run/i4496b.scala b/tests/run/i4496b.scala index 2e777f64e8ac..82d8967e5beb 100644 --- a/tests/run/i4496b.scala +++ b/tests/run/i4496b.scala @@ -24,7 +24,7 @@ object Test { // These accesses are also clearly well-typed def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) @@ -58,7 +58,7 @@ object Test { def upcast2(v: Foo2): T = v def upcast3(v: Foo3): T = v def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) @@ -88,7 +88,7 @@ object Test { type T = {val a: Int; def a_=(x: Int): Unit} def upcast3(v: Foo3): T = v def consume(v: T) = v.a - inline def consumeInl(v: T) = v.a + transparent def consumeInl(v: T) = v.a def verify(v: T) = { assert(consume(v) == 10) assert(consumeInl(v) == 10) diff --git a/tests/run/i4754.scala b/tests/run/i4754.scala index 0907880e6499..f2763526fa9a 100644 --- a/tests/run/i4754.scala +++ b/tests/run/i4754.scala @@ -6,7 +6,7 @@ object Foo { class Foo { import Foo._ - inline def foo = x + Foo.x + y + Foo.y + z + Foo.z + transparent def foo = x + Foo.x + y + Foo.y + z + Foo.z } object Test { diff --git a/tests/run/inline-implicits.check b/tests/run/inline-implicits.check deleted file mode 100644 index 17a1d370d730..000000000000 --- a/tests/run/inline-implicits.check +++ /dev/null @@ -1 +0,0 @@ -(X(),Y()) diff --git a/tests/run/inline-implicits.scala b/tests/run/inline-implicits.scala deleted file mode 100644 index 6d46a61d28e4..000000000000 --- a/tests/run/inline-implicits.scala +++ /dev/null @@ -1,26 +0,0 @@ -case class X() -case class Y() - -object impl { - implicit val y: Y = new Y() -} - -object inlines { - import impl._ - - class C { - implicit val x: X = new X() - - inline - def f(): (X, Y) = - (implicitly[X], implicitly[Y]) - } -} - -object Test { - def main(args: Array[String]) = { - val c = new inlines.C - val xy = c.f() - println(xy) - } -} diff --git a/tests/run/inline-object.check b/tests/run/inline-object.check deleted file mode 100644 index 5716ca5987cb..000000000000 --- a/tests/run/inline-object.check +++ /dev/null @@ -1 +0,0 @@ -bar diff --git a/tests/run/inline-object.scala b/tests/run/inline-object.scala deleted file mode 100644 index 88a5777dd252..000000000000 --- a/tests/run/inline-object.scala +++ /dev/null @@ -1,14 +0,0 @@ - -object Test { - def main(args: Array[String]): Unit = { - Foo.foo - } -} - -object Foo extends Bar { - inline def foo: Unit = bar -} - -class Bar { - def bar: Unit = println("bar") -} diff --git a/tests/run/inline.check b/tests/run/inline.check deleted file mode 100644 index 779315ff2880..000000000000 --- a/tests/run/inline.check +++ /dev/null @@ -1,11 +0,0 @@ -100 -10000 - - Inner -Outer.f -Outer.f Inner - Inner -Outer.f -Outer.f Inner -(hi,1) -(true,) diff --git a/tests/run/inline/Test_2.scala b/tests/run/inline/Test_2.scala deleted file mode 100644 index 431951300eb6..000000000000 --- a/tests/run/inline/Test_2.scala +++ /dev/null @@ -1,23 +0,0 @@ -object Test { - - import p.inlines._ - - def main(args: Array[String]): Unit = { - println(f(10)) - println(f(f(10))) - - track("hello") { println("") } - - val o = new Outer - val i = new o.Inner - val p = new TestPassing - println(i.m) - println(i.g) - println(i.h) - println(o.inner.m) - println(o.inner.g) - println(o.inner.h) - println(p.foo("hi")) - println(p.bar(true)) - } -} \ No newline at end of file diff --git a/tests/run/inline/inlines_1.scala b/tests/run/inline/inlines_1.scala deleted file mode 100644 index 825d84913013..000000000000 --- a/tests/run/inline/inlines_1.scala +++ /dev/null @@ -1,59 +0,0 @@ -package p -import collection.mutable - -object inlines { - - final val monitored = false - - inline def f(x: Int): Int = x * x - - val hits = new mutable.HashMap[String, Int] { - override def default(key: String): Int = 0 - } - - def record(fn: String, n: Int = 1) = { - if (monitored) { - val name = if (fn.startsWith("member-")) "member" else fn - hits(name) += n - } - } - - @volatile private var stack: List[String] = Nil - - inline def track[T](fn: String)(op: => T) = - if (monitored) { - stack = fn :: stack - record(fn) - try op - finally stack = stack.tail - } else op - - class Outer { - def f = "Outer.f" - class Inner { - val msg = " Inner" - inline def m = msg - inline def g = f - inline def h = f ++ m - } - val inner = new Inner - } - - class C[T](private[inlines] val x: T) { - private[inlines] def next[U](y: U): (T, U) = (xx, y) - private[inlines] var xx: T = _ - } - - class TestPassing { - inline def foo[A](x: A): (A, Int) = { - val c = new C[A](x) - c.xx = c.x - c.next(1) - } - inline def bar[A](x: A): (A, String) = { - val c = new C[A](x) - c.xx = c.x - c.next("") - } - } -} diff --git a/tests/run/inlineAccess/C_1.scala b/tests/run/inlineAccess/C_1.scala deleted file mode 100644 index 349f5b1508dd..000000000000 --- a/tests/run/inlineAccess/C_1.scala +++ /dev/null @@ -1,7 +0,0 @@ -package p { -class C { - protected def f(): Unit = () - - inline def inl() = f() // error (when inlined): not accessible -} -} diff --git a/tests/run/inlineAccess/Test_2.scala b/tests/run/inlineAccess/Test_2.scala deleted file mode 100644 index 98ea7693abb7..000000000000 --- a/tests/run/inlineAccess/Test_2.scala +++ /dev/null @@ -1,7 +0,0 @@ - -object Test { - def main(args: Array[String]) = { - val c = new p.C() - c.inl() - } -} diff --git a/tests/run/inlineArrowAssoc.scala b/tests/run/inlineArrowAssoc.scala deleted file mode 100644 index 0a3b5c6d7e37..000000000000 --- a/tests/run/inlineArrowAssoc.scala +++ /dev/null @@ -1,24 +0,0 @@ -import scala.collection.immutable._ - -import scala.collection.mutable.{ Builder, ListBuffer } - -object Test { - - private val defaultOrdering = Map[Numeric[_], Ordering[_]]( - Numeric.BigIntIsIntegral -> Ordering.BigInt, - Numeric.IntIsIntegral -> Ordering.Int - ) - - final implicit class ArrowAssoc[A](private val self: A) extends AnyVal { - inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y) - def →[B](y: B): Tuple2[A, B] = ->(y) - } - - def main(args: Array[String]): Unit = { - assert((1 -> 2) == (1, 2)) - assert((1 → 2) == (1, 2)) - } - - -} - diff --git a/tests/run/inlineByName.scala b/tests/run/inlineByName.scala deleted file mode 100644 index 94f276dcce3e..000000000000 --- a/tests/run/inlineByName.scala +++ /dev/null @@ -1,37 +0,0 @@ -object Test { - - class Range(from: Int, end: Int) { - inline def foreach(op: => Int => Unit): Unit = { - var i = from - while (i < end) { - op(i) - i += 1 - } - } - } - inline def twice(op: => Int => Unit): Unit = { - op(1) - op(2) - } - inline def thrice(op: => Unit): Unit = { - op - op - op - } - - def main(args: Array[String]) = { - var j = 0 - new Range(1, 10).foreach(j += _) - assert(j == 45, j) - twice { x => j = j - x } - thrice { j = j + 1 } - val f = new Range(1, 10).foreach - f(j -= _) - assert(j == 0, j) - new Range(1, 10).foreach { i1 => - new Range(2, 11).foreach { i2 => - j += i1 * i2 - } - } - } -} diff --git a/tests/run/inlineForeach.check b/tests/run/inlineForeach.check deleted file mode 100644 index 3fced2fad78d..000000000000 --- a/tests/run/inlineForeach.check +++ /dev/null @@ -1,137 +0,0 @@ -1 -2 -3 -4 -5 -6 -7 -8 -9 -1 -2 -3 -4 -5 -6 -7 -8 -9 -1 -2 -3 -4 -5 -6 -7 -8 -9 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -2 -2 -2 -2 -2 -2 -2 -2 -2 -2 -3 -3 -3 -3 -3 -3 -3 -3 -3 -3 -4 -4 -4 -4 -4 -4 -4 -4 -4 -4 -5 -5 -5 -5 -5 -5 -5 -5 -5 -5 -6 -6 -6 -6 -6 -6 -6 -6 -6 -6 -7 -7 -7 -7 -7 -7 -7 -7 -7 -7 -8 -8 -8 -8 -8 -8 -8 -8 -8 -8 -9 -9 -9 -9 -9 -9 -9 -9 -9 -9 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 diff --git a/tests/run/inlineForeach.scala b/tests/run/inlineForeach.scala deleted file mode 100644 index 7f22f1cc3753..000000000000 --- a/tests/run/inlineForeach.scala +++ /dev/null @@ -1,67 +0,0 @@ -object Test { - - class Range(from: Int, end: Int) { - - inline - def foreach(op: => Int => Unit): Unit = { - var i = from - while (i < end) { - op(i) - i += 1 - } - } - - def filter(p: Int => Boolean): List[Int] = ??? - } - - implicit class intWrapper(private val start: Int) extends AnyVal { - def until(end: Int) = new Range(start, end) - def to(limit: Int) = new Range(start, limit + 1) - } - - def matmul(xs: Array[Array[Double]], ys: Array[Array[Double]]): Array[Array[Double]] = { - def nrows = xs.length - def ncols = ys(0).length - def n = ys.length - assert(xs(0).length == n) - val zs = Array.ofDim[Double](nrows, ncols) - for (i <- intWrapper(0) until nrows) - for (j <- 0 until ncols) { - var x = 0.0 - for (k <- 0 until n) - x += xs(i)(k) * ys(k)(j) - zs(i)(j) = x - } - zs - } - - implicit class intArrayOps(arr: Array[Int]) { - inline def foreach(op: => Int => Unit): Unit = { - var i = 0 - while (i < arr.length) { - op(arr(i)) - i += 1 - } - } - } - - def sum(ints: Array[Int]): Int = { - var t = 0 - for (n <- ints) t += n - t - } - - def main(args: Array[String]) = { - 1.until(10).foreach(i => println(i)) - 1.until(10).foreach(println(_)) - 1.until(10).foreach(println) - for (i <- 1 to 10) println(i) - - for (k1 <- 1 to 10) - for (k2 <- 1 to 10) - println(s"$k1") - - val xs = Array(1, 2, 3, 4) - assert(sum(xs) == 10, sum(xs)) - } -} diff --git a/tests/run/inlinePower.check b/tests/run/inlinePower.check deleted file mode 100644 index 25e11563452f..000000000000 --- a/tests/run/inlinePower.check +++ /dev/null @@ -1,2 +0,0 @@ -1024.0 -2048.0 diff --git a/tests/run/inlinePower/Test_2.scala b/tests/run/inlinePower/Test_2.scala deleted file mode 100644 index 8e16587b5653..000000000000 --- a/tests/run/inlinePower/Test_2.scala +++ /dev/null @@ -1,9 +0,0 @@ -import p.pow.power -object Test { - - def main(args: Array[String]): Unit = { - println(power(2.0, 10)) - def x = 2.0 - println(power(x, 11)) - } -} diff --git a/tests/run/inlinePower/power_1.scala b/tests/run/inlinePower/power_1.scala deleted file mode 100644 index 4e96d7caa264..000000000000 --- a/tests/run/inlinePower/power_1.scala +++ /dev/null @@ -1,12 +0,0 @@ -package p - -object pow { - - inline def power(x: Double, n: Int): Double = - if (n == 0) 1.0 - else if (n == 1) x - else { - val y = power(x, n / 2) - if (n % 2 == 0) y * y else y * y * x - } -} diff --git a/tests/run/inlinePrivates.scala b/tests/run/inlinePrivates.scala deleted file mode 100644 index ce438ae8d8a0..000000000000 --- a/tests/run/inlinePrivates.scala +++ /dev/null @@ -1,36 +0,0 @@ -object Test { - - class C[T](private val x: T) { - - private def foo[Z](z: Z): T = x - - private var y: T = _ - - inline def get1 = x - inline def get2[U](c: C[U]) = c.x - - inline def foo1(x: Int) = foo(x) - inline def foo2[U](c: C[U]) = c.foo(x) - - inline def set1(z: T) = { y = z; y } - inline def set2[U](c: C[U]) = { c.y = c.x; c.y } - } - - object CC { - private val x = 3 - inline def get1 = x - } - - def main(args: Array[String]) = { - val cc = new C(2) - assert(cc.get1 == 2) - assert(cc.get2(cc) == 2) - assert(cc.foo1(1) == 2) - assert(cc.foo2(cc) == 2) - assert(cc.set1(3) == 3) - assert(cc.set2(cc) == 2) - - assert(CC.get1 == 3) - } - -} diff --git a/tests/run/inlineProtected.scala b/tests/run/inlineProtected.scala deleted file mode 100644 index 1a725ec27346..000000000000 --- a/tests/run/inlineProtected.scala +++ /dev/null @@ -1,22 +0,0 @@ -package P { - class C { - protected def f(): Int = 22 - } -} - -package Q { - class D extends P.C { - class Inner { - inline def g() = f() - } - } -} - -object Test extends App { - import Q._ - - val d = new D - val i = new d.Inner - val x = i.g() - assert(x == 22) -} diff --git a/tests/run/inlinedAssign.scala b/tests/run/inlinedAssign.scala deleted file mode 100644 index 37e66833a0dc..000000000000 --- a/tests/run/inlinedAssign.scala +++ /dev/null @@ -1,24 +0,0 @@ -object Test { - - inline def swap[T](x: T, x_= : => T => Unit, y: T, y_= : => T => Unit) = { - x_=(y) - y_=(x) - } - - inline def f(x: Int => Unit) = x - - def main(args: Array[String]) = { - var x = 1 - var y = 2 - inline def setX(z: Int) = x = z - inline def setY(z: Int) = y = z - swap(x, setX, y, setY) - assert(x == 2 && y == 1) - - swap(x, x = _, y, y = _) - assert(x == 1 && y == 2) - - - val z = f(setX) // tests case where inline arg is not applied - } -} diff --git a/tests/run/lst-transparent/Lst.scala b/tests/run/lst-transparent/Lst.scala deleted file mode 100644 index 9e0f52076c0d..000000000000 --- a/tests/run/lst-transparent/Lst.scala +++ /dev/null @@ -1,496 +0,0 @@ -package dotty.tools.dotc -package util - -import printing.{Printer, Texts} -import Texts.Text -import collection.mutable.{ListBuffer, StringBuilder} - - -/** A lightweight class for lists, optimized for short and medium lengths. - * A list is represented at runtime as - * - * If it is empty: the value `Lst.Empty` - * If it contains one element: the element itself - * If it contains more elements: an Array[Any] containing the elements - */ -class Lst[+T](val elems: Any) extends AnyVal { - import Lst._ - - def length: Int = elems match { - case null => 0 - case elems: Arr => elems.length - case elem => 1 - } - - def isEmpty = elems == null - def nonEmpty = elems != null - - transparent def foreach(op: => T => Unit): Unit = { - def sharedOp(x: T) = op(x) - elems match { - case null => - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length) { sharedOp(elem(i)); i += 1 } - case elem: T @ unchecked => sharedOp(elem) - } - } - - /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. - * Should be used only of `op` is small - */ - transparent def foreachInlined(op: => T => Unit): Unit = elems match { - case null => - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length) { op(elem(i)); i += 1 } - case elem: T @unchecked => op(elem) - } - - def iterator(): Iterator[T] = elems match { - case null => Iterator.empty - case elems: Arr => elems.iterator.asInstanceOf[Iterator[T]] - case elem: T @unchecked => Iterator.single(elem) - } - - def copyToArray[U >: T](target: Array[U], from: Int) = elems match { - case null => - case elems: Arr => System.arraycopy(elems, 0, target, from, elems.length) - case elem: T @ unchecked => target(from) = elem - } - - /** `f` is pulled out, not duplicated */ - transparent def map[U](f: => T => U): Lst[U] = { - def op(x: T) = f(x) - elems match { - case null => Empty - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - val newElems = new Arr(elems.length) - var i = 0 - while (i < elems.length) { newElems(i) = op(elem(i)); i += 1 } - new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](op(elem)) - } - } - - def mapConserve[U](f: T => U): Lst[U] = elems match { - case null => Empty - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var newElems: Arr = null - var i = 0 - while (i < elems.length) { - val x = elem(i) - val y = f(x) - if (newElems != null) newElems(i) = y - else if (!eq(x, y)) { - newElems = new Arr(elems.length) - System.arraycopy(elems, 0, newElems, 0, i) - newElems(i) = y - } - i += 1 - } - if (newElems == null) this.asInstanceOf[Lst[U]] else new Lst[U](newElems) - case elem: T @ unchecked => new Lst[U](f(elem)) - } - - def flatMap[U](f: T => Lst[U]): Lst[U] = elems match { - case null => Empty - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - val newElemss: Arr = new Arr(elems.length) - var i = 0 - var len = 0 - while (i < elems.length) { - val ys = f(elem(i)) - len += ys.length - newElemss(i) = ys.elems - i += 1 - } - if (len == 0) Empty - else if (len == 1) { - i = 0 - while (newElemss(i) == null) i += 1 - new Lst[U](newElemss(i)) - } - else { - val newElems = new Arr(len) - i = 0 - var j = 0 - while (i < newElemss.length) { - val ys = new Lst[U](newElemss(i)) - ys.copyToArray(newElems, j) - j += ys.length - i += 1 - } - new Lst[U](newElems) - } - case elem: T @ unchecked => new Lst[U](f(elem).elems) - } - - def filter(p: T => Boolean): Lst[T] = elems match { - case null => Empty - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - val scratch = new Arr(elems.length) - var i = 0 - var len = 0 - while (i < elems.length) { - val x = elem(i) - if (p(x)) { scratch(len) = x; len += 1 } - i += 1 - } - if (len == elems.length) this - else _fromArray(scratch, 0, len) - case elem: T @unchecked => - if (p(elem)) this else Empty - } - def filterNot(p: T => Boolean): Lst[T] = filter(!p(_)) - - transparent def exists(p: => T => Boolean): Boolean = { - def op(x: T) = p(x) - elems match { - case null => false - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length && !op(elem(i))) i += 1 - i < elems.length - case elem: T @unchecked => - op(elem) - } - } - - transparent def forall(p: => T => Boolean): Boolean = { - def op(x: T) = p(x) - elems match { - case null => true - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length && op(elem(i))) i += 1 - i == elems.length - case elem: T @unchecked => - op(elem) - } - } - - transparent def contains[U >: T](x: U): Boolean = elems match { - case null => false - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length && elem(i) != x) i += 1 - i < elems.length - case elem: T @unchecked => - elem == x - } - - transparent def foldLeft[U](z: U)(f: => (U, T) => U) = { - def op(x: U, y: T) = f(x, y) - elems match { - case null => z - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - var acc = z - while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } - acc - case elem: T @unchecked => - op(z, elem) - } - } - - transparent def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) - - def reduceLeft[U >: T](op: (U, U) => U) = elems match { - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 1 - var acc: U = elem(0) - while (i < elems.length) { acc = op(acc, elem(i)); i += 1 } - acc - case elem: T @unchecked => - elem - } - - def reverse: Lst[T] = elems match { - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - val newElems = new Arr(elems.length) - var i = 0 - while (i < elems.length) { - newElems(elems.length - 1 - i) = elem(i) - i += 1 - } - new Lst[T](newElems) - case _ => this - } - - def apply(n: Int): T = elems match { - case null => throw new IndexOutOfBoundsException(n.toString) - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - elem(n) - case elem: T @unchecked => - if (n == 0) elem else throw new IndexOutOfBoundsException(n.toString) - } - - def head: T = apply(0) - def last: T = apply(length - 1) - - def headOr[U >: T](alt: => U): U = if (isEmpty) alt else head - - def slice(start: Int, end: Int): Lst[T] = - if (start < 0) slice(0, end) - else elems match { - case null => this - case elems: Arr => _fromArray(elems, start, end `min` elems.length) - case elem: T @ unchecked => if (end == 0) Empty else this - } - - def drop(n: Int): Lst[T] = slice(n, length) - def tail = drop(1) - def take(n: Int): Lst[T] = slice(0, n) - - def ++ [U >: T](that: Lst[U]): Lst[U] = - if (elems == null) that - else if (that.elems == null) this - else { - val len1 = this.length - val len2 = that.length - val newElems = new Arr(len1 + len2) - this.copyToArray(newElems, 0) - that.copyToArray(newElems, len1) - new Lst[U](newElems) - } - - def zipWith[U, V](that: Lst[U])(op: (T, U) => V): Lst[V] = - this.elems match { - case null => Empty - case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] - that.elems match { - case null => Empty - case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] - val len = elems1.length min elems2.length - if (len == 0) Empty - else if (len == 1) new Lst[V](op(elem1(0), elem2(0))) - else { - var newElems: Arr = null - var i = 0 - while (i < len) { - val x = elem1(i) - val y = op(x, elem2(i)) - if (newElems != null) newElems(i) = y - else if (!eq(x, y)) { - newElems = new Arr(len) - System.arraycopy(elems, 0, newElems, 0, i) - newElems(i) = y - } - i += 1 - } - new Lst[V](newElems) - } - case elem2: U @unchecked => - new Lst[V](op(elem1(0), elem2)) - } - case elem1: T @unchecked => - that.elems match { - case null => Empty - case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] - new Lst[V](op(elem1, elem2(0))) - case elem2: U @unchecked => new Lst[V](op(elem1, elem2)) - } - } - - def zip[U](that: Lst[U]): Lst[(T, U)] = zipWith(that)((_, _)) - - def zipWithIndex: Lst[(T, Int)] = elems match { - case null => Empty - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - val newElems = new Arr(elems.length) - var i = 0 - while (i < elems.length) { newElems(i) = (elem(i), i); i += 1 } - new Lst[(T, Int)](newElems) - case elem: T @unchecked => - new Lst[(T, Int)]((elem, 0)) - } - - def corresponds[U](that: Lst[U])(p: (T, U) => Boolean): Boolean = - (this `eqLst` that) || { - this.elems match { - case null => - that.elems == null - case elems1: Arr => def elem1(i: Int) = elems1(i).asInstanceOf[T] - that.elems match { - case elems2: Arr => def elem2(i: Int) = elems2(i).asInstanceOf[U] - val len = elems1.length - len == elems2.length && { - var i = 0 - while (i < len && p(elem1(i), elem2(i))) i += 1 - i == len - } - case _ => false - } - case elem1: T @unchecked => - that.elems match { - case null => false - case elems2: Arr => false - case elem2: U @unchecked => p(elem1, elem2) - } - } - } - - def === [U](that: Lst[U]) = - (this `eqLst` that) || { - elems match { - case elems1: Arr => - that.elems match { - case elems2: Arr => - val len = elems1.length - len == elems2.length && { - var i = 0 - while (i < len && elems1(i).equals(elems2(i))) i += 1 - i == len - } - case _ => false - } - case elem => elem == that.elems - } - } - - def eqLst[U](that: Lst[U]) = eq(elems, that.elems) - - def eqElements[U](that: Lst[U]): Boolean = corresponds(that)(eqFn) - - def mkString: String = mkString(", ") - - def mkString(sep: String): String = mkString("", sep, "") - def mkString(left: String, sep: String, right: String): String = { - val b = new StringBuilder(left) - var first = true - foreachInlined { elem => - if (first) first = false else b ++= sep - b ++= elem.toString - } - b ++= right - b.toString - } - - override def toString = mkString("Lst(", ", ", ")") -} - -object Lst { - - private type Arr = Array[Any] - - private def eq(x: Any, y: Any) = x.asInstanceOf[AnyRef] `eq` y.asInstanceOf[AnyRef] - private val eqFn = (x: Any, y: Any) => eq(x, y) - - val Empty = new Lst[Nothing](null) - - def apply[T](): Lst[T] = Empty - - def apply[T](x0: T): Lst[T] = new Lst[T](x0) - - def apply[T](x0: T, x1: T): Lst[T] = { - val elems = new Arr(2) - elems(0) = x0 - elems(1) = x1 - new Lst[T](elems) - } - - def apply[T](x0: T, x1: T, x2: T): Lst[T] = { - val elems = new Arr(3) - elems(0) = x0 - elems(1) = x1 - elems(2) = x2 - new Lst[T](elems) - } - - def apply[T](x0: T, x1: T, x2: T, x3: T): Lst[T] = { - val elems = new Arr(4) - elems(0) = x0 - elems(1) = x1 - elems(2) = x2 - elems(3) = x3 - new Lst[T](elems) - } - - def apply[T](x0: T, x1: T, x2: T, x3: T, x4: T, xs: T*): Lst[T] = { - val elems = new Arr(5 + xs.length) - elems(0) = x0 - elems(1) = x1 - elems(2) = x2 - elems(3) = x3 - elems(4) = x4 - xs.copyToArray(elems, 5) - new Lst[T](elems) - } - - def fill[T](n: Int)(elem: => T) = { - val elems = new Arr(n) - var i = 0 - while (i < n) { elems(i) = elem; i += 1} - new Lst[T](elems) - } - - class Buffer[T] { - private var len = 0 - private var elem: T = _ - private var elems: Arr = _ - - def size = len - - /** pre: len > 0, n > 1 */ - private def ensureSize(n: Int) = - if (len == 1) { - elems = new Arr(n `max` 16) - elems(0) = elem - } - else if (len < n) { - val newLen = n `max` len << 2 - val newElems = new Arr(newLen) - System.arraycopy(elems, 0, newElems, 0, len) - elems = newElems - } - - def += (x: T): this.type = { - if (len == 0) elem = x - else { - ensureSize(len + 1) - elems(len) = x - } - len += 1 - this - } - - def ++= (xs: Lst[T]): this.type = { - xs.elems match { - case null => this - case elems2: Arr => - if (len == 0) elems = elems2 - else { - ensureSize(len + elems2.length) - System.arraycopy(elems2, 0, elems, len, elems2.length) - } - len += elems2.length - case elem: T @unchecked => this += elem - } - this - } - - def toLst: Lst[T] = - if (len == 0) Empty - else if (len == 1) new Lst[T](elem) - else _fromArray(elems, 0, len) - - def clear() = - len = 0 - } - - private def _fromArray[T](elems: Arr, start: Int, end: Int): Lst[T] = { - val len = end - start - if (len <= 0) Empty - else if (len == 1) new Lst[T](elems(start)) - else if (start == 0 && end == elems.length) new Lst[T](elems) - else { - val newElems = new Arr(len) - System.arraycopy(elems, start, newElems, 0, len) - new Lst[T](newElems) - } - } - - def fromArray[T](elems: Array[T], start: Int, end: Int): Lst[T] = - _fromArray(elems.asInstanceOf[Arr], start, end) -} diff --git a/tests/run/lst-transparent/LstTest.scala b/tests/run/lst-transparent/LstTest.scala deleted file mode 100644 index 87f2541fb62a..000000000000 --- a/tests/run/lst-transparent/LstTest.scala +++ /dev/null @@ -1,329 +0,0 @@ -object Test extends App { - import dotty.tools.dotc.util.Lst - - val xs0: Lst[String] = Lst.Empty - val xs1 = Lst("a") - val xs2 = Lst("a", "b") - val xs3 = Lst("a", "b", "c") - val xs4 = Lst("a", "b", "c", "d") - val xs5 = Lst("a", "b", "c", "d", "e") - val xs10 = xs5 ++ xs5 - - val is0: Lst[Int] = Lst.Empty - val is1 = Lst(1) - val is2 = Lst(1, 2) - val is3 = Lst(1, 2, 3) - val is4 = Lst(1, 2, 3, 4) - val is5 = Lst(1, 2, 3, 4, 5) - val is10 = is5 ++ is5 - - def lengthTest() = { - assert(xs0.length == 0) - assert(xs1.length == 1) - assert(xs2.length == 2) - assert(xs10.length == 10) - - assert(is0.length == 0) - assert(is1.length == 1) - assert(is2.length == 2) - assert(is10.length == 10) - } - - def concatTest() = { - assert(xs1 ++ xs0 == xs1) - assert(xs0 ++ xs1 == xs1) - assert(xs3 ++ xs0 == xs3) - assert(xs0 ++ xs4 == xs4) - - assert(is1 ++ is0 == is1) - assert(is0 ++ is1 == is1) - assert(is3 ++ is0 == is3) - assert(is0 ++ is4 == is4) - } - - def foreachTest() = { - xs0.foreach(s => assert(s.length == 1)) - xs3.foreach(s => assert(s.length == 1)) - def test1() = { - var x = 0 - xs10.foreach(elem => x += elem.length) - } - def test2() = { - var y = 0 - xs10.foreachInlined(elem => y += elem.length) - } - test1() - test2() - - is0.foreach(i => assert(i == 1)) - is3.foreach(i => assert(i <= 3)) - } - - def mapTest() = { - val ys0 = xs0.map(_.reverse) - val ys1 = xs1.map(s => s + s) - assert(ys1.mkString == "aa") - val ys5 = xs5.map(s => s + s) - assert(ys5.mkString == "aa, bb, cc, dd, ee") - - val js0 = is0.map(i => i * i) - val js1 = is1.map(i => i + i) - assert(js1.mkString == "2") - val js5 = is5.map(s => s + s) - assert(js5.mkString == "2, 4, 6, 8, 10") - } - - def mapConserveTest() = { - val ys0 = xs0.mapConserve(_.reverse) - val ys1 = xs1.mapConserve(s => s + s) - assert(ys1.mkString == "aa") - val ys2 = xs2.mapConserve(identity) - assert(ys2 `eqLst` xs2) - val ys5 = xs5.map(s => s + s) - assert(ys5.mkString == "aa, bb, cc, dd, ee") - val ys4 = xs4.mapConserve(s => if (s == "c") "cc" else s) - assert(ys4.mkString == "a, b, cc, d") - - val js0 = is0.mapConserve(i => i * i) - val js1 = is1.mapConserve(s => s + s) - assert(js1.mkString == "2") - val js2 = is2.mapConserve(identity) - assert(js2 `eqLst` is2) - val js5 = is5.map(s => s + s) - assert(js5.mkString == "2, 4, 6, 8, 10") - val js4 = is4.mapConserve(s => if (s == 3) -3 else s) - assert(js4.mkString == "1, 2, -3, 4") - } - - def flatMapTest() = { - val ys0 = xs0.flatMap(s => Lst(s, s)) - assert(ys0.isEmpty) - val ys2 = xs2.flatMap(s => Lst(s, s)) - assert(ys2.mkString == "a, a, b, b") - val ys2a = xs2.flatMap(_ => Lst.Empty) - assert(ys2a.isEmpty) - val ys4 = xs4.flatMap(s => Lst.fill(s.head - 'a')(s)) - assert(ys4.mkString == "b, c, c, d, d, d") - val ys5 = xs5.flatMap(s => if s == "c" then Lst(s) else Lst()) - assert(ys5 == Lst("c")) - - val js0 = is0.flatMap(s => Lst(s, s)) - assert(js0.isEmpty) - val js2 = is2.flatMap(s => Lst(s, s)) - assert(js2.mkString == "1, 1, 2, 2") - val js2a = is2.flatMap(_ => Lst.Empty) - assert(js2a.isEmpty) - val js4 = is4.flatMap(s => Lst.fill(s - 1)(s)) - assert(js4.mkString == "2, 3, 3, 4, 4, 4", js4) - val js5 = is5.flatMap(s => if s == 3 then Lst(-3) else Lst()) - assert(js5 == Lst(-3)) - } - - def filterTest() = { - val ys0 = xs0.filter(_.head >= 'c') - assert(ys0.isEmpty) - val ys1 = xs1.filter(_.head >= 'c') - assert(ys1.isEmpty) - val ys1a = xs1.filterNot(_.head >= 'c') - assert(ys1a `eqLst` xs1) - val ys5 = xs5.filter(_.head % 2 != 0) - assert(ys5 === Lst("a", "c", "e"), ys5) - - val js0 = is0.filter(_ > 3) - assert(js0.isEmpty) - val js1 = is1.filter(_ > 3) - assert(js1.isEmpty) - val js1a = is1.filterNot(_ > 3) - assert(js1a `eqLst` is1) - val js5 = is5.filter(_ % 2 != 0) - assert(js5 === Lst(1, 3, 5), js5) - } - - def existsTest() = { - assert(!xs0.exists(_ => true)) - assert(xs1.exists(_ == "a")) - assert(xs5.exists(_ == "c")) - assert(!xs5.exists(_.head > 'e')) - - assert(!is0.exists(_ => true)) - assert(is1.exists(_ == 1)) - assert(is5.exists(_ == 3)) - assert(!is5.exists(_ > 5)) - } - - def forallTest() = { - assert(xs0.forall(_ => false)) - assert(xs1.forall(_ == "a")) - assert(xs5.forall(_.head <= 'e')) - assert(!xs5.forall(_ == "c")) - } - - def containsTest() = { - assert(!xs0.contains("")) - assert(xs1.contains("a")) - assert(xs10.contains("e")) - assert(!xs10.contains("f")) - - assert(!is0.contains(2)) - assert(is1.contains(1)) - assert(is10.contains(5)) - assert(!is10.contains(6)) - } - - def foldTest() = { - assert(xs0.foldLeft("x")(_ ++ _) == "x") - assert(xs1.foldLeft("x")(_ ++ _) == "xa") - assert(xs2.foldLeft("x")(_ ++ _) == "xab") - assert(xs3.foldLeft("x")(_ ++ _) == "xabc") - assert(("x" /: xs0)(_ ++ _) == "x") - assert(("x" /: xs1)(_ ++ _) == "xa") - assert(("x" /: xs2)(_ ++ _) == "xab") - assert(("x" /: xs3)(_ ++ _) == "xabc") - assert(xs1.reduceLeft(_ + _) == "a") - assert(xs3.reduceLeft(_ + _) == "abc") - - assert(is0.foldLeft(3)(_ + _) == 3) - assert(is1.foldLeft(3)(_ + _) == 4) - assert(is2.foldLeft(3)(_ + _) == 6) - assert(is3.foldLeft(3)(_ + _) == 9) - assert((3 /: is0)(_ + _) == 3) - assert((3 /: is1)(_ + _) == 4) - assert((3 /: is2)(_ + _) == 6) - assert((3 /: is3)(_ + _) == 9) - assert(is1.reduceLeft(_ + _) == 1) - assert(is3.reduceLeft(_ + _) == 6) - } - - def reverseTest() = { - assert(xs0.reverse === xs0) - assert(xs1.reverse === xs1) - assert(xs3.reverse.mkString == "c, b, a", xs3.reverse.mkString) - assert(xs4.reverse.reverse === xs4, xs4.reverse.reverse) - } - - def applyTest() = { - assert(xs5.head == "a") - assert(xs5.last == "e") - assert(xs5(3) == "d") - assert(xs1(0) == "a") - - assert(is5.head == 1) - assert(is5.last == 5) - assert(is5(3) == 4) - assert(is1(0) == 1) - } - - def sliceTest() = { - assert(xs5.slice(2, 4) === Lst("c", "d")) - assert(xs5.drop(4) === Lst("e")) - assert(xs5.take(4).mkString("") == "abcd") - assert(xs5.drop(-1) `eqLst` xs5) - assert(xs1.take(1) `eqLst` xs1) - assert(xs0.take(10).length == 0) - - assert(is5.slice(2, 4) === Lst(3, 4)) - assert(is5.drop(4) === Lst(5)) - assert(is5.take(4).mkString("") == "1234") - assert(is5.drop(-1) `eqLst` is5) - assert(is1.take(1) `eqLst` is1) - assert(is0.take(10).length == 0) - } - - def zipWithTest() = { - val ys4a = xs4.zipWith(xs5)(_ + _) - val ys4b = xs5.zipWith(xs4)(_ + _) - assert(ys4a.mkString("") == "aabbccdd", ys4a) - assert(ys4a === ys4b) - val ys1a = xs1.zipWith(xs1)(_ + _) - assert(ys1a === Lst("aa")) - val ys1b = xs1.zipWith(xs2)(_ + _) - assert(ys1b === Lst("aa")) - val ys1c = xs2.zipWith(xs1)(_ + _) - assert(ys1c === Lst("aa")) - val ys0a = xs1.zipWith(xs0)(_ + _) - val ys0b = xs0.zipWith(xs1)(_ + _) - assert((ys0a ++ ys0b).isEmpty) - val ys3i = xs3.zipWithIndex.map((x, y) => (x, y + 1)) - assert(ys3i === Lst(("a", 1), ("b", 2), ("c", 3)), ys3i) - - val js4a = is4.zipWith(is5)(_ + _) - val js4b = is5.zipWith(is4)(_ + _) - assert(js4a.mkString("") == "2468", js4a) - assert(js4a === js4b) - val js1a = is1.zipWith(is1)(_ + _) - assert(js1a === Lst(2)) - val js1b = is1.zipWith(is2)(_ + _) - assert(js1b === Lst(2)) - val js1c = is2.zipWith(is1)(_ + _) - assert(js1c === Lst(2)) - val js0a = is1.zipWith(is0)(_ + _) - val js0b = is0.zipWith(is1)(_ + _) - assert((js0a ++ js0b).isEmpty) - val js3i = is3.zipWithIndex.map((x, y) => (x, y + 1)) - assert(js3i === Lst((1, 1), (2, 2), (3, 3)), js3i) - assert(js3i.forall(_ == _)) - } - - def correspondsTest() = { - assert(xs4.corresponds(xs4)(_ == _)) - assert(!xs4.corresponds(xs5)(_ == _)) - assert(xs1.corresponds(xs1)(_ == _)) - assert(!xs1.corresponds(Lst("b"))(_ == _)) - assert(xs0.corresponds(xs0)(_ == _)) - assert(!xs0.corresponds(xs1)(_ == _)) - val zs1 = Lst(new Object, new Object) - val zs2 = Lst(zs1(0), zs1(1)) - val zs3 = Lst(new Object, new Object) - assert(zs1.eqElements(zs1)) - assert(zs1.eqElements(zs2)) - assert(!zs1.eqElements(zs3)) - - assert(is4.corresponds(is4)(_ == _)) - assert(!is4.corresponds(is5)(_ == _)) - assert(is1.corresponds(is1)(_ == _)) - assert(!is1.corresponds(Lst(-1))(_ == _)) - assert(is0.corresponds(is0)(_ == _)) - assert(!is0.corresponds(is1)(_ == _)) - } - - def bufferTest() = { - { val b = new Lst.Buffer[String] - b += "a" - assert(b.size == 1) - assert(b.toLst === Lst("a")) - b += "aa" - b ++= Lst.fill(20)("a") - assert(b.toLst.mkString("") == "a" * 23) - assert(b.size == 22) - } - - { val b = new Lst.Buffer[Int] - b += 1 - assert(b.size == 1) - assert(b.toLst === Lst(1)) - b += 11 - b ++= Lst.fill(20)(1) - assert(b.toLst.mkString("") == "1" * 23) - assert(b.size == 22) - } - } - - println("testing") - lengthTest() - concatTest() - foreachTest() - mapTest() - mapConserveTest() - flatMapTest() - filterTest() - existsTest() - forallTest() - containsTest() - foldTest() - reverseTest() - applyTest() - sliceTest() - zipWithTest() - correspondsTest() - bufferTest() -} \ No newline at end of file diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 3cdd4a076119..1ea1974880e6 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -27,7 +27,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => def isEmpty = elems == null def nonEmpty = elems != null - inline def foreach(op: => T => Unit): Unit = { + transparent def foreach(op: => T => Unit): Unit = { def sharedOp(x: T) = op(x) elems match { case null => @@ -38,7 +38,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def foreachReversed(inline op: T => Unit): Unit = { + transparent def foreachReversed(transparent op: T => Unit): Unit = { def sharedOp(x: T) = op(x) elems match { case null => @@ -52,7 +52,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ - inline def foreachInlined(op: => T => Unit): Unit = elems match { + transparent def foreachInlined(op: => T => Unit): Unit = elems match { case null => case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -101,7 +101,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } /** `f` is pulled out, not duplicated */ - inline def map[U](f: => T => U): Lst[U] = { + transparent def map[U](f: => T => U): Lst[U] = { def op(x: T) = f(x) elems match { case null => Empty @@ -193,7 +193,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => buf.toLst } - inline def exists(p: => T => Boolean): Boolean = { + transparent def exists(p: => T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => false @@ -206,7 +206,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def forall(p: => T => Boolean): Boolean = { + transparent def forall(p: => T => Boolean): Boolean = { def op(x: T) = p(x) elems match { case null => true @@ -219,7 +219,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def contains[U >: T](x: U): Boolean = elems match { + def contains[U >: T](x: U): Boolean = elems match { case null => false case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] var i = 0 @@ -229,7 +229,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => elem == x } - inline def foldLeft[U](z: U)(f: => (U, T) => U) = { + transparent def foldLeft[U](z: U)(f: => (U, T) => U) = { def op(x: U, y: T) = f(x, y) elems match { case null => z @@ -243,7 +243,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - inline def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) + transparent def /: [U](z: U)(op: => (U, T) => U) = foldLeft(z)(op) def reduceLeft[U >: T](op: (U, U) => U) = elems match { case null => diff --git a/tests/run/lst/LstTest.scala b/tests/run/lst/LstTest.scala index 9e3caad751a0..9f7c7a7ff1c6 100644 --- a/tests/run/lst/LstTest.scala +++ b/tests/run/lst/LstTest.scala @@ -238,7 +238,7 @@ object Test extends App { assert(is10.contains(5)) assert(!is10.contains(6)) - assert(!xss0.contains(List(""))) + assert(!xss0.contains(Lst(""))) assert(xss1.contains(Lst("a"))) assert(xss10.contains(Lst("e")), xss10) assert(!xss10.contains(Lst("f"))) diff --git a/tests/run/nats.scala-deptypes b/tests/run/nats.scala-deptypes index 80b68336d7e3..a20e521bb5e2 100644 --- a/tests/run/nats.scala-deptypes +++ b/tests/run/nats.scala-deptypes @@ -11,11 +11,11 @@ object Nat { val Z = new Z } -type NatOf(inline x: Int where x >= 0) = +type NatOf(transparent x: Int where x >= 0) = if (x == 0) Z else S[NatOf(x - 1)] -inline def natOf(inline x: Int where x >= 0): NatOf(x) = +inline def natOf(transparent x: Int where x >= 0): NatOf(x) = if (x == 0) Z else S(natOf(x - 1)) diff --git a/tests/run/quote-and-splice/Macros_1.scala b/tests/run/quote-and-splice/Macros_1.scala index d8aa24abeeaa..08259d650966 100644 --- a/tests/run/quote-and-splice/Macros_1.scala +++ b/tests/run/quote-and-splice/Macros_1.scala @@ -2,22 +2,22 @@ import scala.quoted._ object Macros { - inline def macro1 = ~ macro1Impl + transparent def macro1 = ~ macro1Impl def macro1Impl = '(3) - inline def macro2(inline p: Boolean) = ~ macro2Impl(p) + transparent def macro2(transparent p: Boolean) = ~ macro2Impl(p) def macro2Impl(p: Boolean) = if (p) '(3) else '(4) - inline def macro3(n: Int) = ~ macro3Impl('(n)) + transparent def macro3(n: Int) = ~ macro3Impl('(n)) def macro3Impl(p: Expr[Int]) = '{ 2 + ~p } - inline def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) + transparent def macro4(i: Int)(j: Int) = ~ macro4Impl('(i))('(j)) def macro4Impl(i: Expr[Int])(j: Expr[Int]) = '{ ~i + ~j } - inline def macro5(i: Int, j: Int) = ~ macro5Impl(j = '(j), i = '(i)) + transparent def macro5(i: Int, j: Int) = ~ macro5Impl(j = '(j), i = '(i)) def macro5Impl(i: Expr[Int], j: Expr[Int]) = '{ ~i + ~j } - inline def power(inline n: Int, x: Double) = ~powerCode(n, '(x)) + transparent def power(transparent n: Int, x: Double) = ~powerCode(n, '(x)) def powerCode(n: Int, x: Expr[Double]): Expr[Double] = if (n == 0) '(1.0) diff --git a/tests/run/quote-force/quoted_1.scala b/tests/run/quote-force/quoted_1.scala index 47c01a3c3abf..c4eff1425993 100644 --- a/tests/run/quote-force/quoted_1.scala +++ b/tests/run/quote-force/quoted_1.scala @@ -4,7 +4,7 @@ case class Location(owners: List[String]) object Location { - implicit inline def location: Location = ~impl + implicit transparent def location: Location = ~impl def impl: Expr[Location] = { val list = List("a", "b", "c", "d", "e", "f") diff --git a/tests/run/quote-impure-by-name/quoted_1.scala b/tests/run/quote-impure-by-name/quoted_1.scala index 5b83d7c37d93..6e569fab4765 100644 --- a/tests/run/quote-impure-by-name/quoted_1.scala +++ b/tests/run/quote-impure-by-name/quoted_1.scala @@ -9,7 +9,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index("0") - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('(prev))('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('(prev))('[K], '[H], '[T]) def succImpl[K, H, T](prev: Expr[Index[K, T]])(implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { val value = s"1 + {${prev.show}}" diff --git a/tests/run/quote-indexed-map-by-name/quoted_1.scala b/tests/run/quote-indexed-map-by-name/quoted_1.scala index 32ed34bac0e6..2a7003bc90e9 100644 --- a/tests/run/quote-indexed-map-by-name/quoted_1.scala +++ b/tests/run/quote-indexed-map-by-name/quoted_1.scala @@ -5,7 +5,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index(0) - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl('[K], '[H], '[T]) def succImpl[K, H, T](implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { '(new Index(0)) diff --git a/tests/run/quote-inline-function/quoted_1.scala b/tests/run/quote-inline-function/quoted_1.scala index 55dcc04efe8a..58f41a423995 100644 --- a/tests/run/quote-inline-function/quoted_1.scala +++ b/tests/run/quote-inline-function/quoted_1.scala @@ -4,8 +4,8 @@ import dotty.tools.dotc.quoted.Toolbox._ object Macros { - inline def foreach1(start: Int, end: Int, f: Int => Unit): String = ~impl('(start), '(end), '(f)) - inline def foreach2(start: Int, end: Int, f: => Int => Unit): String = ~impl('(start), '(end), '(f)) + transparent def foreach1(start: Int, end: Int, f: Int => Unit): String = ~impl('(start), '(end), '(f)) + transparent def foreach2(start: Int, end: Int, f: => Int => Unit): String = ~impl('(start), '(end), '(f)) def impl(start: Expr[Int], end: Expr[Int], f: Expr[Int => Unit]): Expr[String] = { val res = '{ diff --git a/tests/run/quote-sep-comp/Macro_1.scala b/tests/run/quote-sep-comp/Macro_1.scala index c3ce7e2e1d53..5f7e539c4230 100644 --- a/tests/run/quote-sep-comp/Macro_1.scala +++ b/tests/run/quote-sep-comp/Macro_1.scala @@ -1,5 +1,5 @@ import scala.quoted._ object Macros { - inline def assert2(expr: => Boolean): Unit = ~ assertImpl('(expr)) + transparent def assert2(expr: => Boolean): Unit = ~ assertImpl('(expr)) def assertImpl(expr: Expr[Boolean]) = '{ println(~expr) } } diff --git a/tests/run/quote-simple-macro/quoted_1.scala b/tests/run/quote-simple-macro/quoted_1.scala index 165889e278ee..7d1549148650 100644 --- a/tests/run/quote-simple-macro/quoted_1.scala +++ b/tests/run/quote-simple-macro/quoted_1.scala @@ -1,6 +1,6 @@ import scala.quoted._ object Macros { - inline def foo(inline i: Int, dummy: Int, j: Int): Int = ~bar(i + 1, '(j)) + transparent def foo(transparent i: Int, dummy: Int, j: Int): Int = ~bar(i + 1, '(j)) def bar(x: Int, y: Expr[Int]): Expr[Int] = '{ ~x.toExpr + ~y } } diff --git a/tests/run/quote-splice-interpret-1/Macro_1.scala b/tests/run/quote-splice-interpret-1/Macro_1.scala index 6ba8f0259e4e..16a12855bc8f 100644 --- a/tests/run/quote-splice-interpret-1/Macro_1.scala +++ b/tests/run/quote-splice-interpret-1/Macro_1.scala @@ -7,7 +7,7 @@ object Macros { case object Z extends Nat case class S[N <: Nat]() extends Nat - inline def isZero(inline n: Int): Boolean = ~{ + transparent def isZero(transparent n: Int): Boolean = ~{ if (n == 0) '(true) else '(false) } diff --git a/tests/run/tasty-custom-show/quoted_1.scala b/tests/run/tasty-custom-show/quoted_1.scala index 59ee8806a5e5..dfadb7e83e37 100644 --- a/tests/run/tasty-custom-show/quoted_1.scala +++ b/tests/run/tasty-custom-show/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util.{TreeTraverser, Show} object Macros { - implicit inline def printOwners[T](x: => T): Unit = + implicit transparent def printOwners[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-eval/quoted_1.scala b/tests/run/tasty-eval/quoted_1.scala index 0b4e4761607b..dada0c5925f9 100644 --- a/tests/run/tasty-eval/quoted_1.scala +++ b/tests/run/tasty-eval/quoted_1.scala @@ -5,14 +5,14 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def foo(i: Int): String = + implicit transparent def foo(i: Int): String = ~impl('(i))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(i: Expr[Int])(implicit tasty: Tasty): Expr[String] = { value(i).toString.toExpr } - inline implicit def value[X](e: Expr[X])(implicit tasty: Tasty, ev: Valuable[X]): Option[X] = ev.value(e) + transparent implicit def value[X](e: Expr[X])(implicit tasty: Tasty, ev: Valuable[X]): Option[X] = ev.value(e) trait Valuable[X] { def value(e: Expr[X])(implicit tasty: Tasty): Option[X] diff --git a/tests/run/tasty-extractors-1/quoted_1.scala b/tests/run/tasty-extractors-1/quoted_1.scala index 3bbf1d01924e..a04873a208cb 100644 --- a/tests/run/tasty-extractors-1/quoted_1.scala +++ b/tests/run/tasty-extractors-1/quoted_1.scala @@ -4,7 +4,7 @@ import scala.tasty._ object Macros { - implicit inline def printTree[T](x: => T): Unit = + implicit transparent def printTree[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-2/quoted_1.scala b/tests/run/tasty-extractors-2/quoted_1.scala index 49d6c5493f40..b8cb7d1cccb3 100644 --- a/tests/run/tasty-extractors-2/quoted_1.scala +++ b/tests/run/tasty-extractors-2/quoted_1.scala @@ -4,7 +4,7 @@ import scala.tasty._ object Macros { - implicit inline def printTree[T](x: => T): Unit = + implicit transparent def printTree[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-3/quoted_1.scala b/tests/run/tasty-extractors-3/quoted_1.scala index e49d7b603158..741212d1f5a3 100644 --- a/tests/run/tasty-extractors-3/quoted_1.scala +++ b/tests/run/tasty-extractors-3/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printTypes[T](x: => T): Unit = + implicit transparent def printTypes[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-constants-1/quoted_1.scala b/tests/run/tasty-extractors-constants-1/quoted_1.scala index e26eee497303..47ebe1df8ba6 100644 --- a/tests/run/tasty-extractors-constants-1/quoted_1.scala +++ b/tests/run/tasty-extractors-constants-1/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util._ object Macros { - implicit inline def testMacro: Unit = + implicit transparent def testMacro: Unit = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-constants-2/quoted_1.scala b/tests/run/tasty-extractors-constants-2/quoted_1.scala index de396371fbe3..9b49afae8d00 100644 --- a/tests/run/tasty-extractors-constants-2/quoted_1.scala +++ b/tests/run/tasty-extractors-constants-2/quoted_1.scala @@ -6,7 +6,7 @@ import scala.tasty.util._ object Macros { - inline def testMacro: Unit = + transparent def testMacro: Unit = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-owners/quoted_1.scala b/tests/run/tasty-extractors-owners/quoted_1.scala index 5afeb121c448..dacacdc05a2b 100644 --- a/tests/run/tasty-extractors-owners/quoted_1.scala +++ b/tests/run/tasty-extractors-owners/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printOwners[T](x: => T): Unit = + implicit transparent def printOwners[T](x: => T): Unit = ~impl('(x))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-extractors-types/quoted_1.scala b/tests/run/tasty-extractors-types/quoted_1.scala index 3d8f4cfe553e..2b5b80112bc0 100644 --- a/tests/run/tasty-extractors-types/quoted_1.scala +++ b/tests/run/tasty-extractors-types/quoted_1.scala @@ -5,7 +5,7 @@ import scala.tasty.util.TreeTraverser object Macros { - implicit inline def printType[T]: Unit = + implicit transparent def printType[T]: Unit = ~impl('[T])(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Type[T])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-getfile/Macro_1.scala b/tests/run/tasty-getfile/Macro_1.scala index 867695307433..57c10cdb3909 100644 --- a/tests/run/tasty-getfile/Macro_1.scala +++ b/tests/run/tasty-getfile/Macro_1.scala @@ -3,7 +3,7 @@ import scala.tasty.{Tasty, TopLevelSplice} object SourceFiles { - implicit inline def getThisFile: String = + implicit transparent def getThisFile: String = ~getThisFileImpl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ private def getThisFileImpl(implicit tasty: Tasty): Expr[String] = { diff --git a/tests/run/tasty-indexed-map/quoted_1.scala b/tests/run/tasty-indexed-map/quoted_1.scala index 56d5559b67c9..ac66c1c4a75b 100644 --- a/tests/run/tasty-indexed-map/quoted_1.scala +++ b/tests/run/tasty-indexed-map/quoted_1.scala @@ -24,7 +24,7 @@ object Index { implicit def zero[K, T]: Index[K, (K, T)] = new Index(0) - implicit inline def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl(TopLevelSplice.tastyContext)('[K], '[H], '[T]) + implicit transparent def succ[K, H, T](implicit prev: => Index[K, T]): Index[K, (H, T)] = ~succImpl(TopLevelSplice.tastyContext)('[K], '[H], '[T]) def succImpl[K, H, T](tasty: Tasty)(implicit k: Type[K], h: Type[H], t: Type[T]): Expr[Index[K, (H, T)]] = { import tasty._ diff --git a/tests/run/tasty-linenumber/quoted_1.scala b/tests/run/tasty-linenumber/quoted_1.scala index 01cb1e7efb5f..09a42088f6e3 100644 --- a/tests/run/tasty-linenumber/quoted_1.scala +++ b/tests/run/tasty-linenumber/quoted_1.scala @@ -8,7 +8,7 @@ class LineNumber(val value: Int) { object LineNumber { - implicit inline def line[T >: Unit <: Unit]: LineNumber = + implicit transparent def line[T >: Unit <: Unit]: LineNumber = ~lineImpl('[T])(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def lineImpl(x: Type[Unit])(implicit tasty: Tasty): Expr[LineNumber] = { diff --git a/tests/run/tasty-location/quoted_1.scala b/tests/run/tasty-location/quoted_1.scala index 96bdc2439944..ccb088ab1c66 100644 --- a/tests/run/tasty-location/quoted_1.scala +++ b/tests/run/tasty-location/quoted_1.scala @@ -6,7 +6,7 @@ case class Location(owners: List[String]) object Location { - implicit inline def location: Location = + implicit transparent def location: Location = ~impl(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(implicit tasty: Tasty): Expr[Location] = { diff --git a/tests/run/tasty-macro-assert/quoted_1.scala b/tests/run/tasty-macro-assert/quoted_1.scala index 36a83c0106f4..2a5dd386119b 100644 --- a/tests/run/tasty-macro-assert/quoted_1.scala +++ b/tests/run/tasty-macro-assert/quoted_1.scala @@ -11,7 +11,7 @@ object Asserts { object Ops - inline def macroAssert(cond: Boolean): Unit = + transparent def macroAssert(cond: Boolean): Unit = ~impl('(cond))(TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl(cond: Expr[Boolean])(implicit tasty: Tasty): Expr[Unit] = { diff --git a/tests/run/tasty-positioned/quoted_1.scala b/tests/run/tasty-positioned/quoted_1.scala index b1536bc1fae2..7f4ecc09b701 100644 --- a/tests/run/tasty-positioned/quoted_1.scala +++ b/tests/run/tasty-positioned/quoted_1.scala @@ -9,7 +9,7 @@ case class Positioned[T](value: T, position: Position) object Positioned { - implicit inline def apply[T](x: T): Positioned[T] = + implicit transparent def apply[T](x: T): Positioned[T] = ~impl('(x))('[T], TopLevelSplice.tastyContext) // FIXME infer TopLevelSplice.tastyContext within top level ~ def impl[T](x: Expr[T])(implicit ev: Type[T], tasty: Tasty): Expr[Positioned[T]] = { diff --git a/tests/run/transparent.check b/tests/run/transparent.check index 5f711274b935..779315ff2880 100644 --- a/tests/run/transparent.check +++ b/tests/run/transparent.check @@ -7,3 +7,5 @@ Outer.f Inner Inner Outer.f Outer.f Inner +(hi,1) +(true,) diff --git a/tests/run/transparent/Test_2.scala b/tests/run/transparent/Test_2.scala index a2ab2caca22f..722e302c4b26 100644 --- a/tests/run/transparent/Test_2.scala +++ b/tests/run/transparent/Test_2.scala @@ -16,6 +16,11 @@ object Test { println(o.inner.m) println(o.inner.g) println(o.inner.h) - } + val p = new TestPassing + + println(p.foo("hi")) + println(p.bar(true)) + + } } diff --git a/tests/run/transparent/inlines_1.scala b/tests/run/transparent/inlines_1.scala index 946599b1a196..7fa71ba47ab9 100644 --- a/tests/run/transparent/inlines_1.scala +++ b/tests/run/transparent/inlines_1.scala @@ -2,7 +2,6 @@ package p import collection.mutable object transparents { - final val monitored = false transparent def f(x: Int): Int = x * x @@ -38,4 +37,22 @@ object transparents { } val inner = new Inner } + + class C[T](private[transparents] val x: T) { + private[transparents] def next[U](y: U): (T, U) = (xx, y) + private[transparents] var xx: T = _ + } + + class TestPassing { + transparent def foo[A](x: A): (A, Int) = { + val c = new C[A](x) + c.xx = c.x + c.next(1) + } + transparent def bar[A](x: A): (A, String) = { + val c = new C[A](x) + c.xx = c.x + c.next("") + } + } } diff --git a/tests/run/transparentAssign.scala b/tests/run/transparentAssign.scala index 4fec5fe255be..89147c554305 100644 --- a/tests/run/transparentAssign.scala +++ b/tests/run/transparentAssign.scala @@ -19,6 +19,6 @@ object Test { assert(x == 1 && y == 2) - val z = f(setX) // tests case where inline arg is not applied + val z = f(setX) // tests case where transparent arg is not applied } } diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 00b06895cc74..03fa1673820b 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -42,7 +42,7 @@ object Test extends App { type HNil = HNil.type type Z = Z.type - transparent def ToNat(inline n: Int): ToNat[Nat] = + transparent def ToNat(transparent n: Int): ToNat[Nat] = if n == 0 then new ToNat(Z) else { val n1 = ToNat(n - 1) @@ -101,7 +101,7 @@ object Test extends App { transparent val l1 = xs.length val l1a: 2 = l1 - transparent def index(xs: HList, inline idx: Int): Any = + transparent def index(xs: HList, transparent idx: Int): Any = if idx == 0 then xs.head else index(xs.tail, idx - 1) diff --git a/tests/run/typelevel3.scala b/tests/run/typelevel3.scala new file mode 100644 index 000000000000..9e6dc742e375 --- /dev/null +++ b/tests/run/typelevel3.scala @@ -0,0 +1,60 @@ + +trait HList { + def length: Int + def head: Any + def tail: HList + + transparent def isEmpty: Boolean = length == 0 +} + +case object HNil extends HList { + transparent def length = 0 + def head: Nothing = ??? + def tail: Nothing = ??? +} + +case class HCons[H, T <: HList](hd: H, tl: T) extends HList { + transparent def length = 1 + tl.length + def head: H = this.hd + def tail: T = this.tl +} + +case class Typed[T](val value: T) { type Type = T } + +object Test extends App { + type HNil = HNil.type + + transparent def concat(xs: HList, ys: HList): Typed[_ <: HList] = + if xs.isEmpty then Typed(ys) + else Typed(HCons(xs.head, concat(xs.tail, ys).value)) + + val xs = HCons(1, HCons("a", HCons("b", HNil))) + val ys = HCons(true, HCons(1.0, HNil)) + val zs = concat(xs, ys) + val zs1: zs.Type = zs.value + + val control: HCons[Int, HCons[String, HCons[String, HCons[Boolean, HCons[Double, HNil]]]]] = zs.value + + transparent def index(xs: HList, idx: Int): Typed[_] = + if idx == 0 then Typed(xs.head) + else Typed(index(xs.tail, idx - 1).value) + + val zsv = zs.value + val zs0 = index(zsv, 0) + val zs0c: Int = zs0.value + val zs4 = index(zsv, 4) + val zs4c: Double = zs4.value + def zs5 = index(zsv, 5) + def zs5c: Nothing = zs5.value + + def opaqueConcat(xs: HList, ys: HList): HList = + if xs.isEmpty then ys + else HCons(xs.head, opaqueConcat(xs.tail, ys)) + + transparent def compactConcat(xs: HList, ys: HList): HList = { + erased val r = concat(xs, ys) + opaqueConcat(xs, ys).asInstanceOf[r.Type] + } + + val czs = compactConcat(xs, ys) +} \ No newline at end of file From b7f83024cc5fd27307610b2e46ec9abae91090c8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:38:16 +0200 Subject: [PATCH 15/62] Consolidate InlineTyper Drop the ReTyper, which is not used anymore. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 42 +++++-------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 775d48d3b13a..c59738fc0e17 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -688,7 +688,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree }} - val inlineTyper = if (meth.isTransparentMethod) new TransparentTyper else new InlineReTyper + val inlineTyper = new InlineTyper val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope // The complete translation maps references to `this` and parameters to @@ -839,9 +839,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else None } - /** A base trait of InlineTyper and InlineReTyper containing operations that - * work the same way for both. Beyond typing or retyping, an inline typer performs - * the following functions: + /** A typer for inlined bodies of transparent methods. Beyond standard typing + * an inline typer performs the following functions: * * 1. Implement constant folding over inlined code * 2. Selectively expand ifs with constant conditions @@ -849,7 +848,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * 4. Make sure inlined code is type-correct. * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ - trait InlineTyping extends Typer { + class InlineTyper extends Typer { protected def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { case InlineableArg(rhs) => @@ -872,6 +871,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { super.ensureAccessible(tpe, superAccess, pos) } + override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = + reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) + + override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = + constToLiteral(reduceProjection(super.typedSelect(tree, pt))) + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { val cond1 = typed(tree.cond, defn.BooleanType) cond1.tpe.widenTermRefExpr match { @@ -923,33 +928,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { constToLiteral(betaReduce(super.typedApply(tree, pt))) } - } - - /** A full typer used for transparent methods */ - private class TransparentTyper extends Typer with InlineTyping { - - override def typedTypedSplice(tree: untpd.TypedSplice)(implicit ctx: Context): Tree = - reduceProjection(tryInline(tree.splice) `orElse` super.typedTypedSplice(tree)) - - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = - constToLiteral(reduceProjection(super.typedSelect(tree, pt))) - } - - /** A re-typer used for inlined methods */ - private class InlineReTyper extends ReTyper with InlineTyping { - - override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context) = - tryInline(tree.asInstanceOf[tpd.Tree]) `orElse` super.typedIdent(tree, pt) - - override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - assert(tree.hasType, tree) - val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this)) - val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt) - ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.pos) - res - } - override def newLikeThis: Typer = new InlineReTyper + override def newLikeThis: Typer = new InlineTyper } /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. From 67e9fef8c8c5a9966c826e45b28cc5c999eca3a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:38:40 +0200 Subject: [PATCH 16/62] Split PrepareTransparent off Inliner --- .../dotty/tools/dotc/CompilationUnit.scala | 2 +- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 12 +- .../dotty/tools/dotc/typer/Implicits.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 370 ---------------- .../src/dotty/tools/dotc/typer/Namer.scala | 2 +- .../tools/dotc/typer/PrepareTransparent.scala | 397 ++++++++++++++++++ .../src/dotty/tools/dotc/typer/Typer.scala | 4 +- 7 files changed, 412 insertions(+), 377 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index 3fc50449978a..f66313546081 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -5,7 +5,7 @@ import core.Types.Type // Do not remove me #3383 import util.SourceFile import ast.{tpd, untpd} import tpd.{ Tree, TreeTraverser } -import typer.Inliner.InlineAccessors +import typer.PrepareTransparent.InlineAccessors import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.SymDenotations.ClassDenotation import dotty.tools.dotc.core.Symbols._ diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index fc359edc0eba..6ea974ef4973 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -460,7 +460,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * Strictly speaking we can't replace `O.x` with `42`. But this would make * most expressions non-constant. Maybe we can change the spec to accept this * kind of eliding behavior. Or else enforce true purity in the compiler. - * The choice will be affected by what we will do with `inline` and with + * The choice will be affected by what we will do with `transparent` and with * Singleton type bounds (see SIP 23). Presumably * * object O1 { val x: Singleton = 42; println("43") } @@ -474,7 +474,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => * * O2.x = 42 * - * Revisit this issue once we have implemented `inline`. Then we can demand + * Revisit this issue once we have standardized on `transparent`. Then we can demand * purity of the prefix unless the selection goes to a transparent val. * * Note: This method should be applied to all term tree nodes that are not literals, @@ -710,6 +710,14 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => Nil } + /** The qualifier part of a Select or Ident. + * For an Ident, this is the `This` of the current class. + */ + def qualifier(tree: Tree)(implicit ctx: Context) = tree match { + case Select(qual, _) => qual + case _ => This(ctx.owner.enclosingClass.asClass) + } + /** Is this a selection of a member of a structural type that is not a member * of an underlying class or trait? */ diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index c1107a5803b3..c3e5ab431213 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -1135,7 +1135,7 @@ trait Implicits { self: Typer => searchImplicits(eligible, contextual) match { case result: SearchSuccess => if (contextual && ctx.mode.is(Mode.TransparentBody)) - Inliner.markContextualImplicit(result.tree) + PrepareTransparent.markContextualImplicit(result.tree) result case failure: SearchFailure => failure.reason match { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index c59738fc0e17..589968fa3a3c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -33,16 +33,6 @@ import ast.TreeInfo object Inliner { import tpd._ - /** Marks an implicit reference found in the context (as opposed to the implicit scope) - * from an inlineable body. Such references will be carried along with the body to - * the expansion site. - */ - private val ContextualImplicit = new Property.StickyKey[Unit] - - def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = - if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) - methPart(tree).putAttachment(ContextualImplicit, ()) - /** A key to be used in a context property that provides a map from enclosing implicit * value bindings to their right hand sides. */ @@ -52,358 +42,6 @@ object Inliner { def inlineBindings(implicit ctx: Context): MutableSymbolMap[Tree] = ctx.property(InlineBindings).get - class InlineAccessors extends AccessProxies { - - /** If an inline accessor name wraps a unique inline name, this is taken as indication - * that the inline accessor takes its receiver as first parameter. Such accessors - * are created by MakeInlineablePassing. - */ - override def passReceiverAsArg(name: Name)(implicit ctx: Context) = name match { - case InlineAccessorName(UniqueInlineName(_, _)) => true - case _ => false - } - - /** A tree map which inserts accessors for non-public term members accessed from inlined code. - */ - abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert { - def accessorNameKind = InlineAccessorName - - /** A definition needs an accessor if it is private, protected, or qualified private - * and it is not part of the tree that gets inlined. The latter test is implemented - * by excluding all symbols properly contained in the inlined method. - * - * Constant vals don't need accessors since they are inlined in FirstTransform. - */ - def needsAccessor(sym: Symbol)(implicit ctx: Context) = - sym.isTerm && - (sym.is(AccessFlags) || sym.privateWithin.exists) && - !sym.isContainedIn(inlineSym) && - !(sym.isStable && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) - - def preTransform(tree: Tree)(implicit ctx: Context): Tree - - def postTransform(tree: Tree)(implicit ctx: Context) = tree match { - case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => - cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) - case _ => - tree - } - - override def transform(tree: Tree)(implicit ctx: Context): Tree = - postTransform(super.transform(preTransform(tree))) - } - - /** Direct approach: place the accessor with the accessed symbol. This has the - * advantage that we can re-use the receiver as is. But it is only - * possible if the receiver is essentially this or an outer this, which is indicated - * by the test that we can find a host for the accessor. - */ - class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { - def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case tree: RefTree if needsAccessor(tree.symbol) => - if (tree.symbol.isConstructor) { - ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) - tree // TODO: create a proper accessor for the private constructor - } - else useAccessor(tree) - case _ => - tree - } - override def ifNoHost(reference: RefTree)(implicit ctx: Context): Tree = reference - } - - /** Fallback approach if the direct approach does not work: Place the accessor method - * in the same class as the inlined method, and let it take the receiver as parameter. - * This is tricky, since we have to find a suitable type for the parameter, which might - * require additional type parameters for the inline accessor. An example is in the - * `TestPassing` class in test `run/inline/inlines_1`: - * - * class C[T](x: T) { - * private[inlines] def next[U](y: U): (T, U) = (x, y) - * } - * class TestPassing { - * transparent def foo[A](x: A): (A, Int) = { - * val c = new C[A](x) - * c.next(1) - * } - * transparent def bar[A](x: A): (A, String) = { - * val c = new C[A](x) - * c.next("") - * } - * - * `C` could be compiled separately, so we cannot place the inline accessor in it. - * Instead, the inline accessor goes into `TestPassing` and takes the actual receiver - * type as argument: - * - * def inline$next$i1[A, U](x$0: C[A])(y: U): (A, U) = - * x$0.next[U](y) - * - * Since different calls might have different receiver types, we need to generate one - * such accessor per call, so they need to have unique names. - */ - class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { - - def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { - case _: Apply | _: TypeApply | _: RefTree - if needsAccessor(tree.symbol) && tree.isTerm && !tree.symbol.isConstructor => - val (refPart, targs, argss) = decomposeCall(tree) - val qual = qualifier(refPart) - inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") - - // Need to dealias in order to cagtch all possible references to abstracted over types in - // substitutions - val dealiasMap = new TypeMap { - def apply(t: Type) = mapOver(t.dealias) - } - val qualType = dealiasMap(qual.tpe.widen) - - // The types that are local to the inlined method, and that therefore have - // to be abstracted out in the accessor, which is external to the inlined method - val localRefs = qualType.namedPartsWith(ref => - ref.isType && ref.symbol.isContainedIn(inlineSym)).toList - - // Add qualifier type as leading method argument to argument `tp` - def addQualType(tp: Type): Type = tp match { - case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType)) - case tp: ExprType => addQualType(tp.resultType) - case tp => MethodType(qualType.simplified :: Nil, tp) - } - - // Abstract accessed type over local refs - def abstractQualType(mtpe: Type): Type = - if (localRefs.isEmpty) mtpe - else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe) - .asInstanceOf[PolyType].flatten - - val accessed = refPart.symbol.asTerm - val accessedType = refPart.tpe.widen - val accessor = accessorSymbol( - owner = inlineSym.owner, - accessorName = InlineAccessorName(UniqueInlineName.fresh(accessed.name)), - accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))), - accessed = accessed) - - ref(accessor) - .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) - .appliedToArgss((qual :: Nil) :: argss) - .withPos(tree.pos) - - // TODO: Handle references to non-public types. - // This is quite tricky, as such types can appear anywhere, including as parts - // of types of other things. For the moment we do nothing and complain - // at the implicit expansion site if there's a reference to an inaccessible type. - // Draft code (incomplete): - // - // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType - // myAccessors += TypeDef(accessor).withPos(tree.pos.focus) - // ref(accessor).withPos(tree.pos) - // - case _ => tree - } - } - - /** Adds accessors for all non-public term members accessed - * from `tree`. Non-public type members are currently left as they are. - * This means that references to a private type will lead to typing failures - * on the code when it is inlined. Less than ideal, but hard to do better (see below). - * - * @return If there are accessors generated, a thicket consisting of the rewritten `tree` - * and all accessors, otherwise the original tree. - */ - def makeInlineable(tree: Tree)(implicit ctx: Context) = { - val inlineSym = ctx.owner - if (inlineSym.owner.isTerm) - // Transparent methods in local scopes can only be called in the scope they are defined, - // so no accessors are needed for them. - tree - else - new MakeInlineablePassing(inlineSym).transform( - new MakeInlineableDirect(inlineSym).transform(tree)) - } - } - - def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = - sym.isContainedIn(inlineMethod) && - sym != inlineMethod && - (!sym.is(Param) || sym.owner != inlineMethod) - - /** Register inline info for given transparent method `sym`. - * - * @param sym The symbol denotatioon of the transparent method for which info is registered - * @param treeExpr A function that computes the tree to be inlined, given a context - * This tree may still refer to non-public members. - * @param ctx The context to use for evaluating `treeExpr`. It needs - * to have the inlined method as owner. - */ - def registerInlineInfo( - inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { - inlined.unforcedAnnotation(defn.BodyAnnot) match { - case Some(ann: ConcreteBodyAnnotation) => - case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => - case _ => - if (!ctx.isAfterTyper) { - val inlineCtx = ctx - inlined.updateAnnotation(LazyBodyAnnotation { _ => - implicit val ctx = inlineCtx - val rawBody = treeExpr(ctx) - val typedBody = - if (ctx.reporter.hasErrors) rawBody - else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) - val inlineableBody = addReferences(inlined, originalBody, typedBody) - inlining.println(i"Body to inline for $inlined: $inlineableBody") - inlineableBody - }) - } - } - } - - /** Tweak untyped tree `original` so that all external references are typed - * and it reflects the changes in the corresponding typed tree `typed` that - * make `typed` inlineable. Concretely: - * - * - all external references via identifiers or this-references are converted - * to typed splices, - * - if X gets an inline accessor in `typed`, references to X in `original` - * are converted to the inline accessor name. - */ - def addReferences(inlineMethod: Symbol, - original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { - - def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod) - - // Maps from positions to external reference types and inline selector names. - object referenced extends TreeTraverser { - val typeAtPos = mutable.Map[Position, Type]() - val accessorAtPos = mutable.Map[Position, Symbol]() - val implicitSyms = mutable.Set[Symbol]() - val implicitRefs = new mutable.ListBuffer[Tree] - - def registerIfContextualImplicit(tree: Tree) = tree match { - case tree: RefTree - if tree.removeAttachment(ContextualImplicit).isDefined && - isExternal(tree.symbol) && - !implicitSyms.contains(tree.symbol) => - if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) - ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) - else { - implicitSyms += tree.symbol - implicitRefs += tree - } - case _ => - } - - def registerAccessor(tree: Tree) = { - inlining.println(i"accessor: $tree at ${tree.pos}") - accessorAtPos(tree.pos.toSynthetic) = tree.symbol - } - - def traverse(tree: Tree)(implicit ctx: Context): Unit = { - tree match { - case _: Ident | _: This => - //println(i"leaf: $tree at ${tree.pos}") - if (isExternal(tree.symbol)) { - if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") - typeAtPos(tree.pos.toSynthetic) = tree.tpe - } - case _: Select => - tree.symbol.name match { - case InlineAccessorName(UniqueInlineName(_, _)) => return // was already recorded in Apply - case InlineAccessorName(_) => registerAccessor(tree) - case _ => - } - case Apply(_: RefTree | _: TypeApply, receiver :: Nil) => - tree.symbol.name match { - case InlineAccessorName(UniqueInlineName(_, _)) => registerAccessor(tree) - case _ => - } - case _ => - } - registerIfContextualImplicit(tree) - traverseChildren(tree) - } - } - referenced.traverse(typed) - - // The untyped tree transform that applies the tweaks - object addRefs extends untpd.UntypedTreeMap { - override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = { - - def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match { - case Some(tpe) => untpd.TypedSplice(tree.withType(tpe)) - case none => tree - } - - def adjustForAccessor(ref: untpd.RefTree) = - referenced.accessorAtPos.get(ref.pos.toSynthetic) match { - case Some(acc) => - def accessorRef = untpd.TypedSplice(tpd.ref(acc)) - acc.name match { - case InlineAccessorName(UniqueInlineName(_, _)) => - // In this case we are seeing a pair like this: - // untyped typed - // t.x inline$x(t) - // Drop the selection, since it is part of the accessor - val Select(qual, _) = ref - untpd.Apply(accessorRef, qual :: Nil) - case _ => - accessorRef - } - case none => ref - } - - def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match { - case tree @ Ident(name1) => - referenced.typeAtPos.get(tree.pos.startPos) match { - case Some(tp: ThisType) => - val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos)) - cpy.Select(tree)(qual, name1) - case none => - tree - } - case tree => tree - } - - def isAccessorLHS(lhs: untpd.Tree): Boolean = lhs match { - case lhs: untpd.Apply => isAccessorLHS(lhs.fun) - case lhs: untpd.TypeApply => isAccessorLHS(lhs.fun) - case lhs: untpd.RefTree => lhs.name.is(InlineAccessorName) - case untpd.TypedSplice(lhs1) => lhs1.symbol.name.is(InlineAccessorName) - case _ => false - } - - val tree1 = super.transform(tree) - tree1 match { - case This(_) => - adjustLeaf(tree1) - case tree1: untpd.Ident => - adjustQualifier(adjustLeaf(adjustForAccessor(tree1))) - case tree1: untpd.Select => - adjustForAccessor(tree1) - case Assign(lhs, rhs) if isAccessorLHS(lhs) => - cpy.Apply(tree1)(lhs, rhs :: Nil) - case tree: untpd.DerivedTypeTree => - inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}") - untpd.TypedSplice(ctx.typer.typed(tree)) - case _ => - tree1 - } - } - } - val implicitBindings = - for (iref <- referenced.implicitRefs.toList) yield { - val localImplicit = iref.symbol.asTerm.copy( - owner = inlineMethod, - name = UniqueInlineName.fresh(iref.symbol.name.asTermName), - flags = Implicit | Method | Stable, - info = iref.symbol.info.ensureMethodic, - coord = inlineMethod.pos).asTerm - polyDefDef(localImplicit, tps => vrefss => - iref.appliedToTypes(tps).appliedToArgss(vrefss)) - } - val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe) - seq(implicitBindings, untpdSplice) - } - /** `sym` has a transparent method with a known body to inline (note: definitions coming * from Scala2x class files might be `@forceInline`, but still lack that body. */ @@ -468,14 +106,6 @@ object Inliner { } tpd.seq(inlined.bindings, reposition.transform(inlined.expansion)) } - - /** The qualifier part of a Select or Ident. - * For an Ident, this is the `This` of the current class. (TODO: use elsewhere as well?) - */ - private def qualifier(tree: Tree)(implicit ctx: Context) = tree match { - case Select(qual, _) => qual - case _ => This(ctx.owner.enclosingClass.asClass) - } } /** Produces an inlined version of `call` via its `inlined` method. diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ff3c5f7507e7..0aecef759123 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -761,7 +761,7 @@ class Namer { typer: Typer => private def addInlineInfo(sym: Symbol) = original match { case original: untpd.DefDef if sym.isTransparentMethod => - Inliner.registerInlineInfo( + PrepareTransparent.registerInlineInfo( sym, original.rhs, implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala new file mode 100644 index 000000000000..f365c66bd706 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -0,0 +1,397 @@ +package dotty.tools +package dotc +package typer + +import dotty.tools.dotc.ast.Trees.NamedArg +import dotty.tools.dotc.ast.{Trees, untpd, tpd, TreeTypeMap} +import Trees._ +import core._ +import Flags._ +import Symbols._ +import Types._ +import Decorators._ +import Constants._ +import StdNames.nme +import Contexts.Context +import Names.{Name, TermName, EmptyTermName} +import NameOps._ +import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName} +import ProtoTypes.selectionProto +import SymDenotations.SymDenotation +import Annotations._ +import transform.{ExplicitOuter, AccessProxies} +import Inferencing.fullyDefinedType +import config.Printers.inlining +import ErrorReporting.errorTree +import collection.mutable +import transform.TypeUtils._ +import reporting.trace +import util.Positions.Position +import util.Property +import ast.TreeInfo + +object PrepareTransparent { + import tpd._ + + /** Marks an implicit reference found in the context (as opposed to the implicit scope) + * from an inlineable body. Such references will be carried along with the body to + * the expansion site. + */ + private val ContextualImplicit = new Property.StickyKey[Unit] + + def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = + if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) + methPart(tree).putAttachment(ContextualImplicit, ()) + + class InlineAccessors extends AccessProxies { + + /** If an inline accessor name wraps a unique inline name, this is taken as indication + * that the inline accessor takes its receiver as first parameter. Such accessors + * are created by MakeInlineablePassing. + */ + override def passReceiverAsArg(name: Name)(implicit ctx: Context) = name match { + case InlineAccessorName(UniqueInlineName(_, _)) => true + case _ => false + } + + /** A tree map which inserts accessors for non-public term members accessed from inlined code. + */ + abstract class MakeInlineableMap(val inlineSym: Symbol) extends TreeMap with Insert { + def accessorNameKind = InlineAccessorName + + /** A definition needs an accessor if it is private, protected, or qualified private + * and it is not part of the tree that gets inlined. The latter test is implemented + * by excluding all symbols properly contained in the inlined method. + * + * Constant vals don't need accessors since they are inlined in FirstTransform. + */ + def needsAccessor(sym: Symbol)(implicit ctx: Context) = + sym.isTerm && + (sym.is(AccessFlags) || sym.privateWithin.exists) && + !sym.isContainedIn(inlineSym) && + !(sym.isStable && sym.info.widenTermRefExpr.isInstanceOf[ConstantType]) + + def preTransform(tree: Tree)(implicit ctx: Context): Tree + + def postTransform(tree: Tree)(implicit ctx: Context) = tree match { + case Assign(lhs, rhs) if lhs.symbol.name.is(InlineAccessorName) => + cpy.Apply(tree)(useSetter(lhs), rhs :: Nil) + case _ => + tree + } + + override def transform(tree: Tree)(implicit ctx: Context): Tree = + postTransform(super.transform(preTransform(tree))) + } + + /** Direct approach: place the accessor with the accessed symbol. This has the + * advantage that we can re-use the receiver as is. But it is only + * possible if the receiver is essentially this or an outer this, which is indicated + * by the test that we can find a host for the accessor. + */ + class MakeInlineableDirect(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { + def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case tree: RefTree if needsAccessor(tree.symbol) => + if (tree.symbol.isConstructor) { + ctx.error("Implementation restriction: cannot use private constructors in transparent methods", tree.pos) + tree // TODO: create a proper accessor for the private constructor + } + else useAccessor(tree) + case _ => + tree + } + override def ifNoHost(reference: RefTree)(implicit ctx: Context): Tree = reference + } + + /** Fallback approach if the direct approach does not work: Place the accessor method + * in the same class as the inlined method, and let it take the receiver as parameter. + * This is tricky, since we have to find a suitable type for the parameter, which might + * require additional type parameters for the inline accessor. An example is in the + * `TestPassing` class in test `run/inline/inlines_1`: + * + * class C[T](x: T) { + * private[inlines] def next[U](y: U): (T, U) = (x, y) + * } + * class TestPassing { + * transparent def foo[A](x: A): (A, Int) = { + * val c = new C[A](x) + * c.next(1) + * } + * transparent def bar[A](x: A): (A, String) = { + * val c = new C[A](x) + * c.next("") + * } + * + * `C` could be compiled separately, so we cannot place the inline accessor in it. + * Instead, the inline accessor goes into `TestPassing` and takes the actual receiver + * type as argument: + * + * def inline$next$i1[A, U](x$0: C[A])(y: U): (A, U) = + * x$0.next[U](y) + * + * Since different calls might have different receiver types, we need to generate one + * such accessor per call, so they need to have unique names. + */ + class MakeInlineablePassing(inlineSym: Symbol) extends MakeInlineableMap(inlineSym) { + + def preTransform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case _: Apply | _: TypeApply | _: RefTree + if needsAccessor(tree.symbol) && tree.isTerm && !tree.symbol.isConstructor => + val (refPart, targs, argss) = decomposeCall(tree) + val qual = qualifier(refPart) + inlining.println(i"adding receiver passing inline accessor for $tree/$refPart -> (${qual.tpe}, $refPart: ${refPart.getClass}, [$targs%, %], ($argss%, %))") + + // Need to dealias in order to cagtch all possible references to abstracted over types in + // substitutions + val dealiasMap = new TypeMap { + def apply(t: Type) = mapOver(t.dealias) + } + val qualType = dealiasMap(qual.tpe.widen) + + // The types that are local to the inlined method, and that therefore have + // to be abstracted out in the accessor, which is external to the inlined method + val localRefs = qualType.namedPartsWith(ref => + ref.isType && ref.symbol.isContainedIn(inlineSym)).toList + + // Add qualifier type as leading method argument to argument `tp` + def addQualType(tp: Type): Type = tp match { + case tp: PolyType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, addQualType(tp.resultType)) + case tp: ExprType => addQualType(tp.resultType) + case tp => MethodType(qualType.simplified :: Nil, tp) + } + + // Abstract accessed type over local refs + def abstractQualType(mtpe: Type): Type = + if (localRefs.isEmpty) mtpe + else PolyType.fromParams(localRefs.map(_.symbol.asType), mtpe) + .asInstanceOf[PolyType].flatten + + val accessed = refPart.symbol.asTerm + val accessedType = refPart.tpe.widen + val accessor = accessorSymbol( + owner = inlineSym.owner, + accessorName = InlineAccessorName(UniqueInlineName.fresh(accessed.name)), + accessorInfo = abstractQualType(addQualType(dealiasMap(accessedType))), + accessed = accessed) + + ref(accessor) + .appliedToTypeTrees(localRefs.map(TypeTree(_)) ++ targs) + .appliedToArgss((qual :: Nil) :: argss) + .withPos(tree.pos) + + // TODO: Handle references to non-public types. + // This is quite tricky, as such types can appear anywhere, including as parts + // of types of other things. For the moment we do nothing and complain + // at the implicit expansion site if there's a reference to an inaccessible type. + // Draft code (incomplete): + // + // val accessor = accessorSymbol(tree, TypeAlias(tree.tpe)).asType + // myAccessors += TypeDef(accessor).withPos(tree.pos.focus) + // ref(accessor).withPos(tree.pos) + // + case _ => tree + } + } + + /** Adds accessors for all non-public term members accessed + * from `tree`. Non-public type members are currently left as they are. + * This means that references to a private type will lead to typing failures + * on the code when it is inlined. Less than ideal, but hard to do better (see below). + * + * @return If there are accessors generated, a thicket consisting of the rewritten `tree` + * and all accessors, otherwise the original tree. + */ + def makeInlineable(tree: Tree)(implicit ctx: Context) = { + val inlineSym = ctx.owner + if (inlineSym.owner.isTerm) + // Transparent methods in local scopes can only be called in the scope they are defined, + // so no accessors are needed for them. + tree + else + new MakeInlineablePassing(inlineSym).transform( + new MakeInlineableDirect(inlineSym).transform(tree)) + } + } + + def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = + sym.isContainedIn(inlineMethod) && + sym != inlineMethod && + (!sym.is(Param) || sym.owner != inlineMethod) + + /** Register inline info for given transparent method `sym`. + * + * @param sym The symbol denotatioon of the transparent method for which info is registered + * @param treeExpr A function that computes the tree to be inlined, given a context + * This tree may still refer to non-public members. + * @param ctx The context to use for evaluating `treeExpr`. It needs + * to have the inlined method as owner. + */ + def registerInlineInfo( + inlined: Symbol, originalBody: untpd.Tree, treeExpr: Context => Tree)(implicit ctx: Context): Unit = { + inlined.unforcedAnnotation(defn.BodyAnnot) match { + case Some(ann: ConcreteBodyAnnotation) => + case Some(ann: LazyBodyAnnotation) if ann.isEvaluated => + case _ => + if (!ctx.isAfterTyper) { + val inlineCtx = ctx + inlined.updateAnnotation(LazyBodyAnnotation { _ => + implicit val ctx = inlineCtx + val rawBody = treeExpr(ctx) + val typedBody = + if (ctx.reporter.hasErrors) rawBody + else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) + val inlineableBody = addReferences(inlined, originalBody, typedBody) + inlining.println(i"Body to inline for $inlined: $inlineableBody") + inlineableBody + }) + } + } + } + + /** Tweak untyped tree `original` so that all external references are typed + * and it reflects the changes in the corresponding typed tree `typed` that + * make `typed` inlineable. Concretely: + * + * - all external references via identifiers or this-references are converted + * to typed splices, + * - if X gets an inline accessor in `typed`, references to X in `original` + * are converted to the inline accessor name. + */ + private def addReferences(inlineMethod: Symbol, + original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { + + def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod) + + // Maps from positions to external reference types and inline selector names. + object referenced extends TreeTraverser { + val typeAtPos = mutable.Map[Position, Type]() + val accessorAtPos = mutable.Map[Position, Symbol]() + val implicitSyms = mutable.Set[Symbol]() + val implicitRefs = new mutable.ListBuffer[Tree] + + def registerIfContextualImplicit(tree: Tree) = tree match { + case tree: RefTree + if tree.removeAttachment(ContextualImplicit).isDefined && + isExternal(tree.symbol) && + !implicitSyms.contains(tree.symbol) => + if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) + ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) + else { + implicitSyms += tree.symbol + implicitRefs += tree + } + case _ => + } + + def registerAccessor(tree: Tree) = { + inlining.println(i"accessor: $tree at ${tree.pos}") + accessorAtPos(tree.pos.toSynthetic) = tree.symbol + } + + def traverse(tree: Tree)(implicit ctx: Context): Unit = { + tree match { + case _: Ident | _: This => + //println(i"leaf: $tree at ${tree.pos}") + if (isExternal(tree.symbol)) { + if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + typeAtPos(tree.pos.toSynthetic) = tree.tpe + } + case _: Select => + tree.symbol.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => return // was already recorded in Apply + case InlineAccessorName(_) => registerAccessor(tree) + case _ => + } + case Apply(_: RefTree | _: TypeApply, receiver :: Nil) => + tree.symbol.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => registerAccessor(tree) + case _ => + } + case _ => + } + registerIfContextualImplicit(tree) + traverseChildren(tree) + } + } + referenced.traverse(typed) + + // The untyped tree transform that applies the tweaks + object addRefs extends untpd.UntypedTreeMap { + override def transform(tree: untpd.Tree)(implicit ctx: Context): untpd.Tree = { + + def adjustLeaf(tree: untpd.Tree): untpd.Tree = referenced.typeAtPos.get(tree.pos.toSynthetic) match { + case Some(tpe) => untpd.TypedSplice(tree.withType(tpe)) + case none => tree + } + + def adjustForAccessor(ref: untpd.RefTree) = + referenced.accessorAtPos.get(ref.pos.toSynthetic) match { + case Some(acc) => + def accessorRef = untpd.TypedSplice(tpd.ref(acc)) + acc.name match { + case InlineAccessorName(UniqueInlineName(_, _)) => + // In this case we are seeing a pair like this: + // untyped typed + // t.x inline$x(t) + // Drop the selection, since it is part of the accessor + val Select(qual, _) = ref + untpd.Apply(accessorRef, qual :: Nil) + case _ => + accessorRef + } + case none => ref + } + + def adjustQualifier(tree: untpd.Tree): untpd.Tree = tree match { + case tree @ Ident(name1) => + referenced.typeAtPos.get(tree.pos.startPos) match { + case Some(tp: ThisType) => + val qual = untpd.TypedSplice(This(tp.cls).withPos(tree.pos.startPos)) + cpy.Select(tree)(qual, name1) + case none => + tree + } + case tree => tree + } + + def isAccessorLHS(lhs: untpd.Tree): Boolean = lhs match { + case lhs: untpd.Apply => isAccessorLHS(lhs.fun) + case lhs: untpd.TypeApply => isAccessorLHS(lhs.fun) + case lhs: untpd.RefTree => lhs.name.is(InlineAccessorName) + case untpd.TypedSplice(lhs1) => lhs1.symbol.name.is(InlineAccessorName) + case _ => false + } + + val tree1 = super.transform(tree) + tree1 match { + case This(_) => + adjustLeaf(tree1) + case tree1: untpd.Ident => + adjustQualifier(adjustLeaf(adjustForAccessor(tree1))) + case tree1: untpd.Select => + adjustForAccessor(tree1) + case Assign(lhs, rhs) if isAccessorLHS(lhs) => + cpy.Apply(tree1)(lhs, rhs :: Nil) + case tree: untpd.DerivedTypeTree => + inlining.println(i"inlining derived $tree --> ${ctx.typer.typed(tree)}") + untpd.TypedSplice(ctx.typer.typed(tree)) + case _ => + tree1 + } + } + } + val implicitBindings = + for (iref <- referenced.implicitRefs.toList) yield { + val localImplicit = iref.symbol.asTerm.copy( + owner = inlineMethod, + name = UniqueInlineName.fresh(iref.symbol.name.asTermName), + flags = Implicit | Method | Stable, + info = iref.symbol.info.ensureMethodic, + coord = inlineMethod.pos).asTerm + polyDefDef(localImplicit, tps => vrefss => + iref.appliedToTypes(tps).appliedToArgss(vrefss)) + } + val untpdSplice = tpd.UntypedSplice(addRefs.transform(original)).withType(typed.tpe) + seq(implicitBindings, untpdSplice) + } +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5458f052b6e1..434b87f50e90 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -477,7 +477,7 @@ class Typer extends Namer case _ => false } val enclosingTransparent = ctx.owner.ownersIterator.findSymbol(_.isTransparentMethod) - if (enclosingTransparent.exists && !Inliner.isLocal(qual1.symbol, enclosingTransparent)) + if (enclosingTransparent.exists && !PrepareTransparent.isLocal(qual1.symbol, enclosingTransparent)) ctx.error(SuperCallsNotAllowedTransparent(enclosingTransparent), tree.pos) pt match { case pt: SelectionProto if pt.name.isTypeName => @@ -1440,7 +1440,7 @@ class Typer extends Namer if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) - if (sym.isTransparentMethod) Inliner.registerInlineInfo(sym, ddef.rhs, _ => rhs1) + if (sym.isTransparentMethod) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) From 8e5eacac91552bb7e79289815ed64147dd45c712 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:39:29 +0200 Subject: [PATCH 17/62] Don't add extra bindings for own implicit parameters in inlineable code Also, a small polishing for clarity that avoided an error in the optimizer (fixed by now). --- .../tools/dotc/typer/PrepareTransparent.scala | 16 ++++++++-------- .../src/dotty/tools/dotc/typer/ProtoTypes.scala | 6 ++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index f365c66bd706..a85b6875f8c1 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -213,10 +213,11 @@ object PrepareTransparent { } } + def isLocalOrParam(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = + sym.isContainedIn(inlineMethod) && sym != inlineMethod + def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context) = - sym.isContainedIn(inlineMethod) && - sym != inlineMethod && - (!sym.is(Param) || sym.owner != inlineMethod) + isLocalOrParam(sym, inlineMethod) && !(sym.is(Param) && sym.owner == inlineMethod) /** Register inline info for given transparent method `sym`. * @@ -260,8 +261,6 @@ object PrepareTransparent { private def addReferences(inlineMethod: Symbol, original: untpd.Tree, typed: tpd.Tree)(implicit ctx: Context): tpd.Tree = { - def isExternal(sym: Symbol) = sym.exists && !isLocal(sym, inlineMethod) - // Maps from positions to external reference types and inline selector names. object referenced extends TreeTraverser { val typeAtPos = mutable.Map[Position, Type]() @@ -272,7 +271,8 @@ object PrepareTransparent { def registerIfContextualImplicit(tree: Tree) = tree match { case tree: RefTree if tree.removeAttachment(ContextualImplicit).isDefined && - isExternal(tree.symbol) && + tree.symbol.exists && + !isLocalOrParam(tree.symbol, inlineMethod) && !implicitSyms.contains(tree.symbol) => if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) @@ -292,8 +292,8 @@ object PrepareTransparent { tree match { case _: Ident | _: This => //println(i"leaf: $tree at ${tree.pos}") - if (isExternal(tree.symbol)) { - if (ctx.debug) inlining.println(i"type at pos ${tree.pos.toSynthetic} = ${tree.tpe}") + if (tree.symbol.exists && !isLocal(tree.symbol, inlineMethod)) { + if (ctx.debug) inlining.println(i"type at $tree @ ${tree.pos.toSynthetic} = ${tree.tpe}") typeAtPos(tree.pos.toSynthetic) = tree.tpe } case _: Select => diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 2aa2eb0423e0..fe902a13de51 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -79,8 +79,10 @@ object ProtoTypes { * achieved by replacing expected type parameters with wildcards. */ def constrainResult(meth: Symbol, mt: Type, pt: Type)(implicit ctx: Context): Boolean = - if (Inliner.isTransparentInlineable(meth)) - constrainResult(mt, wildApprox(pt)) || true + if (Inliner.isTransparentInlineable(meth)) { + constrainResult(mt, wildApprox(pt)) + true + } else constrainResult(mt, pt) } From 8fe1a0ea0e88f33a3d10029ba79447613ab882a2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:46:14 +0200 Subject: [PATCH 18/62] Traverse all of bindings to compute reference counts of bindings We used to traverse only the RHS of bindings, but this misses references that are part of the binding's type. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 589968fa3a3c..cfcb7bd7ee0c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -579,19 +579,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } val countRefs = new TreeTraverser { override def traverse(t: Tree)(implicit ctx: Context) = { + def updateRefCount(sym: Symbol, inc: Int) = + for (x <- refCount.get(sym)) refCount(sym) = x + inc t match { - case t: RefTree => - refCount.get(t.symbol) match { - case Some(x) => refCount(t.symbol) = x + 1 - case none => - } + case t: RefTree => updateRefCount(t.symbol, 1) case _: New | _: TypeTree => t.tpe.foreachPart { - case ref: TermRef => - refCount.get(ref.symbol) match { - case Some(x) => refCount(ref.symbol) = x + 2 - case none => - } + case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 case _ => } case _ => @@ -600,7 +594,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } countRefs.traverse(tree) - for (binding <- bindings) countRefs.traverse(binding.rhs) + for (binding <- bindings) countRefs.traverse(binding) val inlineBindings = new TreeMap { override def transform(t: Tree)(implicit ctx: Context) = super.transform { From 812d22db94df1247e1f6586f75e88ecd81631099 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:46:36 +0200 Subject: [PATCH 19/62] Two fixes to pickling and unpickling of untyped trees - Fix pickling of untyped templates - Fix unpickling of TypeBoundsTrees --- .../dotty/tools/dotc/core/tasty/TreePickler.scala | 12 +++++++----- .../dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- tests/pickling/transparent.scala | 9 +++++++++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 tests/pickling/transparent.scala diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 83aaebd08793..2a2cc70df635 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -798,12 +798,14 @@ class TreePickler(pickler: TastyPickler) { pickleDef(OBJECTDEF, tree, tree.impl) case tree: untpd.Template => writeByte(TEMPLATE) - tree.parents.foreach(pickleParent) - if (!tree.self.isEmpty) { - writeByte(SELFDEF); pickleName(tree.self.name); pickleTpt(tree.self.tpt) + withLength { + tree.parents.foreach(pickleParent) + if (!tree.self.isEmpty) { + writeByte(SELFDEF); pickleName(tree.self.name); pickleTpt(tree.self.tpt) + } + pickleUntyped(tree.constr) + tree.body.foreach(pickleUntyped) } - pickleUntyped(tree.constr) - tree.body.foreach(pickleUntyped) case Import(expr, selectors) => writeByte(IMPORT) withLength { pickleUntyped(expr); pickleSelectors(selectors) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 1827e39dd644..40405e9194f8 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1365,7 +1365,7 @@ class TreeUnpickler(reader: TastyReader, untpd.LambdaTypeTree(tparams, body) case TYPEBOUNDStpt => val lo = readUntyped() - val hi = ifBefore(end)(lo, readUntyped()) + val hi = ifBefore(end)(readUntyped(), lo) untpd.TypeBoundsTree(lo, hi) case TYPEDSPLICE => untpd.TypedSplice(readTerm()) diff --git a/tests/pickling/transparent.scala b/tests/pickling/transparent.scala new file mode 100644 index 000000000000..fbf01d91508e --- /dev/null +++ b/tests/pickling/transparent.scala @@ -0,0 +1,9 @@ +class Foo { + transparent def foo() = { + abstract class C[T] extends Object { + def x: T + println(x) + } + () + } +} From bdf2b548a86fbbefcdac5044ac2f7c7970859cef Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:47:19 +0200 Subject: [PATCH 20/62] Adapt transparent value checking to new restrictions --- .../dotc/reporting/diagnostic/messages.scala | 4 ++-- .../src/dotty/tools/dotc/typer/Checking.scala | 18 +++++++++--------- tests/neg/inlinevals.scala | 15 ++++++++++----- tests/pos/i3597.scala | 3 --- 4 files changed, 21 insertions(+), 19 deletions(-) delete mode 100644 tests/pos/i3597.scala diff --git a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala index 92722b3a4ea6..ef0e416ae1a2 100644 --- a/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/diagnostic/messages.scala @@ -1748,7 +1748,7 @@ object messages { val kind = "Syntax" val msg = hl"no explicit ${"return"} allowed from transparent $owner" val explanation = - hl"""Methods marked with ${"inline"} modifier may not use ${"return"} statements. + hl"""Methods marked with ${"transparent"} modifier may not use ${"return"} statements. |Instead, you should rely on the last expression's value being |returned from a method. |""" @@ -2057,7 +2057,7 @@ object messages { case class ParamsNoTransparent(owner: Symbol)(implicit ctx: Context) extends Message(ParamsNoTransparentID) { val kind = "Syntax" - val msg = hl"""${"transparent"} modifier cannot be used for a ${owner.showKind} parameter""" + val msg = hl"""${"transparent"} modifier cannot be used for a parameter of a non-transparent method""" val explanation = "" } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 026d0ceb392a..b3ada92ca8e8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -369,7 +369,10 @@ object Checking { if (!ok && !sym.is(Synthetic)) fail(i"modifier `$flag` is not allowed for this definition") - if (sym.is(Transparent) && ((sym.is(ParamAccessor) && sym.owner.isClass) || sym.is(TermParam) && sym.owner.isClassConstructor)) + if (sym.is(Transparent) && + ( sym.is(ParamAccessor) && sym.owner.isClass + || sym.is(TermParam) && !sym.owner.isTransparentMethod + )) fail(ParamsNoTransparent(sym.owner)) if (sym.is(ImplicitCommon)) { @@ -669,14 +672,11 @@ trait Checking { def checkTransparentConformant(tree: Tree, isFinal: Boolean, what: => String)(implicit ctx: Context): Unit = { // final vals can be marked transparent even if they're not pure, see Typer#patchFinalVals val purityLevel = if (isFinal) Idempotent else Pure - tree.tpe match { - case tp: TermRef if tp.symbol.is(TransparentParam) => // ok - case tp => tp.widenTermRefExpr match { - case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok - case _ => - val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isTransparentMethod) - if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) - } + tree.tpe.widenTermRefExpr match { + case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok + case _ => + val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isTransparentMethod) + if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) } } diff --git a/tests/neg/inlinevals.scala b/tests/neg/inlinevals.scala index 52a2caa67907..c754991e567c 100644 --- a/tests/neg/inlinevals.scala +++ b/tests/neg/inlinevals.scala @@ -1,6 +1,8 @@ object Test { - def power(x: Double, transparent n: Int): Double = ??? + def power0(x: Double, transparent n: Int): Double = ??? // error + + transparent def power(x: Double, transparent n: Int): Double = ??? // ok transparent val N = 10 def X = 20 @@ -19,12 +21,15 @@ object Test { transparent val xs = List(1, 2, 3) // error: must be a constant expression - def f(transparent xs: List[Int]) = xs + transparent def foo(x: Int) = { + + def f(transparent xs: List[Int]) = xs // error - f(List(1, 2, 3)) // error: must be a constant expression + transparent val y = { println("hi"); 1 } // ok + transparent val z = x // ok - def byname(transparent f: => String): Int = ??? // ok + } - byname("hello" ++ " world") + transparent def byname(transparent f: => String): Int = ??? // ok } diff --git a/tests/pos/i3597.scala b/tests/pos/i3597.scala deleted file mode 100644 index fadd09047f14..000000000000 --- a/tests/pos/i3597.scala +++ /dev/null @@ -1,3 +0,0 @@ -object Test { - def bar(transparent n: Int) = n -} From b6663230e2dabbad3984e58b038d74ca6f60c1ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:47:57 +0200 Subject: [PATCH 21/62] Fix types of implicit definitions in transparent closure We need the type of the reference, not the type of the symbol. With this change, the code specialization example compiles and runs correctly. --- .../tools/dotc/typer/PrepareTransparent.scala | 8 ++--- tests/run/typelevel-numeric.check | 1 + tests/run/typelevel-numeric.scala | 33 +++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/run/typelevel-numeric.check create mode 100644 tests/run/typelevel-numeric.scala diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index a85b6875f8c1..3558b30ad00f 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -265,7 +265,7 @@ object PrepareTransparent { object referenced extends TreeTraverser { val typeAtPos = mutable.Map[Position, Type]() val accessorAtPos = mutable.Map[Position, Symbol]() - val implicitSyms = mutable.Set[Symbol]() + val implicitRefTypes = mutable.Set[Type]() val implicitRefs = new mutable.ListBuffer[Tree] def registerIfContextualImplicit(tree: Tree) = tree match { @@ -273,11 +273,11 @@ object PrepareTransparent { if tree.removeAttachment(ContextualImplicit).isDefined && tree.symbol.exists && !isLocalOrParam(tree.symbol, inlineMethod) && - !implicitSyms.contains(tree.symbol) => + !implicitRefTypes.contains(tree.tpe) => if (tree.existsSubTree(t => isLocal(tree.symbol, inlineMethod))) ctx.warning("implicit reference $tree is dropped at inline site because it refers to local symbol(s)", tree.pos) else { - implicitSyms += tree.symbol + implicitRefTypes += tree.tpe implicitRefs += tree } case _ => @@ -386,7 +386,7 @@ object PrepareTransparent { owner = inlineMethod, name = UniqueInlineName.fresh(iref.symbol.name.asTermName), flags = Implicit | Method | Stable, - info = iref.symbol.info.ensureMethodic, + info = iref.tpe.widen.ensureMethodic, coord = inlineMethod.pos).asTerm polyDefDef(localImplicit, tps => vrefss => iref.appliedToTypes(tps).appliedToArgss(vrefss)) diff --git a/tests/run/typelevel-numeric.check b/tests/run/typelevel-numeric.check new file mode 100644 index 000000000000..18e16e38c5de --- /dev/null +++ b/tests/run/typelevel-numeric.check @@ -0,0 +1 @@ +-1.0 diff --git a/tests/run/typelevel-numeric.scala b/tests/run/typelevel-numeric.scala new file mode 100644 index 000000000000..7934f961722f --- /dev/null +++ b/tests/run/typelevel-numeric.scala @@ -0,0 +1,33 @@ + +import math.Numeric + +abstract class MathLib[N : Numeric] { + def dotProduct(xs: Array[N], ys: Array[N]): N +} + +object MathLib { + + transparent def apply[N](implicit n: Numeric[N]) = new MathLib[N] { + import n._ + def dotProduct(xs: Array[N], ys: Array[N]): N = { + require(xs.length == ys.length) + var i = 0 + var s: N = n.zero + while (i < xs.length) { + s = s + xs(i) * ys(i) + i += 1 + } + s + } + } +} + +object Test extends App { + + val mlib = MathLib.apply[Double](scala.math.Numeric.DoubleIsFractional) + + val xs = Array(1.0, 1.0) + val ys = Array(2.0, -3.0) + val p = mlib.dotProduct(xs, ys) + println(p) +} From a125c67e1e41305f5e69b48d8abce4c17736e122 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:48:36 +0200 Subject: [PATCH 22/62] Refactor Inliner Several refactorings for clarity and legibility; no change in functionality. --- .../dotty/tools/dotc/core/Decorators.scala | 5 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 388 +++++++++--------- 2 files changed, 199 insertions(+), 194 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 1417d15087a1..5da5cc8bd9e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -176,7 +176,10 @@ object Decorators { } implicit class genericDeco[T](val x: T) extends AnyVal { - def reporting(op: T => String): T = { println(op(x)); x } + def reporting(op: T => String, printer: config.Printers.Printer = config.Printers.default): T = { + printer.println(op(x)) + x + } def assertingErrorsReported(implicit ctx: Context): T = { assert(ctx.reporter.errorsReported) x diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index cfcb7bd7ee0c..d16b69389f0b 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -117,12 +117,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { import tpd._ import Inliner._ - private val (methPart, targs, argss) = decomposeCall(call) - private val meth = methPart.symbol - private val prefix = qualifier(methPart) + private val (methPart, callTypeArgs, callValueArgss) = decomposeCall(call) + private val inlinedMethod = methPart.symbol + private val inlineCallPrefix = qualifier(methPart) // Make sure all type arguments to the call are fully determined - for (targ <- targs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) + for (targ <- callTypeArgs) fullyDefinedType(targ.tpe, "inlined type argument", targ.pos) /** A map from parameter names of the transparent method to references of the actual arguments. * For a type argument this is the full argument type. @@ -155,8 +155,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A buffer for bindings that define proxies for actual arguments */ val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - computeParamBindings(meth.info, targs, argss) - private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -208,9 +206,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { assert(argss.isEmpty) } + // Compute val-definitions for all this-proxies and append them to `bindingsBuf` + private def computeThisBindings() = { + // All needed this-proxies, paired-with and sorted-by nesting depth of + // the classes they represent (innermost first) + val sortedProxies = thisProxy.toList.map { + case (cls, proxy) => + // The class that the this-proxy `selfSym` represents + def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol + // The total nesting depth of the class represented by `selfSym`. + def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length + (outerLevel(cls), proxy.symbol) + }.sortBy(-_._1) + + var lastSelf: Symbol = NoSymbol + var lastLevel: Int = 0 + for ((level, selfSym) <- sortedProxies) { + lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol + val rhs = + if (lastSelf.exists) + ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) + else if (rhsClsSym.is(Module) && rhsClsSym.isStatic) + ref(rhsClsSym.sourceModule) + else + inlineCallPrefix + bindingsBuf += ValDef(selfSym.asTerm, rhs) + inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") + lastSelf = selfSym + lastLevel = level + } + } + private def canElideThis(tpe: ThisType): Boolean = - prefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || - tpe.cls.isContainedIn(meth) || + inlineCallPrefix.tpe == tpe && ctx.owner.isContainedIn(tpe.cls) || + tpe.cls.isContainedIn(inlinedMethod) || tpe.cls.is(Package) /** Populate `thisProxy` and `paramProxy` as follows: @@ -228,13 +257,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private def registerType(tpe: Type): Unit = tpe match { case tpe: ThisType if !canElideThis(tpe) && !thisProxy.contains(tpe.cls) => val proxyName = s"${tpe.cls.name}_this".toTermName - val proxyType = tpe.asSeenFrom(prefix.tpe, meth.owner) + val proxyType = tpe.asSeenFrom(inlineCallPrefix.tpe, inlinedMethod.owner) thisProxy(tpe.cls) = newSym(proxyName, Synthetic, proxyType).termRef if (!tpe.cls.isStaticOwner) - registerType(meth.owner.thisType) // make sure we have a base from which to outer-select + registerType(inlinedMethod.owner.thisType) // make sure we have a base from which to outer-select case tpe: NamedType - if tpe.symbol.is(Param) && tpe.symbol.owner == meth && - !paramProxy.contains(tpe) => + if tpe.symbol.is(Param) && tpe.symbol.owner == inlinedMethod && !paramProxy.contains(tpe) => paramProxy(tpe) = paramBinding(tpe.name) case _ => } @@ -248,84 +276,60 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** The Inlined node representing the inlined call */ def inlined(pt: Type) = { + // Compute bindings for all parameters, appending them to bindingsBuf + computeParamBindings(inlinedMethod.info, callTypeArgs, callValueArgss) + // make sure prefix is executed if it is impure - if (!isIdempotentExpr(prefix)) registerType(meth.owner.thisType) + if (!isIdempotentExpr(inlineCallPrefix)) registerType(inlinedMethod.owner.thisType) // Register types of all leaves of inlined body so that the `paramProxy` and `thisProxy` maps are defined. rhsToInline.foreachSubTree(registerLeaf) - // The class that the this-proxy `selfSym` represents - def classOf(selfSym: Symbol) = selfSym.info.widen.classSymbol - - // The total nesting depth of the class represented by `selfSym`. - def outerLevel(selfSym: Symbol): Int = classOf(selfSym).ownersIterator.length - - // All needed this-proxies, paired-with and sorted-by nesting depth of - // the classes they represent (innermost first) - val sortedProxies = thisProxy.toList.map { - case (cls, proxy) => (outerLevel(cls), proxy.symbol) - } sortBy (-_._1) - - // Compute val-definitions for all this-proxies and append them to `bindingsBuf` - var lastSelf: Symbol = NoSymbol - var lastLevel: Int = 0 - for ((level, selfSym) <- sortedProxies) { - lazy val rhsClsSym = selfSym.info.widenDealias.classSymbol - val rhs = - if (lastSelf.exists) - ref(lastSelf).outerSelect(lastLevel - level, selfSym.info) - else if (rhsClsSym.is(Module) && rhsClsSym.isStatic) - ref(rhsClsSym.sourceModule) - else - prefix - bindingsBuf += ValDef(selfSym.asTerm, rhs) - inlining.println(i"proxy at $level: $selfSym = ${bindingsBuf.last}") - lastSelf = selfSym - lastLevel = level - } - - // The type map to apply to the inlined tree. This maps references to this-types - // and parameters to type references of their arguments or proxies. - val typeMap = new TypeMap { - def apply(t: Type) = t match { - case t: ThisType => thisProxy.getOrElse(t.cls, t) - case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) - case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) - case t => mapOver(t) - } - override def mapClassInfo(tp: ClassInfo) = mapFullClassInfo(tp) - } - - // The tree map to apply to the inlined tree. This maps references to this-types - // and parameters to references of their arguments or their proxies. - def treeMap(tree: Tree) = { - tree match { - case _: This => - tree.tpe match { - case thistpe: ThisType => - thisProxy.get(thistpe.cls) match { - case Some(t) => ref(t).withPos(tree.pos) - case None => tree - } - case _ => tree - } - case _: Ident => - paramProxy.get(tree.tpe) match { - case Some(t) if tree.isTerm && t.isSingleton => singleton(t).withPos(tree.pos) - case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) - case _ => tree - } - case _ => tree - }} + // Compute bindings for all this-proxies, appending them to bindingsBuf + computeThisBindings() val inlineTyper = new InlineTyper val inlineCtx = inlineContext(call).fresh.setTyper(inlineTyper).setNewScope - // The complete translation maps references to `this` and parameters to + // A tree type map to prepare the inlined body for typechecked. + // The translation maps references to `this` and parameters to // corresponding arguments or proxies on the type and term level. It also changes // the owner from the inlined method to the current owner. - val inliner = new TreeTypeMap(typeMap, treeMap, meth :: Nil, ctx.owner :: Nil)(inlineCtx) - + val inliner = new TreeTypeMap( + typeMap = + new TypeMap { + def apply(t: Type) = t match { + case t: ThisType => thisProxy.getOrElse(t.cls, t) + case t: TypeRef => paramProxy.getOrElse(t, mapOver(t)) + case t: SingletonType => paramProxy.getOrElse(t, mapOver(t)) + case t => mapOver(t) + } + override def mapClassInfo(tp: ClassInfo) = mapFullClassInfo(tp) + }, + treeMap = { + case tree: This => + tree.tpe match { + case thistpe: ThisType => + thisProxy.get(thistpe.cls) match { + case Some(t) => ref(t).withPos(tree.pos) + case None => tree + } + case _ => tree + } + case tree: Ident => + paramProxy.get(tree.tpe) match { + case Some(t) if tree.isTerm && t.isSingleton => singleton(t).withPos(tree.pos) + case Some(t) if tree.isType => TypeTree(t).withPos(tree.pos) + case _ => tree + } + case tree => tree + }, + oldOwners = inlinedMethod :: Nil, + newOwners = ctx.owner :: Nil + )(inlineCtx) + + // Apply inliner to `rhsToInline`, split off any implicit bindings from result, and + // make them part of `bindingsBuf`. The expansion is then the untyped tree that remains. val expansion = inliner.transform(rhsToInline.withPos(call.pos)) match { case Block(implicits, tpd.UntypedSplice(expansion)) => val prevOwners = implicits.map(_.symbol.owner).distinct @@ -346,31 +350,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { expansion } - /** If this is a value binding: - * - reduce its rhs if it is a projection and adjust its type accordingly, - * - record symbol -> rhs in the InlineBindings context propery. - * Also, set position to the one of the inline call. - */ - def normalizeBinding(binding: ValOrDefDef)(implicit ctx: Context) = { - val binding1 = binding match { - case binding: ValDef => - val rhs1 = reduceProjection(binding.rhs) - inlineBindings(inlineCtx).put(binding.symbol, rhs1) - if (rhs1 `eq` binding.rhs) binding - else { - binding.symbol.info = rhs1.tpe - cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) - } - case _ => - binding - } - binding1.withPos(call.pos) - } - trace(i"inlining $call", inlining, show = true) { /** All bindings in `bindingsBuf` */ - val bindings = bindingsBuf.toList.map(normalizeBinding) + val bindings = bindingsBuf.toList.map(reducer.normalizeBinding(_)(inlineCtx)) // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -387,38 +370,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } - /** An extractor for terms equivalent to `new C(args)`, returning the class `C` - * and the arguments `args`. Can see inside blocks and Inlined nodes and can - * follow a reference to an inline value binding to its right hand side. - */ - object NewInstance { - def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { - def unapplyLet(bindings: List[Tree], expr: Tree) = - unapply(expr) map { - case (cls, reduced, prefix) => (cls, reduced, bindings ::: prefix) + /** A utility object offering methods for rewriting inlined code */ + object reducer { + + /** An extractor for terms equivalent to `new C(args)`, returning the class `C` + * and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + */ + private object NewInstance { + def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { + def unapplyLet(bindings: List[Tree], expr: Tree) = + unapply(expr) map { + case (cls, reduced, prefix) => (cls, reduced, bindings ::: prefix) + } + tree match { + case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => + Some((tpt.tpe.classSymbol, args, Nil)) + case Ident(_) => + inlineBindings.get(tree.symbol).flatMap(unapply) + case Inlined(_, bindings, expansion) => + unapplyLet(bindings, expansion) + case Block(stats, expr) if isPureExpr(tree) => + unapplyLet(stats, expr) + case _ => + None } - tree match { - case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => - Some((tpt.tpe.classSymbol, args, Nil)) - case Ident(_) => - inlineBindings.get(tree.symbol).flatMap(unapply) - case Inlined(_, bindings, expansion) => - unapplyLet(bindings, expansion) - case Block(stats, expr) if isPureExpr(tree) => - unapplyLet(stats, expr) - case _ => - None } } - } - /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` - * where class `C` does not have initialization code and `x` is a parameter corresponding - * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation - * of impure arguments, otherwise `tree` itself. - */ - def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { - if (meth.isTransparentMethod) { + /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` + * where class `C` does not have initialization code and `x` is a parameter corresponding + * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation + * of impure arguments, otherwise `tree` itself. + */ + def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { case Select(NewInstance(cls, args, prefix), field) if cls.isNoInitsClass => @@ -441,32 +426,91 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val argInPlace = if (trailing.isEmpty) arg else letBindUnless(TreeInfo.Pure, arg)(seq(trailing, _)) - val reduced = seq(prefix, seq(leading, argInPlace)) - inlining.println(i"projecting $tree -> ${reduced}") - return reduced + seq(prefix, seq(leading, argInPlace)) + .reporting(res => i"projecting $tree -> $res", inlining) + } + else tree + case _ => tree + } + } + + /** If this is a value binding: + * - reduce its rhs if it is a projection and adjust its type accordingly, + * - record symbol -> rhs in the InlineBindings context propery. + */ + def normalizeBinding(binding: ValOrDefDef)(implicit ctx: Context) = { + val binding1 = binding match { + case binding: ValDef => + val rhs1 = reduceProjection(binding.rhs) + inlineBindings(ctx).put(binding.symbol, rhs1) + if (rhs1 `eq` binding.rhs) binding + else { + binding.symbol.info = rhs1.tpe + cpy.ValDef(binding)(tpt = TypeTree(rhs1.tpe), rhs = rhs1) } case _ => + binding } + binding1.withPos(call.pos) + } + /** An extractor for references to inlineable arguments. These are : + * - by-value arguments marked with `inline` + * - all by-name arguments + */ + private object InlineableArg { + lazy val paramProxies = paramProxy.values.toSet + def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = + if (paramProxies.contains(tree.typeOpt)) + bindingsBuf.find(_.name == tree.name) match { + case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => + Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) + case Some(ddef: DefDef) => + Some(ddef.rhs.changeOwner(ddef.symbol, ctx.owner)) + case _ => None + } + else None } - tree - } - /** An extractor for references to inlineable arguments. These are : - * - by-value arguments marked with `inline` - * - all by-name arguments - */ - private object InlineableArg { - lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = - if (paramProxies.contains(tree.typeOpt)) - bindingsBuf.find(_.name == tree.name) match { - case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => - Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) - case Some(ddef: DefDef) => - Some(ddef.rhs.changeOwner(ddef.symbol, ctx.owner)) - case _ => None + def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { + case InlineableArg(rhs) => + inlining.println(i"inline arg $tree -> $rhs") + rhs + case _ => + EmptyTree + } + + /** Rewrite an application + * + * ((x1, ..., sn) => b)(e1, ..., en) + * + * to + * + * val/def x1 = e1; ...; val/def xn = en; b + * + * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix + * refs among the ei's directly without creating an intermediate binding. + */ + def betaReduce(tree: Tree)(implicit ctx: Context) = tree match { + case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => + ddef.tpe.widen match { + case mt: MethodType if ddef.vparamss.head.length == args.length => + val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => + arg.tpe.dealias match { + case ref @ TermRef(NoPrefix, _) => ref.symbol + case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol + } + } + val expander = new TreeTypeMap( + oldOwners = ddef.symbol :: Nil, + newOwners = ctx.owner :: Nil, + substFrom = ddef.vparamss.head.map(_.symbol), + substTo = argSyms) + Block(bindingsBuf.toList, expander.transform(ddef.rhs)) + case _ => tree } - else None + case _ => tree + } } /** A typer for inlined bodies of transparent methods. Beyond standard typing @@ -479,14 +523,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * 5. Make sure that the tree's typing is idempotent (so that future -Ycheck passes succeed) */ class InlineTyper extends Typer { - - protected def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { - case InlineableArg(rhs) => - inlining.println(i"inline arg $tree -> $rhs") - rhs - case _ => - EmptyTree - } + import reducer._ override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { @@ -521,43 +558,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } - override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = { - - /** Rewrite an application - * - * ((x1, ..., sn) => b)(e1, ..., en) - * - * to - * - * val/def x1 = e1; ...; val/def xn = en; b - * - * where `def` is used for call-by-name parameters. However, we shortcut any NoPrefix - * refs among the ei's directly without creating an intermediate binding. - */ - def betaReduce(tree: Tree) = tree match { - case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => - ddef.tpe.widen match { - case mt: MethodType if ddef.vparamss.head.length == args.length => - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] - val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => - arg.tpe.dealias match { - case ref @ TermRef(NoPrefix, _) => ref.symbol - case _ => paramBindingDef(name, paramtp, arg, bindingsBuf).symbol - } - } - val expander = new TreeTypeMap( - oldOwners = ddef.symbol :: Nil, - newOwners = ctx.owner :: Nil, - substFrom = ddef.vparamss.head.map(_.symbol), - substTo = argSyms) - Block(bindingsBuf.toList, expander.transform(ddef.rhs)) - case _ => tree - } - case _ => tree - } - + override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = constToLiteral(betaReduce(super.typedApply(tree, pt))) - } override def newLikeThis: Typer = new InlineTyper } From e96c20f3f81460636a44c13d16c2011639dc1ae3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:49:15 +0200 Subject: [PATCH 23/62] Support transparent matches with simple typed patterns More work on pattern matching still to be done. --- .../src/dotty/tools/dotc/config/Config.scala | 2 +- .../src/dotty/tools/dotc/core/NameKinds.scala | 36 +-- .../dotty/tools/dotc/typer/Inferencing.scala | 2 + .../src/dotty/tools/dotc/typer/Inliner.scala | 221 +++++++++++++++--- .../tools/dotc/typer/PrepareTransparent.scala | 1 + .../src/dotty/tools/dotc/typer/Typer.scala | 16 +- library/src/dotty/DottyPredef.scala | 6 +- tests/neg/typelevel-defaultValue.scala | 11 + tests/neg/typelevel.scala | 9 + tests/pos/i3050.scala | 32 ++- tests/run/{lst/LstTest.check => lst.check} | 0 tests/run/lst/Lst.scala | 28 ++- tests/run/typelevel-defaultValue.check | Bin 0 -> 48 bytes tests/run/typelevel-defaultValue.scala | 37 +++ 14 files changed, 325 insertions(+), 76 deletions(-) create mode 100644 tests/neg/typelevel-defaultValue.scala rename tests/run/{lst/LstTest.check => lst.check} (100%) create mode 100644 tests/run/typelevel-defaultValue.check create mode 100644 tests/run/typelevel-defaultValue.scala diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index ea96d9dcd5d9..05aa89fab7b5 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = false + final val tracingEnabled = true /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index a9f841c33697..4e1494f28ed8 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -275,23 +275,25 @@ object NameKinds { } /** Other unique names */ - val TempResultName = new UniqueNameKind("ev$") - val EvidenceParamName = new UniqueNameKind("evidence$") - val DepParamName = new UniqueNameKind("(param)") - val LazyImplicitName = new UniqueNameKind("$_lazy_implicit_$") - val LazyLocalName = new UniqueNameKind("$lzy") - val LazyLocalInitName = new UniqueNameKind("$lzyINIT") - val LazyFieldOffsetName = new UniqueNameKind("$OFFSET") - val LazyBitMapName = new UniqueNameKind(nme.BITMAP_PREFIX.toString) - val NonLocalReturnKeyName = new UniqueNameKind("nonLocalReturnKey") - val WildcardParamName = new UniqueNameKind("_$") - val TailLabelName = new UniqueNameKind("tailLabel") - val ExceptionBinderName = new UniqueNameKind("ex") - val SkolemName = new UniqueNameKind("?") - val LiftedTreeName = new UniqueNameKind("liftedTree") - val SuperArgName = new UniqueNameKind("$superArg$") - val DocArtifactName = new UniqueNameKind("$doc") - val UniqueInlineName = new UniqueNameKind("$i") + val TempResultName = new UniqueNameKind("ev$") + val EvidenceParamName = new UniqueNameKind("evidence$") + val DepParamName = new UniqueNameKind("(param)") + val LazyImplicitName = new UniqueNameKind("$_lazy_implicit_$") + val LazyLocalName = new UniqueNameKind("$lzy") + val LazyLocalInitName = new UniqueNameKind("$lzyINIT") + val LazyFieldOffsetName = new UniqueNameKind("$OFFSET") + val LazyBitMapName = new UniqueNameKind(nme.BITMAP_PREFIX.toString) + val NonLocalReturnKeyName = new UniqueNameKind("nonLocalReturnKey") + val WildcardParamName = new UniqueNameKind("_$") + val TailLabelName = new UniqueNameKind("tailLabel") + val ExceptionBinderName = new UniqueNameKind("ex") + val SkolemName = new UniqueNameKind("?") + val LiftedTreeName = new UniqueNameKind("liftedTree") + val SuperArgName = new UniqueNameKind("$superArg$") + val DocArtifactName = new UniqueNameKind("$doc") + val UniqueInlineName = new UniqueNameKind("$i") + val TransparentScrutineeName = new UniqueNameKind("$scrutinee") + val TransparentBinderName = new UniqueNameKind("$elem") /** A kind of unique extension methods; Unlike other unique names, these can be * unmangled. diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index dfab0ca08fa2..151aae7ff77e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -185,6 +185,8 @@ object Inferencing { * * Invariant refinement can be assumed if `PatternType`'s class(es) are final or * case classes (because of `RefChecks#checkCaseClassInheritanceInvariant`). + * + * TODO: Update so that GADT symbols can be variant, and we special case final class types in patterns */ def constrainPatternType(tp: Type, pt: Type)(implicit ctx: Context): Boolean = { def refinementIsInvariant(tp: Type): Boolean = tp match { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d16b69389f0b..d1878b3a405f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -15,7 +15,7 @@ import StdNames.nme import Contexts.Context import Names.{Name, TermName, EmptyTermName} import NameOps._ -import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName} +import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName, TransparentScrutineeName, TransparentBinderName} import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Annotations._ @@ -25,6 +25,7 @@ import config.Printers.inlining import ErrorReporting.errorTree import collection.mutable import transform.TypeUtils._ +import transform.SymUtils._ import reporting.trace import util.Positions.Position import util.Property @@ -33,6 +34,9 @@ import ast.TreeInfo object Inliner { import tpd._ + /** An attachment labeling a toplevel match node of a transparent function */ + private val TopLevelMatch = new Property.Key[Unit] + /** A key to be used in a context property that provides a map from enclosing implicit * value bindings to their right hand sides. */ @@ -153,7 +157,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] /** A buffer for bindings that define proxies for actual arguments */ - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val bindingsBuf = new mutable.ListBuffer[MemberDef] private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -168,7 +172,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * @param bindingsBuf the buffer to which the definition should be appended */ private def paramBindingDef(name: Name, paramtp: Type, arg: Tree, - bindingsBuf: mutable.ListBuffer[ValOrDefDef]): ValOrDefDef = { + bindingsBuf: mutable.ListBuffer[MemberDef]): MemberDef = { val argtpe = arg.tpe.dealiasKeepAnnots val isByName = paramtp.dealias.isInstanceOf[ExprType] val inlineFlag = if (paramtp.hasAnnotation(defn.TransparentParamAnnot)) Transparent else EmptyFlags @@ -352,19 +356,25 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { trace(i"inlining $call", inlining, show = true) { - /** All bindings in `bindingsBuf` */ - val bindings = bindingsBuf.toList.map(reducer.normalizeBinding(_)(inlineCtx)) + // Normalize all bindings in `bindingsBuf` + val rawBindings = bindingsBuf.toList + bindingsBuf.clear() + for (binding <- rawBindings) bindingsBuf += reducer.normalizeBinding(binding)(inlineCtx) + + // Identifiy all toplevel matches + inlineTyper.prepare(expansion) - // The final expansion runs a typing pass over the inlined tree. See InlineTyper for details. + // Run a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) if (ctx.settings.verbose.value) { inlining.println(i"to inline = $rhsToInline") - inlining.println(i"original bindings = $bindings%\n%") + inlining.println(i"original bindings = ${bindingsBuf.toList}%\n%") inlining.println(i"original expansion = $expansion1") } - val (finalBindings, finalExpansion) = dropUnusedDefs(bindings, expansion1) + // Drop unused bindings + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) tpd.Inlined(call, finalBindings, finalExpansion) } @@ -374,9 +384,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { object reducer { /** An extractor for terms equivalent to `new C(args)`, returning the class `C` - * and the arguments `args`. Can see inside blocks and Inlined nodes and can - * follow a reference to an inline value binding to its right hand side. - */ + * and the arguments `args`. Can see inside blocks and Inlined nodes and can + * follow a reference to an inline value binding to its right hand side. + */ private object NewInstance { def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { def unapplyLet(bindings: List[Tree], expr: Tree) = @@ -384,8 +394,19 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case (cls, reduced, prefix) => (cls, reduced, bindings ::: prefix) } tree match { - case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) => - Some((tpt.tpe.classSymbol, args, Nil)) + case Apply(fn, args) => + fn match { + case Select(New(tpt), nme.CONSTRUCTOR) => + Some((tpt.tpe.classSymbol, args, Nil)) + case TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _) => + Some((tpt.tpe.classSymbol, args, Nil)) + case _ => + val meth = fn.symbol + if (meth.name == nme.apply && + meth.flags.is(Synthetic) && + meth.owner.linkedClass.is(Case)) Some(meth.owner.linkedClass, args, Nil) + else None + } case Ident(_) => inlineBindings.get(tree.symbol).flatMap(unapply) case Inlined(_, bindings, expansion) => @@ -399,10 +420,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } /** If we are inlining a transparent method and `tree` is equivalent to `new C(args).x` - * where class `C` does not have initialization code and `x` is a parameter corresponding - * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation - * of impure arguments, otherwise `tree` itself. - */ + * where class `C` does not have initialization code and `x` is a parameter corresponding + * to one of the arguments `args`, the corresponding argument, prefixed by the evaluation + * of impure arguments, otherwise `tree` itself. + */ def reduceProjection(tree: Tree)(implicit ctx: Context): Tree = { if (ctx.debug) inlining.println(i"try reduce projection $tree") tree match { @@ -438,7 +459,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * - reduce its rhs if it is a projection and adjust its type accordingly, * - record symbol -> rhs in the InlineBindings context propery. */ - def normalizeBinding(binding: ValOrDefDef)(implicit ctx: Context) = { + def normalizeBinding(binding: MemberDef)(implicit ctx: Context) = { val binding1 = binding match { case binding: ValDef => val rhs1 = reduceProjection(binding.rhs) @@ -453,10 +474,11 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } binding1.withPos(call.pos) } + /** An extractor for references to inlineable arguments. These are : - * - by-value arguments marked with `inline` - * - all by-name arguments - */ + * - by-value arguments marked with `inline` + * - all by-name arguments + */ private object InlineableArg { lazy val paramProxies = paramProxy.values.toSet def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = @@ -471,6 +493,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { else None } + object ConstantValue { + def unapply(tree: Tree)(implicit ctx: Context) = tree.tpe.widenTermRefExpr match { + case ConstantType(Constant(x)) => Some(x) + case _ => None + } + } + def tryInline(tree: tpd.Tree)(implicit ctx: Context) = tree match { case InlineableArg(rhs) => inlining.println(i"inline arg $tree -> $rhs") @@ -494,7 +523,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case Apply(Select(cl @ closureDef(ddef), nme.apply), args) if defn.isFunctionType(cl.tpe) => ddef.tpe.widen match { case mt: MethodType if ddef.vparamss.head.length == args.length => - val bindingsBuf = new mutable.ListBuffer[ValOrDefDef] + val bindingsBuf = new mutable.ListBuffer[MemberDef] val argSyms = (mt.paramNames, mt.paramInfos, args).zipped.map { (name, paramtp, arg) => arg.tpe.dealias match { case ref @ TermRef(NoPrefix, _) => ref.symbol @@ -511,6 +540,92 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } case _ => tree } + + type MatchRedux = Option[(List[MemberDef], untpd.Tree)] + + /** Reduce a toplevel match of a transparent function + * @param scrutinee the scrutinee expression, assumed to be pure + * @param scrutType its fully defined type + * @param cases All cases of the match + * @param typer The current inline typer + * @return optionally, if match can be reduced to a matching case: A pair of + * bindings for all pattern-bound variables and the untyped RHS of the case. + */ + def reduceTopLevelMatch(scrutinee: Tree, scrutType: Type, cases: List[untpd.CaseDef], typer: Typer)(implicit ctx: Context): MatchRedux = { + + /** Try to match pattern `pat` against scrutinee reference `scrut`. If succesful add + * bindings for variables bound in this pattern to `bindingsBuf`. + */ + def reducePattern(bindingsBuf: mutable.ListBuffer[MemberDef], scrut: TermRef, pat: Tree): Boolean = { + def newBinding(name: TermName, flags: FlagSet, rhs: Tree): Symbol = { + val sym = newSym(name, flags, rhs.tpe.widenTermRefExpr).asTerm + bindingsBuf += ValDef(sym, constToLiteral(rhs)) + sym + } + pat match { + case Typed(pat1, tpt) => + scrut <:< tpt.tpe && + reducePattern(bindingsBuf, scrut, pat1) + case pat @ Bind(name: TermName, body) => + reducePattern(bindingsBuf, scrut, body) && { + if (name != nme.WILDCARD) newBinding(name, EmptyFlags, ref(scrut)) + true + } + case Ident(name) => + name == nme.WILDCARD || + scrut =:= pat.tpe || + scrut.widen =:= pat.tpe.widen && scrut.widen.classSymbol.is(Module) + case UnApply(unapp, _, pats) => + unapp.tpe.widen match { + case mt: MethodType if mt.paramInfos.length == 1 => + val paramType = mt.paramInfos.head + val paramCls = paramType.classSymbol + (scrut <:< paramType && paramCls.is(Case) && unapp.symbol.is(Synthetic)) && { + val caseAccessors = + if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) + else paramCls.asClass.paramAccessors + var subOK = caseAccessors.length == pats.length + for ((pat, accessor) <- (pats, caseAccessors).zipped) + subOK = subOK && { + val rhs = constToLiteral(reduceProjection(ref(scrut).select(accessor).ensureApplied)) + val elem = newBinding(TransparentBinderName.fresh(), Synthetic, rhs) + reducePattern(bindingsBuf, elem.termRef, pat) + } + subOK + } + case _ => + false + } + case _ => false + } + } + + /** The initial scrutinee binding: `val $scrutineeN = ` */ + val scrutineeSym = newSym(TransparentScrutineeName.fresh(), Synthetic, scrutType).asTerm + val scrutineeBinding = normalizeBinding(ValDef(scrutineeSym, scrutinee)) + + def reduceCase(cdef: untpd.CaseDef): MatchRedux = { + def guardOK = cdef.guard.isEmpty || { + typer.typed(cdef.guard, defn.BooleanType) match { + case ConstantValue(true) => true + case _ => false + } + } + val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() += scrutineeBinding + if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, typer.typedPattern(cdef.pat, scrutType)) + && guardOK) + Some((caseBindingsBuf.toList, cdef.body)) + else + None + } + + def recur(cases: List[untpd.CaseDef]): MatchRedux = cases match { + case Nil => None + case cdef :: cases1 => reduceCase(cdef) `orElse` recur(cases1) + } + + recur(cases) + } } /** A typer for inlined bodies of transparent methods. Beyond standard typing @@ -525,6 +640,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { class InlineTyper extends Typer { import reducer._ + /** Mark all toplevel matches with the `TopLevelMatch` attachment */ + def prepare(tree: untpd.Tree): Unit = tree match { + case tree: untpd.Match => + tree.pushAttachment(TopLevelMatch, ()) + tree.cases.foreach(prepare) + case tree: untpd.Block => + prepare(tree.expr) + case _ => + } + override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => @@ -544,19 +669,40 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context) = constToLiteral(reduceProjection(super.typedSelect(tree, pt))) - override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = { - val cond1 = typed(tree.cond, defn.BooleanType) - cond1.tpe.widenTermRefExpr match { - case ConstantType(Constant(condVal: Boolean)) => - var selected = typed(if (condVal) tree.thenp else tree.elsep, pt) + override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = + typed(tree.cond, defn.BooleanType) match { + case cond1 @ ConstantValue(b: Boolean) => + var selected = typed(if (b) tree.thenp else tree.elsep, pt) if (selected.isEmpty) selected = tpd.Literal(Constant(())) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) - case _ => + case cond1 => val if1 = untpd.cpy.If(tree)(cond = untpd.TypedSplice(cond1)) super.typedIf(if1, pt) } - } + + override def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context) = + tree.removeAttachment(TopLevelMatch) match { + case Some(_) => + reduceTopLevelMatch(sel, selType, tree.cases, this) match { + case Some((caseBindings, rhs)) => + for (binding <- caseBindings) { + bindingsBuf += binding + ctx.enter(binding.symbol) + } + typedExpr(rhs, pt) + case None => + def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" + def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" + errorTree(tree, em"""cannot reduce top-level match of transparent function with + | scrutinee: $sel : $selType + | patterns : ${tree.cases.map(patStr).mkString("\n ")} + | + | Hint: if you do not expect the match to be reduced, put it in a locally { ... } block.""") + } + case None => + super.typedMatchFinish(tree, sel, selType, pt) + } override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context) = constToLiteral(betaReduce(super.typedApply(tree, pt))) @@ -567,12 +713,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** Drop any side-effect-free bindings that are unused in expansion or other reachable bindings. * Inline def bindings that are used only once. */ - def dropUnusedDefs(bindings: List[ValOrDefDef], tree: Tree)(implicit ctx: Context): (List[ValOrDefDef], Tree) = { + def dropUnusedDefs(bindings: List[MemberDef], tree: Tree)(implicit ctx: Context): (List[MemberDef], Tree) = { val refCount = newMutableSymbolMap[Int] - val bindingOfSym = newMutableSymbolMap[ValOrDefDef] - def isInlineable(binding: ValOrDefDef) = binding match { + val bindingOfSym = newMutableSymbolMap[MemberDef] + def isInlineable(binding: MemberDef) = binding match { case DefDef(_, Nil, Nil, _, _) => true - case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) + case vdef @ ValDef(_, _, _) => + vdef.name.is(TransparentScrutineeName) || isPureExpr(vdef.rhs) case _ => false } for (binding <- bindings if isInlineable(binding)) { @@ -586,7 +733,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { t match { case t: RefTree => updateRefCount(t.symbol, 1) case _: New | _: TypeTree => - t.tpe.foreachPart { + t.typeOpt.foreachPart { case ref: TermRef => updateRefCount(ref.symbol, 2) // can't be inlined, so make sure refCount is at least 2 case _ => } @@ -605,14 +752,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val sym = t.symbol refCount.get(sym) match { case Some(1) if sym.is(Method) => - bindingOfSym(sym).rhs.changeOwner(sym, ctx.owner) + bindingOfSym(sym) match { + case binding: ValOrDefDef => binding.rhs.changeOwner(sym, ctx.owner) + } case none => t } case _ => t } } } - def retain(binding: ValOrDefDef) = refCount.get(binding.symbol) match { + def retain(binding: MemberDef) = refCount.get(binding.symbol) match { case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method) case none => true } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 3558b30ad00f..75068e259c7f 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -290,6 +290,7 @@ object PrepareTransparent { def traverse(tree: Tree)(implicit ctx: Context): Unit = { tree match { + case Ident(nme.WILDCARD) => case _: Ident | _: This => //println(i"leaf: $tree at ${tree.pos}") if (tree.symbol.exists && !isLocal(tree.symbol, inlineMethod)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 434b87f50e90..3bcd4de5e5ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -966,7 +966,7 @@ class Typer extends Namer assignType(cpy.Closure(tree)(env1, meth1, target), meth1, target) } - def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context) = track("typedMatch") { + def typedMatch(tree: untpd.Match, pt: Type)(implicit ctx: Context): Tree = track("typedMatch") { tree.selector match { case EmptyTree => val (protoFormals, _) = decomposeProtoFunction(pt, 1) @@ -975,13 +975,17 @@ class Typer extends Namer case _ => val sel1 = typedExpr(tree.selector) val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.pos).widen - - val cases1 = harmonic(harmonize)(typedCases(tree.cases, selType, pt.notApplied)) - .asInstanceOf[List[CaseDef]] - assignType(cpy.Match(tree)(sel1, cases1), cases1) + typedMatchFinish(tree, sel1, selType, pt) } } + // Overridden in InlineTyper for transparent matches + def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context): Tree = { + val cases1 = harmonic(harmonize)(typedCases(tree.cases, selType, pt.notApplied)) + .asInstanceOf[List[CaseDef]] + assignType(cpy.Match(tree)(sel, cases1), cases1) + } + def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { /** gadtSyms = "all type parameters of enclosing methods that appear @@ -1004,7 +1008,7 @@ class Typer extends Namer accu(Set.empty, selType) } - cases mapconserve (typedCase(_, pt, selType, gadtSyms)) + cases.mapconserve(typedCase(_, pt, selType, gadtSyms)) } /** Type a case. */ diff --git a/library/src/dotty/DottyPredef.scala b/library/src/dotty/DottyPredef.scala index 632604e4691d..b831f1c3f6dc 100644 --- a/library/src/dotty/DottyPredef.scala +++ b/library/src/dotty/DottyPredef.scala @@ -34,8 +34,10 @@ object DottyPredef { assertFail() } - final def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") - final def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) + def assertFail(): Unit = throw new java.lang.AssertionError("assertion failed") + def assertFail(message: => Any): Unit = throw new java.lang.AssertionError("assertion failed: " + message) @forceInline final def implicitly[T](implicit ev: T): T = ev + + @forceInline def locally[T](body: => T): T = body } diff --git a/tests/neg/typelevel-defaultValue.scala b/tests/neg/typelevel-defaultValue.scala new file mode 100644 index 000000000000..97b347e39412 --- /dev/null +++ b/tests/neg/typelevel-defaultValue.scala @@ -0,0 +1,11 @@ + +object Test { + def anyValue[T]: T = ??? + + transparent def test[T] = anyValue[T] match { // error + case _: Byte => + case _: Char => + } + + test[String] +} \ No newline at end of file diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala index f75c425a4e9d..f35bc2f7f651 100644 --- a/tests/neg/typelevel.scala +++ b/tests/neg/typelevel.scala @@ -55,4 +55,13 @@ object Test { class Deco2(val as: HList) extends java.lang.Cloneable with java.lang.Comparable[Deco2] { transparent def ++ (bs: HList) = concat(as, bs) } + + def anyValue[T]: T = ??? + + transparent def test[T] = anyValue[T] match { + case _: Byte => + case _: Char => + } + + test[String] } \ No newline at end of file diff --git a/tests/pos/i3050.scala b/tests/pos/i3050.scala index 4366dc7887fd..6469bf3dad42 100644 --- a/tests/pos/i3050.scala +++ b/tests/pos/i3050.scala @@ -1,11 +1,39 @@ object Test { + trait Option[+T] + case object None extends Option[Nothing] + case class Some[+T](x: T) extends Option[T] + transparent def openImpl(): Int = Some(42) match { case Some(i) => i } - def open(): Int = openImpl() + def open() = openImpl() + + transparent def openImpl1(): Int = + new Some(42) match { case Some(i) => i } + + def open1() = openImpl1() transparent def openImpl2(): Int = - Some(42) match { case None => 42 } + None match { case None => 42 } def open2(): Int = openImpl2() } + +// Same as Test, with Scala2 case classes +object Test2 { + transparent def openImpl(): Int = + Some(42) match { case Some(i) => i } + + def open() = openImpl() + + transparent def openImpl1(): Int = + new Some(42) match { case Some(i) => i } + + def open1() = openImpl1() + + transparent def openImpl2(): Int = + None match { case None => 42 } + + def open2(): Int = openImpl2() + +} diff --git a/tests/run/lst/LstTest.check b/tests/run/lst.check similarity index 100% rename from tests/run/lst/LstTest.check rename to tests/run/lst.check diff --git a/tests/run/lst/Lst.scala b/tests/run/lst/Lst.scala index 1ea1974880e6..3f6500d475dc 100644 --- a/tests/run/lst/Lst.scala +++ b/tests/run/lst/Lst.scala @@ -18,6 +18,8 @@ import reflect.ClassTag class Lst[+T](val elems: Any) extends AnyVal { self => import Lst._ + transparent def locally[T](body: => T): T = body + def length: Int = elems match { case null => 0 case elems: Arr => elems.length @@ -27,7 +29,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => def isEmpty = elems == null def nonEmpty = elems != null - transparent def foreach(op: => T => Unit): Unit = { + transparent def foreach(op: => T => Unit): Unit = locally { def sharedOp(x: T) = op(x) elems match { case null => @@ -38,7 +40,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - transparent def foreachReversed(transparent op: T => Unit): Unit = { + transparent def foreachReversed(transparent op: T => Unit): Unit = locally { def sharedOp(x: T) = op(x) elems match { case null => @@ -52,12 +54,14 @@ class Lst[+T](val elems: Any) extends AnyVal { self => /** Like `foreach`, but completely inlines `op`, at the price of generating the code twice. * Should be used only of `op` is small */ - transparent def foreachInlined(op: => T => Unit): Unit = elems match { - case null => - case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] - var i = 0 - while (i < elems.length) { op(elem(i)); i += 1 } - case elem: T @unchecked => op(elem) + transparent def foreachInlined(op: => T => Unit): Unit = locally { + elems match { + case null => + case elems: Arr => def elem(i: Int) = elems(i).asInstanceOf[T] + var i = 0 + while (i < elems.length) { op(elem(i)); i += 1 } + case elem: T @unchecked => op(elem) + } } def iterator(): Iterator[T] = elems match { @@ -101,7 +105,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } /** `f` is pulled out, not duplicated */ - transparent def map[U](f: => T => U): Lst[U] = { + transparent def map[U](f: => T => U): Lst[U] = locally { def op(x: T) = f(x) elems match { case null => Empty @@ -193,7 +197,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => buf.toLst } - transparent def exists(p: => T => Boolean): Boolean = { + transparent def exists(p: => T => Boolean): Boolean = locally { def op(x: T) = p(x) elems match { case null => false @@ -206,7 +210,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => } } - transparent def forall(p: => T => Boolean): Boolean = { + transparent def forall(p: => T => Boolean): Boolean = locally { def op(x: T) = p(x) elems match { case null => true @@ -229,7 +233,7 @@ class Lst[+T](val elems: Any) extends AnyVal { self => elem == x } - transparent def foldLeft[U](z: U)(f: => (U, T) => U) = { + transparent def foldLeft[U](z: U)(f: => (U, T) => U) = locally { def op(x: U, y: T) = f(x, y) elems match { case null => z diff --git a/tests/run/typelevel-defaultValue.check b/tests/run/typelevel-defaultValue.check new file mode 100644 index 0000000000000000000000000000000000000000..44b1d5b6313fd561c8a5d4bec830f5e09b309892 GIT binary patch literal 48 kcmWH}&rQ`Z(BujR(|RygT4GLdDpZs~lglqZFBL)n08vm4x&QzG literal 0 HcmV?d00001 diff --git a/tests/run/typelevel-defaultValue.scala b/tests/run/typelevel-defaultValue.scala new file mode 100644 index 000000000000..47c6b7286dff --- /dev/null +++ b/tests/run/typelevel-defaultValue.scala @@ -0,0 +1,37 @@ + +object Test extends App { + def anyValue[T]: T = ??? + + transparent def defaultValue[T]: Option[Any] = anyValue[T] match { + case _: Byte => Some(0: Byte) + case c: Char => Some(0: Char) + case d @ (_: Short) => Some(0: Short) + case _: Int => Some(0) + case _: Long => Some(0L) + case _: Float => Some(0.0f) + case _: Double => Some(0.0d) + case _: Boolean => Some(false) + case _: Unit => Some(()) + //case _: t >: Null => Some(null) + case _ => None + } + + val dInt = defaultValue[Int] + val dDouble = defaultValue[Double] + val dBoolean = defaultValue[Boolean] + val dChar = defaultValue[Char] + val dString = defaultValue[String] + val dAny = defaultValue[Any] + println(dInt) + println(dDouble) + println(dBoolean) + println(dChar) + println(dString) + println(dAny) + val cInt: Int = dInt.get + val cDouble: Double = dDouble.get + val cBoolean: Boolean = dBoolean.get + val cChar: Char = dChar.get + assert(dString.isEmpty) + assert(dAny.isEmpty) +} \ No newline at end of file From a019a4e4532ae086c2d223186580c898023bb5e4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:49:55 +0200 Subject: [PATCH 24/62] Enforce restrictions on typelevel methods Typelevel methods are transparent methods with toplevel matches. We now track this property with a flag. To be done: merge with the Macro flag. Typelevel methods - cannot be eta-expanded - cannot override anything Also, fixes the "missing args" error message, which previously claimed that `()` was missing even for nonempty argument lists. --- .../backend/jvm/DottyBackendInterface.scala | 2 +- .../src/dotty/tools/dotc/core/Flags.scala | 4 ++-- .../tools/dotc/core/SymDenotations.scala | 2 +- .../tools/dotc/core/tasty/TastyFormat.scala | 7 ++++-- .../tools/dotc/core/tasty/TreePickler.scala | 1 + .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 + .../core/unpickleScala2/PickleBuffer.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 11 +++------- .../tools/dotc/typer/PrepareTransparent.scala | 13 +++++++++++ .../dotty/tools/dotc/typer/RefChecks.scala | 4 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 6 ++++- tests/neg/typelevel-noeta.scala | 22 +++++++++++++++++++ ...ultValue.scala => typelevel-nomatch.scala} | 2 ++ tests/neg/typelevel.scala | 9 -------- 14 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 tests/neg/typelevel-noeta.scala rename tests/neg/{typelevel-defaultValue.scala => typelevel-nomatch.scala} (95%) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 45ac2570e643..e7a64880dd61 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -433,7 +433,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val Flag_METHOD: Flags = Flags.Method.bits val ExcludedForwarderFlags: Flags = { Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | - Flags.Bridge | Flags.VBridge | Flags.Private | Flags.Macro + Flags.Bridge | Flags.Private | Flags.TypeLevel | Flags.Macro }.bits def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 540b9cbe373f..69afad6c7cb7 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -351,8 +351,8 @@ object Flags { /** A bridge method. Set by Erasure */ final val Bridge = termFlag(34, "") - /** Symbol is a Java varargs bridge */ // (needed?) - final val VBridge = termFlag(35, "") // TODO remove + /** Symbol is a typelevel method, cannot be used in normal code */ + final val TypeLevel = termFlag(35, "") /** Symbol is a method which should be marked ACC_SYNCHRONIZED */ final val Synchronized = termFlag(36, "") diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index c1ab3f9d3cc2..bf7edbe97499 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -600,7 +600,7 @@ object SymDenotations { /** Is this a denotation of a stable term (or an arbitrary type)? */ final def isStable(implicit ctx: Context) = - isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType]) + isType || is(Stable) || !is(UnstableValue) && !info.isInstanceOf[ExprType] /** Is this a denotation of a class that does not have - either direct or inherited - * initaliazion code? diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 5d948e1a1948..58d23810340c 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -182,6 +182,7 @@ Standard-Section: "ASTs" TopLevelStat* LAZY OVERRIDE TRANSPARENT + TYPELEVEL // transparent method containing toplevel matches (TODO: merge with MACRO) MACRO // transparent method containing toplevel splices STATIC // mapped to static Java member OBJECT // an object or its class @@ -295,8 +296,8 @@ object TastyFormat { final val IMPLICIT = 13 final val LAZY = 14 final val OVERRIDE = 15 - - final val TRANSPARENT = 17 + final val TRANSPARENT = 16 + final val TYPELEVEL = 17 final val STATIC = 18 final val OBJECT = 19 final val TRAIT = 20 @@ -473,6 +474,7 @@ object TastyFormat { | LAZY | OVERRIDE | TRANSPARENT + | TYPELEVEL | MACRO | STATIC | OBJECT @@ -529,6 +531,7 @@ object TastyFormat { case LAZY => "LAZY" case OVERRIDE => "OVERRIDE" case TRANSPARENT => "TRANSPARENT" + case TYPELEVEL => "TYPELEVEL" case MACRO => "MACRO" case STATIC => "STATIC" case OBJECT => "OBJECT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 2a2cc70df635..07780be6cf27 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -601,6 +601,7 @@ class TreePickler(pickler: TastyPickler) { if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) if (flags is Transparent) writeByte(TRANSPARENT) + if (flags is TypeLevel) writeByte(TYPELEVEL) if (flags is Macro) writeByte(MACRO) if (flags is JavaStatic) writeByte(STATIC) if (flags is Module) writeByte(OBJECT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 40405e9194f8..a3728b9e61c5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -585,6 +585,7 @@ class TreeUnpickler(reader: TastyReader, case LAZY => addFlag(Lazy) case OVERRIDE => addFlag(Override) case TRANSPARENT => addFlag(Transparent) + case TYPELEVEL => addFlag(TypeLevel) case MACRO => addFlag(Macro) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala index 92baaab565e6..31f05cf71f4e 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/PickleBuffer.scala @@ -232,7 +232,7 @@ object PickleBuffer { EXPANDEDNAME -> Scala2ExpandedName, IMPLCLASS -> (Scala2PreSuper, ImplClass), SPECIALIZED -> Specialized, - VBRIDGE -> VBridge, + VBRIDGE -> EmptyFlags, VARARGS -> JavaVarargs, ENUM -> Enum) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d1878b3a405f..dfb3aa3c4fc8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -19,6 +19,7 @@ import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName, Tran import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Annotations._ +import PrepareTransparent.foreachTopLevelMatch import transform.{ExplicitOuter, AccessProxies} import Inferencing.fullyDefinedType import config.Printers.inlining @@ -641,14 +642,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { import reducer._ /** Mark all toplevel matches with the `TopLevelMatch` attachment */ - def prepare(tree: untpd.Tree): Unit = tree match { - case tree: untpd.Match => - tree.pushAttachment(TopLevelMatch, ()) - tree.cases.foreach(prepare) - case tree: untpd.Block => - prepare(tree.expr) - case _ => - } + def prepare(tree: untpd.Tree): Unit = + foreachTopLevelMatch(tree, _.pushAttachment(TopLevelMatch, ())) override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 75068e259c7f..7318e9c60f1e 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -43,6 +43,18 @@ object PrepareTransparent { if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) methPart(tree).putAttachment(ContextualImplicit, ()) + def foreachTopLevelMatch(tree: untpd.Tree, op: untpd.Tree => Unit) = { + def recur(tree: untpd.Tree): Unit = tree match { + case tree: untpd.Match => + op(tree) + tree.cases.foreach(recur) + case tree: untpd.Block => + recur(tree.expr) + case _ => + } + recur(tree) + } + class InlineAccessors extends AccessProxies { /** If an inline accessor name wraps a unique inline name, this is taken as indication @@ -241,6 +253,7 @@ object PrepareTransparent { val typedBody = if (ctx.reporter.hasErrors) rawBody else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) + foreachTopLevelMatch(originalBody, _ => inlined.setFlag(TypeLevel)) val inlineableBody = addReferences(inlined, originalBody, typedBody) inlining.println(i"Body to inline for $inlined: $inlineableBody") inlineableBody diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index d6ea4b9d1e29..95a7b5b917ff 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -143,7 +143,7 @@ object RefChecks { * 1.8.1 M's type is a subtype of O's type, or * 1.8.2 M is of type []S, O is of type ()T and S <: T, or * 1.8.3 M is of type ()S, O is of type []T and S <: T, or - * 1.9 M must not be a Dotty macro def + * 1.9 M must not be a typelevel def or a Dotty macro def * 1.10. If M is a 2.x macro def, O cannot be deferred unless there's a concrete method overriding O. * 1.11. If M is not a macro def, O cannot be a macro def. * 2. Check that only abstract classes have deferred members @@ -376,6 +376,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") + } else if (member.is(TypeLevel)) { // (1.9) + overrideError("is a type-level method, may not override anything") } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) overrideError("is a macro, may not override anything") } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3bcd4de5e5ce..29366422f9f4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2128,7 +2128,9 @@ class Typer extends Namer def readaptSimplified(tree: Tree)(implicit ctx: Context) = readapt(simplify(tree, pt, locked)) def missingArgs(mt: MethodType) = { - ctx.error(MissingEmptyArgumentList(methPart(tree).symbol), tree.pos) + val meth = methPart(tree).symbol + if (mt.paramNames.length == 0) ctx.error(MissingEmptyArgumentList(meth), tree.pos) + else ctx.error(em"missing arguments for $meth", tree.pos) tree.withType(mt.resultType) } @@ -2338,10 +2340,12 @@ class Typer extends Namer // Reasons NOT to eta expand: // - we reference a constructor + // - we reference a typelevel method // - we are in a pattern // - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that) if (arity >= 0 && !tree.symbol.isConstructor && + !tree.symbol.is(TypeLevel) && !ctx.mode.is(Mode.Pattern) && !(isSyntheticApply(tree) && !isExpandableApply)) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) diff --git a/tests/neg/typelevel-noeta.scala b/tests/neg/typelevel-noeta.scala new file mode 100644 index 000000000000..a858ac169fae --- /dev/null +++ b/tests/neg/typelevel-noeta.scala @@ -0,0 +1,22 @@ + +object Test { + def anyValue[T]: T = ??? + + transparent def test(x: Int) = x match { + case _: Byte => + case _: Char => + } + + transparent def test2() = 1 match { + case _: Byte => + case _: Char => + } + + val x: Any = test // error + + test // error + + val x2: Any = test2 // error + + test2 // error +} \ No newline at end of file diff --git a/tests/neg/typelevel-defaultValue.scala b/tests/neg/typelevel-nomatch.scala similarity index 95% rename from tests/neg/typelevel-defaultValue.scala rename to tests/neg/typelevel-nomatch.scala index 97b347e39412..a4dc222336f5 100644 --- a/tests/neg/typelevel-defaultValue.scala +++ b/tests/neg/typelevel-nomatch.scala @@ -8,4 +8,6 @@ object Test { } test[String] + + test } \ No newline at end of file diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala index f35bc2f7f651..f75c425a4e9d 100644 --- a/tests/neg/typelevel.scala +++ b/tests/neg/typelevel.scala @@ -55,13 +55,4 @@ object Test { class Deco2(val as: HList) extends java.lang.Cloneable with java.lang.Comparable[Deco2] { transparent def ++ (bs: HList) = concat(as, bs) } - - def anyValue[T]: T = ??? - - transparent def test[T] = anyValue[T] match { - case _: Byte => - case _: Char => - } - - test[String] } \ No newline at end of file From 31f6db7d796509f9e2d3989d8a61597ec0ee8545 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:50:26 +0200 Subject: [PATCH 25/62] Match bindings should go with inlined body ... or they won't pick up the right position for errors. I.e. the fact tyat they are part of an Inlined call would be lost for them. --- .../src/dotty/tools/dotc/typer/Inliner.scala | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index dfb3aa3c4fc8..9439f31f0482 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -158,7 +158,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { private val thisProxy = new mutable.HashMap[ClassSymbol, TermRef] /** A buffer for bindings that define proxies for actual arguments */ - val bindingsBuf = new mutable.ListBuffer[MemberDef] + private val bindingsBuf = new mutable.ListBuffer[MemberDef] private def newSym(name: Name, flags: FlagSet, info: Type): Symbol = ctx.newSymbol(ctx.owner, name, flags, info, coord = call.pos) @@ -357,10 +357,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { trace(i"inlining $call", inlining, show = true) { - // Normalize all bindings in `bindingsBuf` - val rawBindings = bindingsBuf.toList - bindingsBuf.clear() - for (binding <- rawBindings) bindingsBuf += reducer.normalizeBinding(binding)(inlineCtx) + // The normalized bindings collected in `bindingsBuf` + bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx)) // Identifiy all toplevel matches inlineTyper.prepare(expansion) @@ -375,15 +373,22 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } // Drop unused bindings - val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList, expansion1) + val matchBindings = reducer.matchBindingsBuf.toList + val (finalBindings, finalExpansion) = dropUnusedDefs(bindingsBuf.toList ++ matchBindings, expansion1) + val (finalMatchBindings, finalArgBindings) = finalBindings.partition(matchBindings.contains(_)) - tpd.Inlined(call, finalBindings, finalExpansion) + // Take care that only argument bindings go into `bindings`, since positions are + // different for bindings from arguments and bindings from body. + tpd.Inlined(call, finalArgBindings, seq(finalMatchBindings, finalExpansion)) } } /** A utility object offering methods for rewriting inlined code */ object reducer { + /** A dditional bidnings established by reducing match expressions */ + val matchBindingsBuf = new mutable.ListBuffer[MemberDef] + /** An extractor for terms equivalent to `new C(args)`, returning the class `C` * and the arguments `args`. Can see inside blocks and Inlined nodes and can * follow a reference to an inline value binding to its right hand side. @@ -482,9 +487,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ private object InlineableArg { lazy val paramProxies = paramProxy.values.toSet - def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = + def unapply(tree: Trees.Ident[_])(implicit ctx: Context): Option[Tree] = { + def search(buf: mutable.ListBuffer[MemberDef]) = buf.find(_.name == tree.name) if (paramProxies.contains(tree.typeOpt)) - bindingsBuf.find(_.name == tree.name) match { + search(bindingsBuf).orElse(search(matchBindingsBuf)) match { case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) case Some(ddef: DefDef) => @@ -492,6 +498,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => None } else None + } } object ConstantValue { @@ -682,7 +689,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { reduceTopLevelMatch(sel, selType, tree.cases, this) match { case Some((caseBindings, rhs)) => for (binding <- caseBindings) { - bindingsBuf += binding + matchBindingsBuf += binding ctx.enter(binding.symbol) } typedExpr(rhs, pt) From 7f48ed10fce96cab05614e6c0a84f5c5ba1c0156 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:51:04 +0200 Subject: [PATCH 26/62] Refinements to erased 1. Count erased functions as stable Since they are never applied at runtime, they cannot have side effects! 2. Fixes to erased value checking Several updates to the checkNotErased check that verifiy that no erased values survive to PostTyper. - we missed a case where the check should be done before - checks should be disabled in transparent methods, since method expansion might remove the erased value - checks need to take inline position into account 3. Remove purity assumption for scrutinees of toplevel matches Toplevel match scrutinees are no longer assumed to be pure. This removes a special case. We can still typecheck typelevel-default value by declaring a method as `erased` (which renders the call to it pure). --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 6 ------ compiler/src/dotty/tools/dotc/core/Flags.scala | 3 +++ .../dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/core/Symbols.scala | 2 ++ compiler/src/dotty/tools/dotc/core/TypeOps.scala | 3 +++ .../dotty/tools/dotc/transform/PostTyper.scala | 5 +++-- .../src/dotty/tools/dotc/typer/Checking.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 5 ++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 1 - docs/docs/typelevel.md | 12 +++++------- tests/neg/transparent-override/A_1.scala | 7 +++++++ tests/neg/transparent-override/B_2.scala | 9 +++++++++ tests/neg/typelevel-erased-leak.scala | 15 +++++++++++++++ tests/run/quote-unrolled-foreach/quoted_1.scala | 2 +- tests/run/typelevel-defaultValue.scala | 7 +++++-- 15 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 tests/neg/transparent-override/A_1.scala create mode 100644 tests/neg/transparent-override/B_2.scala create mode 100644 tests/neg/typelevel-erased-leak.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 6ea974ef4973..59a65ba2f5ef 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -391,12 +391,6 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => SimplyPure case TypeApply(fn, _) => exprPurity(fn) -/* - * Not sure we'll need that. Comment out until we find out - case Apply(Select(free @ Ident(_), nme.apply), _) if free.symbol.name endsWith nme.REIFY_FREE_VALUE_SUFFIX => - // see a detailed explanation of this trick in `GenSymbols.reifyFreeTerm` - free.symbol.hasStableFlag && isIdempotentExpr(free) -*/ case Apply(fn, args) => def isKnownPureOp(sym: Symbol) = sym.owner.isPrimitiveValueClass || sym.owner == defn.StringClass diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 69afad6c7cb7..638ae24f60a1 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -548,6 +548,9 @@ object Flags { /** Either method or lazy or deferred */ final val MethodOrLazyOrDeferred = Method | Lazy | Deferred + /** Assumed to be pure */ + final val StableOrErased = Stable | Erased + /** Labeled `private`, `final`, or `transparent` */ final val EffectivelyFinal = Private | Final | Transparent diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index bf7edbe97499..ddffa5ede5ca 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -600,7 +600,7 @@ object SymDenotations { /** Is this a denotation of a stable term (or an arbitrary type)? */ final def isStable(implicit ctx: Context) = - isType || is(Stable) || !is(UnstableValue) && !info.isInstanceOf[ExprType] + isType || is(StableOrErased) || !is(UnstableValue) && !info.isInstanceOf[ExprType] /** Is this a denotation of a class that does not have - either direct or inherited - * initaliazion code? diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 40ff5b0dfb19..e16145d5b048 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -397,6 +397,8 @@ trait Symbols { this: Context => def requiredMethod(path: PreName): TermSymbol = base.staticRef(path.toTermName).requiredSymbol(_ is Method).asTerm + + def requiredMethodRef(path: PreName): TermRef = requiredMethod(path).termRef } object Symbols { diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 05fd66b54282..ec3343d83884 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -271,6 +271,9 @@ trait TypeOps { this: Context => // TODO: Make standalone object. violations.toList } + /** Are we in a transparent method body */ + def inTransparentMethod = owner.ownersIterator.exists(_.isTransparentMethod) + /** Is `feature` enabled in class `owner`? * This is the case if one of the following two alternatives holds: * diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0781f958e375..438a85686f68 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -212,6 +212,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case sel: Select => val args1 = transform(args) val sel1 = transformSelect(sel, args1) + checkNotErased(sel) cpy.TypeApply(tree1)(sel1, args1) case _ => super.transform(tree1) @@ -233,7 +234,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val callTrace = if (call.symbol.is(Macro)) call else Ident(call.symbol.topLevelClass.typeRef).withPos(call.pos) - cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)) + cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) case tree: Template => withNoCheckNews(tree.parents.flatMap(newPart)) { val templ1 = paramFwd.forwardParamAccessors(tree) @@ -318,7 +319,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } private def checkNotErased(tree: RefTree)(implicit ctx: Context): Unit = { - if (tree.symbol.is(Erased) && !ctx.mode.is(Mode.Type)) { + if (tree.symbol.is(Erased) && !ctx.mode.is(Mode.Type) && !ctx.inTransparentMethod) { val msg = if (tree.symbol.is(CaseAccessor)) "First parameter list of case class may not contain `erased` parameters" else i"${tree.symbol} is declared as erased, but is in fact used" diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index b3ada92ca8e8..f939eb262df4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -675,7 +675,7 @@ trait Checking { tree.tpe.widenTermRefExpr match { case tp: ConstantType if exprPurity(tree) >= purityLevel => // ok case _ => - val allow = ctx.erasedTypes || ctx.owner.ownersIterator.exists(_.isTransparentMethod) + val allow = ctx.erasedTypes || ctx.inTransparentMethod if (!allow) ctx.error(em"$what must be a constant expression", tree.pos) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 9439f31f0482..44a192770a73 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -63,7 +63,7 @@ object Inliner { def isInlineable(meth: Symbol)(implicit ctx: Context): Boolean = { def suppressInline = - ctx.owner.ownersIterator.exists(_.isTransparentMethod) || + ctx.inTransparentMethod || ctx.settings.YnoInline.value || ctx.isAfterTyper || ctx.reporter.hasErrors @@ -720,8 +720,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { val bindingOfSym = newMutableSymbolMap[MemberDef] def isInlineable(binding: MemberDef) = binding match { case DefDef(_, Nil, Nil, _, _) => true - case vdef @ ValDef(_, _, _) => - vdef.name.is(TransparentScrutineeName) || isPureExpr(vdef.rhs) + case vdef @ ValDef(_, _, _) => isPureExpr(vdef.rhs) case _ => false } for (binding <- bindings if isInlineable(binding)) { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 29366422f9f4..abb838dc71fb 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2240,7 +2240,6 @@ class Typer extends Namer if (!wtp.isErasedMethod) args else args.map { arg => arg.tpe match { - case tpe if tpe.isStable => arg case _: AmbiguousImplicits => arg case tpe => defaultValue(tpe) } diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 5f28fff2abb8..7c9e87b3293d 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -177,16 +177,15 @@ So the end result of the expansion is the expression `throw new IndexOutOfBounds We have seen so far transparent functions that take terms (tuples and integers) as parameters. What if we want to base case distinctions on types instead? For instance, one would like to be able to write a function `defaultValue`, that, given a type `T` returns optionally the default value of `T`, if it exists. In fact, we can already express -this using ordinary match expressions and a simple helper function, `scala.typelevel.anyValue`, which is defined as follows: +this using ordinary match expressions and a simple helper function, `scala.typelevel.erasedValue`, which is defined as follows: ```scala -def anyValue[T]: T = ??? +erased def erasedValue[T]: T = ??? ``` -The `anyValue` function _pretends_ to return a value of its type argument `T`. In fact, it will always raise a `NotImplementedError` exception instead. -This is OK, since the function is intended to be used only on the type-level; it should never be executed at run-time. +The `erasedValue` function _pretends_ to return a value of its type argument `T`. In fact, it would always raise a `NotImplementedError` exception when called. But the function can in fact never be called, since it is declared `erased`, so can be only used a compile-time during type checking. -Using `anyValue`, we can then define `defaultValue` as follows: +Using `erasedValue`, we can then define `defaultValue` as follows: ```scala -transparent def defaultValue[T]: Option[T] = anyValue[T] match { +transparent def defaultValue[T]: Option[T] = erasedValue[T] match { case _: Byte => Some(0: Byte) case _: Char => Some(0: Char) case _: Short => Some(0: Short) @@ -207,7 +206,6 @@ defaultValue[Boolean] = Some(false) defaultValue[String | Null] = Some(null) defaultValue[AnyVal] = None ``` - As another example, consider the type-level inverse of `toNat`: given a _type_ representing a Peano number, return the integer _value_ corresponding to it. Here's how this can be defined: ```scala transparent def toInt[N <: Nat]: Int = anyValue[N] match { diff --git a/tests/neg/transparent-override/A_1.scala b/tests/neg/transparent-override/A_1.scala new file mode 100644 index 000000000000..d80fc2a5462d --- /dev/null +++ b/tests/neg/transparent-override/A_1.scala @@ -0,0 +1,7 @@ +abstract class A { + def f(x: Int): Int + transparent def g(x: Int): Int = x +} + + + diff --git a/tests/neg/transparent-override/B_2.scala b/tests/neg/transparent-override/B_2.scala new file mode 100644 index 000000000000..c4613f91e9e8 --- /dev/null +++ b/tests/neg/transparent-override/B_2.scala @@ -0,0 +1,9 @@ +class B extends A { + transparent def f(x: Int): Int = x match { // error + case 0 => 1 + case _ => x + } + def g(x: Int): Int = 1 // error +} + + diff --git a/tests/neg/typelevel-erased-leak.scala b/tests/neg/typelevel-erased-leak.scala new file mode 100644 index 000000000000..e55a47486d89 --- /dev/null +++ b/tests/neg/typelevel-erased-leak.scala @@ -0,0 +1,15 @@ + +object typelevel { + erased def erasedValue[T]: T = ??? +} + +object Test { + + transparent def test[T] = typelevel.erasedValue[T] match { // error + case b: Byte => b + case c: Char => "A" + } + + test[Byte] + test[Char] // ok +} \ No newline at end of file diff --git a/tests/run/quote-unrolled-foreach/quoted_1.scala b/tests/run/quote-unrolled-foreach/quoted_1.scala index 0f1ac1d3599a..f5a8b5ee5049 100644 --- a/tests/run/quote-unrolled-foreach/quoted_1.scala +++ b/tests/run/quote-unrolled-foreach/quoted_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ object Macro { - inline def unrolledForeach(inline unrollSize: Int, seq: Array[Int])(f: => Int => Unit): Unit = // or f: Int => Unit + transparent def unrolledForeach(transparent unrollSize: Int, seq: Array[Int])(f: => Int => Unit): Unit = // or f: Int => Unit ~unrolledForeachImpl(unrollSize, '(seq), '(f)) private def unrolledForeachImpl(unrollSize: Int, seq: Expr[Array[Int]], f: Expr[Int => Unit]): Expr[Unit] = '{ diff --git a/tests/run/typelevel-defaultValue.scala b/tests/run/typelevel-defaultValue.scala index 47c6b7286dff..c31b363f3b18 100644 --- a/tests/run/typelevel-defaultValue.scala +++ b/tests/run/typelevel-defaultValue.scala @@ -1,8 +1,11 @@ +object typelevel { + erased def erasedValue[T]: T = ??? +} + object Test extends App { - def anyValue[T]: T = ??? - transparent def defaultValue[T]: Option[Any] = anyValue[T] match { + transparent def defaultValue[T]: Option[Any] = typelevel.erasedValue[T] match { case _: Byte => Some(0: Byte) case c: Char => Some(0: Char) case d @ (_: Short) => Some(0: Short) From 19aa394876bb76104dedfa23ed1fd37a0e2c8223 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:51:40 +0200 Subject: [PATCH 27/62] Compute gadt context when reducing matches Need to do the same thing as for regular matches, since it is a prerequisite for typing patterns correctly. --- .../tools/dotc/transform/TreeChecker.scala | 4 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 6 ++- .../src/dotty/tools/dotc/typer/Typer.scala | 54 ++++++++++--------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e1c36b9395c3..47f725deccf1 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -411,9 +411,9 @@ class TreeChecker extends Phase with SymTransformer { } } - override def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = { + override def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = { withPatSyms(tpd.patVars(tree.pat.asInstanceOf[tpd.Tree])) { - super.typedCase(tree, pt, selType, gadtSyms) + super.typedCase(tree, selType, pt, gadtSyms) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 44a192770a73..41e25f6f7f76 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -561,6 +561,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { */ def reduceTopLevelMatch(scrutinee: Tree, scrutType: Type, cases: List[untpd.CaseDef], typer: Typer)(implicit ctx: Context): MatchRedux = { + val gadtSyms = typer.gadtSyms(scrutType) + /** Try to match pattern `pat` against scrutinee reference `scrut`. If succesful add * bindings for variables bound in this pattern to `bindingsBuf`. */ @@ -620,8 +622,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } } val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() += scrutineeBinding - if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, typer.typedPattern(cdef.pat, scrutType)) - && guardOK) + val pat1 = typer.typedPattern(cdef.pat, scrutType)(typer.gadtContext(gadtSyms)) + if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, pat1) && guardOK) Some((caseBindingsBuf.toList, cdef.body)) else None diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index abb838dc71fb..9320d0bf3a18 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -986,39 +986,45 @@ class Typer extends Namer assignType(cpy.Match(tree)(sel, cases1), cases1) } - def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { - - /** gadtSyms = "all type parameters of enclosing methods that appear - * non-variantly in the selector type" todo: should typevars - * which appear with variances +1 and -1 (in different - * places) be considered as well? - */ - val gadtSyms: Set[Symbol] = trace(i"GADT syms of $selType", gadts) { - val accu = new TypeAccumulator[Set[Symbol]] { - def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { - val tsyms1 = t match { - case tr: TypeRef if (tr.symbol is TypeParam) && tr.symbol.owner.isTerm && variance == 0 => - tsyms + tr.symbol - case _ => - tsyms - } - foldOver(tsyms1, t) + /** gadtSyms = "all type parameters of enclosing methods that appear + * non-variantly in the selector type" todo: should typevars + * which appear with variances +1 and -1 (in different + * places) be considered as well? + */ + def gadtSyms(selType: Type)(implicit ctx: Context): Set[Symbol] = trace(i"GADT syms of $selType", gadts) { + val accu = new TypeAccumulator[Set[Symbol]] { + def apply(tsyms: Set[Symbol], t: Type): Set[Symbol] = { + val tsyms1 = t match { + case tr: TypeRef if (tr.symbol is TypeParam) && tr.symbol.owner.isTerm && variance == 0 => + tsyms + tr.symbol + case _ => + tsyms } + foldOver(tsyms1, t) } - accu(Set.empty, selType) } - - cases.mapconserve(typedCase(_, pt, selType, gadtSyms)) + accu(Set.empty, selType) } - /** Type a case. */ - def typedCase(tree: untpd.CaseDef, pt: Type, selType: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") { - val originalCtx = ctx - + /** Context with fresh GADT bounds for all gadtSyms */ + def gadtContext(gadtSyms: Set[Symbol])(implicit ctx: Context) = { val gadtCtx = ctx.fresh.setFreshGADTBounds for (sym <- gadtSyms) if (!gadtCtx.gadt.bounds.contains(sym)) gadtCtx.gadt.setBounds(sym, TypeBounds.empty) + gadtCtx + } + + def typedCases(cases: List[untpd.CaseDef], selType: Type, pt: Type)(implicit ctx: Context) = { + val gadts = gadtSyms(selType) + cases.mapconserve(typedCase(_, selType, pt, gadts)) + } + + /** Type a case. */ + def typedCase(tree: untpd.CaseDef, selType: Type, pt: Type, gadtSyms: Set[Symbol])(implicit ctx: Context): CaseDef = track("typedCase") { + val originalCtx = ctx + + val gadtCtx = gadtContext(gadtSyms) /** - strip all instantiated TypeVars from pattern types. * run/reducable.scala is a test case that shows stripping typevars is necessary. From dd29dc686a062e1bfe4957069e1d6a3b6ceac7bf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:52:20 +0200 Subject: [PATCH 28/62] Wait until PostTyper before eliminating erased arguments Wait until PostTyper before eliminating erased arguments and right-hand sides. The way it was done in Typer before, an erased call would be eliminated before we could inline it. Doing everything in PostTyper also improves locality because it is there where we check that no erased values remain. --- .../tools/dotc/transform/PostTyper.scala | 29 +++-- .../dotty/tools/dotc/typer/Applications.scala | 12 +- .../src/dotty/tools/dotc/typer/Typer.scala | 15 +-- tests/run/typelevel-patmat.check | 3 + tests/run/typelevel-patmat.scala | 116 ++++++++++++++++++ 5 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 tests/run/typelevel-patmat.check create mode 100644 tests/run/typelevel-patmat.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 438a85686f68..f333dbe541a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -193,14 +193,19 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else transformSelect(tree, Nil) case tree: Apply => - methPart(tree) match { + val app = + if (tree.fun.tpe.widen.isErasedMethod) + tpd.cpy.Apply(tree)(tree.fun, tree.args.map(arg => defaultValue(arg.tpe))) + else + tree + methPart(app) match { case Select(nu: New, nme.CONSTRUCTOR) if isCheckable(nu) => // need to check instantiability here, because the type of the New itself // might be a type constructor. Checking.checkInstantiable(tree.tpe, nu.pos) - withNoCheckNews(nu :: Nil)(super.transform(tree)) + withNoCheckNews(nu :: Nil)(super.transform(app)) case _ => - super.transform(tree) + super.transform(app) } case tree: TypeApply => val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree) @@ -242,9 +247,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase superAcc.wrapTemplate(templ1)( super.transform(_).asInstanceOf[Template])) } + case tree: ValDef => + val tree1 = cpy.ValDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + transformMemberDef(tree1) + super.transform(tree1) case tree: DefDef => - transformMemberDef(tree) - superAcc.wrapDefDef(tree)(super.transform(tree).asInstanceOf[DefDef]) + val tree1 = cpy.DefDef(tree)(rhs = normalizeErasedRhs(tree.rhs, tree.symbol)) + transformMemberDef(tree1) + superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]) case tree: TypeDef => transformMemberDef(tree) val sym = tree.symbol @@ -257,9 +267,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase tree } super.transform(tree) - case tree: MemberDef => - transformMemberDef(tree) - super.transform(tree) case tree: New if isCheckable(tree) => Checking.checkInstantiable(tree.tpe, tree.pos) super.transform(tree) @@ -318,6 +325,12 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase throw ex } + /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. + * Performed to shrink the tree that is known to be erased later. + */ + private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = + if (sym.is(Erased) && rhs.tpe.exists) defaultValue(rhs.tpe) else rhs + private def checkNotErased(tree: RefTree)(implicit ctx: Context): Unit = { if (tree.symbol.is(Erased) && !ctx.mode.is(Mode.Type) && !ctx.inTransparentMethod) { val msg = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 46fc35eac331..0008aff5d044 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -797,11 +797,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) case _ => } - app match { - case Apply(fun, args) if fun.tpe.widen.isErasedMethod => - tpd.cpy.Apply(app)(fun = fun, args = args.map(arg => defaultValue(arg.tpe))) - case _ => app - } + app } } @@ -1567,12 +1563,6 @@ trait Applications extends Compatibility { self: Typer with Dynamic => harmonizedElems } - /** Transforms the rhs tree into a its default tree if it is in an `erased` val/def. - * Performed to shrink the tree that is known to be erased later. - */ - protected def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = - if (sym.is(Erased) && rhs.tpe.exists) defaultValue(rhs.tpe) else rhs - /** If all `types` are numeric value types, and they are not all the same type, * pick a common numeric supertype and widen any constant types in `tpes` to it. * If the resulting types are all the same, return them instead of the original ones. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9320d0bf3a18..69ed6c75f4b4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1384,7 +1384,7 @@ class Typer extends Namer val tpt1 = checkSimpleKinded(typedType(tpt)) val rhs1 = vdef.rhs match { case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe - case rhs => normalizeErasedRhs(typedExpr(rhs, tpt1.tpe), sym) + case rhs => typedExpr(rhs, tpt1.tpe) } val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) if (sym.is(Transparent, butNot = DeferredOrTermParamOrAccessor)) @@ -1448,7 +1448,7 @@ class Typer extends Namer rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) - val rhs1 = normalizeErasedRhs(typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx), sym) + val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) if (sym.isTransparentMethod) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) @@ -2242,16 +2242,7 @@ class Typer extends Namer arg :: implicitArgs(formals1) } } - def eraseErasedArgs(args: List[Tree]): List[Tree] = { - if (!wtp.isErasedMethod) args - else args.map { arg => - arg.tpe match { - case _: AmbiguousImplicits => arg - case tpe => defaultValue(tpe) - } - } - } - val args = eraseErasedArgs(implicitArgs(wtp.paramInfos)) + val args = implicitArgs(wtp.paramInfos) def propagatedFailure(args: List[Tree]): Type = args match { case arg :: args1 => diff --git a/tests/run/typelevel-patmat.check b/tests/run/typelevel-patmat.check new file mode 100644 index 000000000000..295138eb77dc --- /dev/null +++ b/tests/run/typelevel-patmat.check @@ -0,0 +1,3 @@ +Typed(Z) +Typed(S(Z)) +Typed(S(S(Z))) diff --git a/tests/run/typelevel-patmat.scala b/tests/run/typelevel-patmat.scala new file mode 100644 index 000000000000..f64019831d49 --- /dev/null +++ b/tests/run/typelevel-patmat.scala @@ -0,0 +1,116 @@ + +object typelevel { + case class Typed[T](value: T) { type Type = T } +} + +trait Nat { + def toInt: Int +} + +case object Z extends Nat { + transparent def toInt = 0 +} + +case class S[N <: Nat](n: N) extends Nat { + transparent def toInt = n.toInt + 1 +} + +trait HList + +// () +case object HNil extends HList +// (H, T) + +@annotation.showAsInfix(true) +case class HCons [H, T <: HList](hd: H, tl: T) extends HList + +object Test extends App { + import typelevel._ + type HNil = HNil.type + type Z = Z.type + + transparent def ToNat(transparent n: Int): Typed[Nat] = + if n == 0 then Typed(Z) + else Typed(S(ToNat(n - 1).value)) + + val x0 = ToNat(0) + val y0: Z = x0.value + val x1 = ToNat(1) + val y1: S[Z] = x1.value + val x2 = ToNat(2) + val y2: S[S[Z]] = x2.value + println(x0) + println(x1) + println(x2) + + transparent def toInt(n: Nat): Int = n match { + case Z => 0 + case S(n1) => toInt(n1) + 1 + } + + transparent val i0 = toInt(y0) + val j0: 0 = i0 + transparent val i2 = toInt(y2) + val j2: 2 = i2 + + transparent def concat(xs: HList, ys: HList): HList = xs match { + case HNil => ys + case HCons(x, xs1) => HCons(x, concat(xs1, ys)) + } + + val xs = HCons(1, HCons("a", HNil)) + + val r0 = concat(HNil, HNil) + val c0: HNil = r0 + val r1 = concat(HNil, xs) + val c1: HCons[Int, HCons[String, HNil]] = r1 + val r2 = concat(xs, HNil) + val c2: HCons[Int, HCons[String, HNil]] = r2 + val r3 = concat(xs, xs) + val c3: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = r3 + + val r4 = concat(HNil, HCons(1, HCons("a", HNil))) + val c4: HCons[Int, HCons[String, HNil]] = r4 + val r5 = concat(HCons(1, HCons("a", HNil)) , HNil) + val c5: HCons[Int, HCons[String, HNil]] = r5 + val r6 = concat(HCons(1, HCons("a", HNil)), HCons(true, HCons(1.0, HNil))) + val c6: HCons[Int, HCons[String, HCons[Boolean, HCons[Double, HNil]]]] = r6 + + transparent def nth(xs: HList, n: Int): Any = xs match { + case HCons(x, _) if n == 0 => x + case HCons(_, xs1) if n > 0 => nth(xs1, n - 1) + } + + val e0 = nth(r2, 0) + val ce0: Int = e0 + val e1 = nth(r2, 1) + val ce1: String = e1 + + transparent def concatTyped(xs: HList, ys: HList): Typed[_ <: HList] = xs match { + case HNil => Typed(ys) + case HCons(x, xs1) => Typed(HCons(x, concatTyped(xs1, ys).value)) + } + + def concatImpl(xs: HList, ys: HList): HList = xs match { + case HNil => ys + case HCons(x, xs1) => HCons(x, concatImpl(xs1, ys)) + } + + transparent def concatErased(xs: HList, ys: HList): HList = { + erased val r = concatTyped(xs, ys) + concatImpl(xs, ys).asInstanceOf[r.Type] + } + + { + val r0 = concatErased(HNil, HNil) + val c0: HNil = r0 + val r1 = concatErased(HNil, xs) + val c1: HCons[Int, HCons[String, HNil]] = r1 + val r2 = concatErased(xs, HNil) + val c2: HCons[Int, HCons[String, HNil]] = r2 + val r3 = concatErased(xs, xs) + val c3: HCons[Int, HCons[String, HCons[Int, HCons[String, HNil]]]] = r3 + } + + +} \ No newline at end of file From 25a2cc9957f8d2bf063d1efde8c99ce1b17b3e46 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:52:54 +0200 Subject: [PATCH 29/62] Fix return type of defaultValue in typelevel.md The declared return type of `defaultValue` has to be left off or be kept weaker (e.g. `Option[Any]`) since GADT matching is currently not good enough to infer `Option[T]`. But this does not affect what we can do in the expansions. --- docs/docs/typelevel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 7c9e87b3293d..3ad7a9d1ee35 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -185,7 +185,7 @@ The `erasedValue` function _pretends_ to return a value of its type argument `T` Using `erasedValue`, we can then define `defaultValue` as follows: ```scala -transparent def defaultValue[T]: Option[T] = erasedValue[T] match { +transparent def defaultValue[T] = erasedValue[T] match { case _: Byte => Some(0: Byte) case _: Char => Some(0: Char) case _: Short => Some(0: Short) From 1f12ce2df21e93cc74877f589699982b144911cb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:53:22 +0200 Subject: [PATCH 30/62] Reorganize labeling of toplevel matches Identify toplevel matches at definition as well as use site --- .../src/dotty/tools/dotc/typer/Inliner.scala | 13 +---------- .../tools/dotc/typer/PrepareTransparent.scala | 22 +++++++++---------- .../src/dotty/tools/dotc/typer/Typer.scala | 5 ++++- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 41e25f6f7f76..f163b6f9483e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -19,7 +19,6 @@ import NameKinds.{ClassifiedNameKind, InlineAccessorName, UniqueInlineName, Tran import ProtoTypes.selectionProto import SymDenotations.SymDenotation import Annotations._ -import PrepareTransparent.foreachTopLevelMatch import transform.{ExplicitOuter, AccessProxies} import Inferencing.fullyDefinedType import config.Printers.inlining @@ -35,9 +34,6 @@ import ast.TreeInfo object Inliner { import tpd._ - /** An attachment labeling a toplevel match node of a transparent function */ - private val TopLevelMatch = new Property.Key[Unit] - /** A key to be used in a context property that provides a map from enclosing implicit * value bindings to their right hand sides. */ @@ -360,9 +356,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { // The normalized bindings collected in `bindingsBuf` bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx)) - // Identifiy all toplevel matches - inlineTyper.prepare(expansion) - // Run a typing pass over the inlined tree. See InlineTyper for details. val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx) @@ -650,10 +643,6 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { class InlineTyper extends Typer { import reducer._ - /** Mark all toplevel matches with the `TopLevelMatch` attachment */ - def prepare(tree: untpd.Tree): Unit = - foreachTopLevelMatch(tree, _.pushAttachment(TopLevelMatch, ())) - override def ensureAccessible(tpe: Type, superAccess: Boolean, pos: Position)(implicit ctx: Context): Type = { tpe match { case tpe: NamedType if tpe.symbol.exists && !tpe.symbol.isAccessibleFrom(tpe.prefix, superAccess) => @@ -686,7 +675,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } override def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context) = - tree.removeAttachment(TopLevelMatch) match { + tree.getAttachment(PrepareTransparent.TopLevelMatch) match { case Some(_) => reduceTopLevelMatch(sel, selType, tree.cases, this) match { case Some((caseBindings, rhs)) => diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 7318e9c60f1e..1c75fc23d2d3 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -39,20 +39,21 @@ object PrepareTransparent { */ private val ContextualImplicit = new Property.StickyKey[Unit] + /** An attachment labeling a toplevel match node of a transparent function */ + val TopLevelMatch = new Property.StickyKey[Unit] + def markContextualImplicit(tree: Tree)(implicit ctx: Context): Unit = if (!defn.ScalaPredefModule.moduleClass.derivesFrom(tree.symbol.maybeOwner)) methPart(tree).putAttachment(ContextualImplicit, ()) - def foreachTopLevelMatch(tree: untpd.Tree, op: untpd.Tree => Unit) = { - def recur(tree: untpd.Tree): Unit = tree match { - case tree: untpd.Match => - op(tree) - tree.cases.foreach(recur) - case tree: untpd.Block => - recur(tree.expr) - case _ => - } - recur(tree) + def markTopLevelMatches(meth: Symbol, tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { + case tree: untpd.Match => + meth.setFlag(TypeLevel) + tree.putAttachment(TopLevelMatch, ()) + tree.cases.foreach(markTopLevelMatches(meth, _)) + case tree: untpd.Block => + markTopLevelMatches(meth, tree.expr) + case _ => } class InlineAccessors extends AccessProxies { @@ -253,7 +254,6 @@ object PrepareTransparent { val typedBody = if (ctx.reporter.hasErrors) rawBody else ctx.compilationUnit.inlineAccessors.makeInlineable(rawBody) - foreachTopLevelMatch(originalBody, _ => inlined.setFlag(TypeLevel)) val inlineableBody = addReferences(inlined, originalBody, typedBody) inlining.println(i"Body to inline for $inlined: $inlineableBody") inlineableBody diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 69ed6c75f4b4..594b861ac69d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1447,7 +1447,10 @@ class Typer extends Namer (tparams1, sym.owner.typeParams).zipped.foreach ((tdef, tparam) => rhsCtx.gadt.setBounds(tdef.symbol, TypeAlias(tparam.typeRef))) } - if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + if (sym.isTransparentMethod) { + rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + PrepareTransparent.markTopLevelMatches(sym, ddef.rhs) + } val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) if (sym.isTransparentMethod) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) From 33e60bcb550f2ad9a4bd797eac81e81d4ab0d75e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 14:54:10 +0200 Subject: [PATCH 31/62] Implement implicit match expressions --- compiler/src/dotty/tools/dotc/ast/Trees.scala | 5 ++ .../dotty/tools/dotc/core/Definitions.scala | 4 ++ .../dotty/tools/dotc/parsing/Parsers.scala | 60 +++++++++++++++---- .../tools/dotc/printing/RefinedPrinter.scala | 2 +- .../tools/dotc/transform/FirstTransform.scala | 11 ++-- .../src/dotty/tools/dotc/typer/Inliner.scala | 34 +++++++++-- .../src/dotty/tools/dotc/typer/Typer.scala | 7 ++- docs/docs/internals/syntax.md | 3 + docs/docs/typelevel.md | 28 +++------ tests/neg/implicitMatch-ambiguous.scala | 12 ++++ tests/neg/implicitMatch-syntax.scala | 26 ++++++++ tests/run/implicitMatch.check | 1 + tests/run/implicitMatch.scala | 26 ++++++++ 13 files changed, 176 insertions(+), 43 deletions(-) create mode 100644 tests/neg/implicitMatch-ambiguous.scala create mode 100644 tests/neg/implicitMatch-syntax.scala create mode 100644 tests/run/implicitMatch.check create mode 100644 tests/run/implicitMatch.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index bcb7d0171098..1213661673b2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -923,6 +923,11 @@ object Trees { case ys => Thicket(ys) } + /** Extractor for the synthetic scrutinee tree of an implicit match */ + object ImplicitScrutinee { + def apply() = Ident(nme.IMPLICITkw) + def unapply(id: Ident): Boolean = id.name == nme.IMPLICITkw && !id.isInstanceOf[BackquotedIdent] + } // ----- Helper classes for copying, transforming, accumulating ----------------- val cpy: TreeCopier diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e920cce39ba1..c5a58b820111 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -333,6 +333,10 @@ class Definitions { def NullType = NullClass.typeRef lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null") + lazy val ImplicitScrutineeTypeSym = + newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty) + def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef + lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") def ScalaPredefModule(implicit ctx: Context) = ScalaPredefModuleRef.symbol diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 2c18b41d5e01..a340d9160150 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -801,11 +801,14 @@ object Parsers { case FORSOME => syntaxError(ExistentialTypesNoLongerSupported()); t case _ => if (imods.is(Implicit) && !t.isInstanceOf[FunctionWithMods]) - syntaxError("Types with implicit keyword can only be function types", Position(start, start + nme.IMPLICITkw.asSimpleName.length)) + syntaxError("Types with implicit keyword can only be function types", implicitKwPos(start)) t } } + private def implicitKwPos(start: Int): Position = + Position(start, start + nme.IMPLICITkw.asSimpleName.length) + /** TypedFunParam ::= id ':' Type */ def typedFunParam(start: Offset, name: TermName, mods: Modifiers = EmptyModifiers): Tree = atPos(start) { accept(COLON) @@ -1092,6 +1095,7 @@ object Parsers { * | SimpleExpr1 ArgumentExprs `=' Expr * | PostfixExpr [Ascription] * | PostfixExpr `match' `{' CaseClauses `}' + * | `implicit' `match' `{' ImplicitCaseClauses `}' * Bindings ::= `(' [Binding {`,' Binding}] `)' * Binding ::= (id | `_') [`:' Type] * Ascription ::= `:' CompoundType @@ -1106,7 +1110,8 @@ object Parsers { val start = in.offset if (in.token == IMPLICIT || in.token == ERASED) { val imods = modifiers(funArgMods) - implicitClosure(start, location, imods) + if (in.token == MATCH) implicitMatch(start, imods) + else implicitClosure(start, location, imods) } else { val saved = placeholderParams placeholderParams = Nil @@ -1207,9 +1212,7 @@ object Parsers { case COLON => ascription(t, location) case MATCH => - atPos(startOffset(t), in.skipToken()) { - inBraces(Match(t, caseClauses())) - } + matchExpr(t) case _ => t } @@ -1240,6 +1243,37 @@ object Parsers { } } + /** `match' { CaseClauses } + * `match' { ImplicitCaseClauses } + */ + def matchExpr(t: Tree) = + atPos(startOffset(t), in.skipToken()) { + inBraces(Match(t, caseClauses())) + } + + /** `match' { ImplicitCaseClauses } + */ + def implicitMatch(start: Int, imods: Modifiers) = { + def markFirstIllegal(mods: List[Mod]) = mods match { + case mod :: _ => syntaxError(em"illegal modifier for implicit match", mod.pos) + case _ => + } + imods.mods match { + case Mod.Implicit() :: mods => markFirstIllegal(mods) + case mods => markFirstIllegal(mods) + } + val result @ Match(t, cases) = matchExpr(ImplicitScrutinee().withPos(implicitKwPos(start))) + for (CaseDef(pat, _, _) <- cases) { + def isImplicitPattern(pat: Tree) = pat match { + case Typed(pat1, _) => isVarPattern(pat1) + case pat => isVarPattern(pat) + } + if (!isImplicitPattern(pat)) + syntaxError(em"not a legal pattern for an implicit match", pat.pos) + } + result + } + /** FunParams ::= Bindings * | id * | `_' @@ -1544,8 +1578,9 @@ object Parsers { } } - /** CaseClauses ::= CaseClause {CaseClause} - */ + /** CaseClauses ::= CaseClause {CaseClause} + * ImplicitCaseClauses ::= ImplicitCaseClause {ImplicitCaseClause} + */ def caseClauses(): List[CaseDef] = { val buf = new ListBuffer[CaseDef] buf += caseClause() @@ -1553,7 +1588,8 @@ object Parsers { buf.toList } - /** CaseClause ::= case Pattern [Guard] `=>' Block + /** CaseClause ::= case Pattern [Guard] `=>' Block + * ImplicitCaseClause ::= case PatVar [Ascription] [Guard] `=>' Block */ def caseClause(): CaseDef = atPos(in.offset) { accept(CASE) @@ -2532,8 +2568,12 @@ object Parsers { if (in.token == IMPLICIT || in.token == ERASED) { val start = in.offset var imods = modifiers(funArgMods) - if (isBindingIntro) stats += implicitClosure(start, Location.InBlock, imods) - else stats +++= localDef(start, imods) + if (isBindingIntro) + stats += implicitClosure(start, Location.InBlock, imods) + else if (in.token == MATCH) + stats += implicitMatch(start, imods) + else + stats +++= localDef(start, imods) } else { stats +++= localDef(in.offset) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 60d12147ce5d..bf7a06163e50 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -365,7 +365,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ + (("/* inlined from " ~ toText(call) ~ " */ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 92230e38310b..1481d49136e1 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -30,7 +30,7 @@ object FirstTransform { /** The first tree transform * - eliminates some kinds of trees: Imports, NamedArgs - * - stubs out native methods + * - stubs out native and typelevel methods * - eliminates self tree in Template and self symbol in ClassInfo * - collapses all type trees to trees of class TypeTree * - converts idempotent expressions with constant types @@ -109,12 +109,15 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } override def transformDefDef(ddef: DefDef)(implicit ctx: Context) = { - if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) { + def disable(stubKind: String) = { ddef.symbol.resetFlag(Deferred) DefDef(ddef.symbol.asTerm, _ => ref(defn.Sys_errorR).withPos(ddef.pos) - .appliedTo(Literal(Constant("native method stub")))) - } else ddef + .appliedTo(Literal(Constant(s"$stubKind method stub")))) + } + if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) disable("native") + else if (ddef.symbol.is(TypeLevel)) disable("type level") + else ddef } override def transformStats(trees: List[Tree])(implicit ctx: Context): List[Tree] = diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index f163b6f9483e..0df741e50b64 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -560,15 +560,35 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * bindings for variables bound in this pattern to `bindingsBuf`. */ def reducePattern(bindingsBuf: mutable.ListBuffer[MemberDef], scrut: TermRef, pat: Tree): Boolean = { + val isImplicit = scrut.info == defn.ImplicitScrutineeTypeRef + def newBinding(name: TermName, flags: FlagSet, rhs: Tree): Symbol = { - val sym = newSym(name, flags, rhs.tpe.widenTermRefExpr).asTerm + val info = if (flags `is` Implicit) rhs.tpe.widen else rhs.tpe.widenTermRefExpr + val sym = newSym(name, flags, info).asTerm bindingsBuf += ValDef(sym, constToLiteral(rhs)) sym } + + def searchImplicit(name: TermName, tpt: Tree) = { + val evidence = typer.inferImplicitArg(tpt.tpe, tpt.pos) + evidence.tpe match { + case fail: Implicits.AmbiguousImplicits => + ctx.error(typer.missingArgMsg(evidence, tpt.tpe, ""), tpt.pos) + true // hard error: return true to stop implicit search here + case fail: Implicits.SearchFailureType => + false + case _ => + if (name != nme.WILDCARD) newBinding(name, Implicit, evidence) + true + } + } + pat match { case Typed(pat1, tpt) => - scrut <:< tpt.tpe && - reducePattern(bindingsBuf, scrut, pat1) + if (isImplicit) searchImplicit(nme.WILDCARD, tpt) + else scrut <:< tpt.tpe && reducePattern(bindingsBuf, scrut, pat1) + case pat @ Bind(name: TermName, Typed(_, tpt)) if isImplicit => + searchImplicit(name, tpt) case pat @ Bind(name: TermName, body) => reducePattern(bindingsBuf, scrut, body) && { if (name != nme.WILDCARD) newBinding(name, EmptyFlags, ref(scrut)) @@ -614,7 +634,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => false } } - val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() += scrutineeBinding + val caseBindingsBuf = new mutable.ListBuffer[MemberDef]() + if (scrutType != defn.ImplicitScrutineeTypeRef) caseBindingsBuf += scrutineeBinding val pat1 = typer.typedPattern(cdef.pat, scrutType)(typer.gadtContext(gadtSyms)) if (reducePattern(caseBindingsBuf, scrutineeSym.termRef, pat1) && guardOK) Some((caseBindingsBuf.toList, cdef.body)) @@ -679,11 +700,12 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case Some(_) => reduceTopLevelMatch(sel, selType, tree.cases, this) match { case Some((caseBindings, rhs)) => + var rhsCtx = ctx.fresh.setNewScope for (binding <- caseBindings) { matchBindingsBuf += binding - ctx.enter(binding.symbol) + rhsCtx.enter(binding.symbol) } - typedExpr(rhs, pt) + typedExpr(rhs, pt)(rhsCtx) case None => def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 594b861ac69d..47206df62b3f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -567,7 +567,8 @@ class Typer extends Namer def typedTpt = checkSimpleKinded(typedType(tree.tpt)) def handlePattern: Tree = { val tpt1 = typedTpt - if (!ctx.isAfterTyper) constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) + if (!ctx.isAfterTyper && pt != defn.ImplicitScrutineeTypeRef) + constrainPatternType(tpt1.tpe, pt)(ctx.addMode(Mode.GADTflexible)) // special case for an abstract type that comes with a class tag tryWithClassTag(ascription(tpt1, isWildcard = true), pt) } @@ -972,6 +973,9 @@ class Typer extends Namer val (protoFormals, _) = decomposeProtoFunction(pt, 1) val unchecked = pt.isRef(defn.PartialFunctionClass) typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) + case id @ untpd.ImplicitScrutinee() => + val sel1 = id.withType(defn.ImplicitScrutineeTypeRef) + typedMatchFinish(tree, sel1, sel1.tpe, pt) case _ => val sel1 = typedExpr(tree.selector) val selType = fullyDefinedType(sel1.tpe, "pattern selector", tree.pos).widen @@ -1337,6 +1341,7 @@ class Typer extends Namer if (body1.tpe.isInstanceOf[TermRef]) pt1 else body1.tpe.underlyingIfRepeated(isJava = false) val sym = ctx.newPatternBoundSymbol(tree.name, symTp, tree.pos) + if (pt == defn.ImplicitScrutineeTypeRef) sym.setFlag(Implicit) if (ctx.mode.is(Mode.InPatternAlternative)) ctx.error(i"Illegal variable ${sym.name} in pattern alternative", tree.pos) assignType(cpy.Bind(tree)(tree.name, body1), sym) diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index 00d7524e86bf..b4c5d19c6b16 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -175,6 +175,7 @@ Expr1 ::= ‘if’ ‘(’ Expr ‘)’ {nl} Expr [[semi] ‘else | SimpleExpr1 ArgumentExprs ‘=’ Expr Assign(expr, expr) | PostfixExpr [Ascription] | PostfixExpr ‘match’ ‘{’ CaseClauses ‘}’ Match(expr, cases) -- point on match + | ‘implicit’ ‘match’ ‘{’ ImplicitCaseClauses ‘}’ Ascription ::= ‘:’ InfixType Typed(expr, tp) | ‘:’ Annotation {Annotation} Typed(expr, Annotated(EmptyTree, annot)*) Catches ::= ‘catch’ Expr @@ -223,6 +224,8 @@ Guard ::= ‘if’ PostfixExpr CaseClauses ::= CaseClause { CaseClause } Match(EmptyTree, cases) CaseClause ::= ‘case’ (Pattern [Guard] ‘=>’ Block | INT) CaseDef(pat, guard?, block) // block starts at => +ImplicitCaseClauses ::= ImplicitCaseClause { ImplicitCaseClause } +ImplicitCaseClause ::= ‘case’ PatVar [‘:’ RefinedType] [Guard] ‘=>’ Block Pattern ::= Pattern1 { ‘|’ Pattern1 } Alternative(pats) Pattern1 ::= PatVar ‘:’ RefinedType Bind(name, Typed(Ident(wildcard), tpe)) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 3ad7a9d1ee35..9327ea15b75c 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -331,33 +331,19 @@ By contrast, the new `implicit match` construct makes implicit search available the problem of creating the right set, one would use it as follows: ```scala transparent def setFor[T]: Set[T] = implicit match { - case Ordering[T] => new TreeSet[T] - case _ => new HashSet[T] + case ord: Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] } ``` -An implicit match uses the `implicit` keyword in the place of the scrutinee. Its patterns are types. -Patterns are tried in sequence. The first case with a pattern for which an implicit can be summoned is chosen. +An implicit match uses the `implicit` keyword in the place of the scrutinee. Its patterns are type ascriptions of the form `identifier : Type`. + +Patterns are tried in sequence. The first case with a pattern `x: T` such that an implicit value +of type `T` can be summoned is chosen. The variable `x` is then bound to the implicit value for the remainder of the case. It can in turn be used as an implicit in the right hand side of the case. +It is an error if one of the tested patterns gives rise to an ambiguous implicit search. Implicit matches can only occur as toplevel match expressions of transparent methods. This ensures that all implicit searches are done at compile-time. -There may be several patterns in a case of an implicit match, separated by commas. Patterns may bind type variables. -For instance the case -```scala - case Convertible[T, u], Printable[`u`] => ... -``` -matches any type `T` that is `Convertible` to another type `u` such that `u` is `Printable`. - -## New Syntax for Type Variables in Patterns - -This last example exhibits some awkwardness in the way type variables are currently handled in patterns. To _bind_ a type variable, -it needs to start with a lower case letter, then to refer to the bound variable itself, the variable has to be -put in backticks. This is doable, but feels a bit unnatural. To improve on the syntax, we -allow an alternative syntax that prefixes type variables by `type`: -```scala - case Convertible[T, type U], Printable[U] => ... -``` - ## Transparent Values Value definitions can also be marked `transparent`. Examples: diff --git a/tests/neg/implicitMatch-ambiguous.scala b/tests/neg/implicitMatch-ambiguous.scala new file mode 100644 index 000000000000..c7d485b8a29b --- /dev/null +++ b/tests/neg/implicitMatch-ambiguous.scala @@ -0,0 +1,12 @@ +object Test { + + class A + implicit val a1: A = new A + implicit val a2: A = new A + + transparent def f: Any = implicit match { + case _: A => ??? // error: ambiguous implicits + } + + f +} \ No newline at end of file diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala new file mode 100644 index 000000000000..ec508d89e78d --- /dev/null +++ b/tests/neg/implicitMatch-syntax.scala @@ -0,0 +1,26 @@ +object Test { + import collection.immutable.TreeSet + import collection.immutable.HashSet + + transparent def f1[T] = implicit implicit match { // error: repeated modifier // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + + } + + transparent def f2[T] = implicit erased match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + transparent def f3[T] = erased implicit match { // error: illegal modifier + case ord: Ordered[T] => new TreeSet[T] // error: no implicit + case _ => new HashSet[T] + } + + transparent def f4() = implicit match { + case Nil => ??? // error: not a legal pattern + case x :: xs => ??? // error: not a legal pattern + } + +} \ No newline at end of file diff --git a/tests/run/implicitMatch.check b/tests/run/implicitMatch.check new file mode 100644 index 000000000000..223b7836fb19 --- /dev/null +++ b/tests/run/implicitMatch.check @@ -0,0 +1 @@ +B diff --git a/tests/run/implicitMatch.scala b/tests/run/implicitMatch.scala new file mode 100644 index 000000000000..c11888e8897b --- /dev/null +++ b/tests/run/implicitMatch.scala @@ -0,0 +1,26 @@ +object Test extends App { + import collection.immutable.TreeSet + import collection.immutable.HashSet + + transparent def f[T]() = implicit match { + case ord: Ordering[T] => new TreeSet[T] + case _ => new HashSet[T] + } + + class A + class B + implicit val b: B = new B + + transparent def g = implicit match { + case _: A => println("A") + case _: B => println("B") + } + + implicitly[Ordering[String]] + + f[String]() + f[AnyRef]() + implicitly[B] + g + +} \ No newline at end of file From 8d03e4464c6ee89b9f3cc6c528b1e71656c46c55 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 15:55:09 +0200 Subject: [PATCH 32/62] Fix test --- tests/neg/typelevel.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/typelevel.scala b/tests/neg/typelevel.scala index f75c425a4e9d..201722ded1d9 100644 --- a/tests/neg/typelevel.scala +++ b/tests/neg/typelevel.scala @@ -47,7 +47,7 @@ object Test { val rra: HCons[Int, HNil.type] = rr // ok val rr2 = new Deco2(HCons(1, HNil)) ++ HNil val rr2a: HCons[Int, HNil.type] = rr2 // ok - val rr0 = new Deco0(HCons(1, HNil)) ++ HNil + val rr0 = new Deco0(HCons(1, HNil)) ++ HNil // error Maximal number of successive inlines (32) exceeded val rr0a: HCons[Int, HNil.type] = rr0 // error (type error because no inline) val rr1 = new Deco1(HCons(1, HNil)) ++ HNil val rr1a: HCons[Int, HNil.type] = rr1 // error (type error because no inline) From 9bee4c0508e4595fe482f3dee71542a47d0a92e7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 17:30:57 +0200 Subject: [PATCH 33/62] Keep track of positions for inlined arguments If an inlined argument moved into the expansion of an inlined call, its position is interpretated relative to the source of the next enclosing inline call. But this is wrong if the argument comes from a different file as the inlined function. To correct this, we now wrap inline arguments in another Inlined node, which has an empty `call` part. Such nodes then cancel the effect of an enclosing non-empty Inlined node for the computation of source positions. --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 14 +++++++++++--- .../tools/dotc/core/tasty/TreePickler.scala | 2 ++ .../tools/dotc/core/tasty/TreeUnpickler.scala | 2 ++ .../tools/dotc/printing/RefinedPrinter.scala | 3 ++- .../tools/dotc/sbt/ExtractDependencies.scala | 2 +- .../dotty/tools/dotc/transform/MegaPhase.scala | 2 +- .../dotty/tools/dotc/transform/PostTyper.scala | 2 +- .../dotty/tools/dotc/transform/ReifyQuotes.scala | 2 +- .../src/dotty/tools/dotc/typer/Inliner.scala | 16 +++++++++++++--- .../src/scala/tasty/util/ShowSourceCode.scala | 2 +- tests/neg/inline-position/A_1.scala | 5 +++++ tests/neg/inline-position/B_2.scala | 5 +++++ 12 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 tests/neg/inline-position/A_1.scala create mode 100644 tests/neg/inline-position/B_2.scala diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a8f60aa5572d..779ed69f621c 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1072,9 +1072,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { override def inlineContext(call: Tree)(implicit ctx: Context): Context = ctx.fresh.setProperty(InlinedCalls, call :: enclosingInlineds) - /** All enclosing calls that are currently inlined, from innermost to outermost */ - def enclosingInlineds(implicit ctx: Context): List[Tree] = - ctx.property(InlinedCalls).getOrElse(Nil) + /** All enclosing calls that are currently inlined, from innermost to outermost. + * EmptyTree calls cancel the next-enclosing non-empty call in the list + */ + def enclosingInlineds(implicit ctx: Context): List[Tree] = { + def normalize(ts: List[Tree]): List[Tree] = ts match { + case t :: (ts1 @ (t1 :: ts2)) if t.isEmpty => normalize(if (t1.isEmpty) ts1 else ts2) + case t :: ts1 => t :: normalize(ts1) + case Nil => Nil + } + normalize(ctx.property(InlinedCalls).getOrElse(Nil)) + } /** The source file where the symbol of the `inline` method referred to by `call` * is defined diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 07780be6cf27..8200deec15e5 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -546,6 +546,8 @@ class TreePickler(pickler: TastyPickler) { pickleTree(lo); if (hi ne lo) pickleTree(hi) } + case EmptyTree => + writeByte(EMPTYTREE) case tpd.UntypedSplice(splice) => writeByte(UNTYPEDSPLICE) withLength { pickleUntyped(splice); pickleType(tree.tpe) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index a3728b9e61c5..0ed8904462a2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -1023,6 +1023,8 @@ class TreeUnpickler(reader: TastyReader, ByNameTypeTree(readTpt()) case NAMEDARG => NamedArg(readName(), readTerm()) + case EMPTYTREE => + EmptyTree case _ => readPathTerm() } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index bf7a06163e50..9990d1d9ea8f 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -365,7 +365,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ toText(call) ~ " */ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ + (("/* inlined from " ~ toText(call) ~ " */ ") `provided` + !call.isEmpty && !homogenizedView && !ctx.settings.YshowNoInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala index 593801633f76..3e8698589771 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala @@ -358,7 +358,7 @@ private class ExtractDependenciesCollector extends tpd.TreeTraverser { thisTreeT } tree match { - case Inlined(call, _, _) => + case Inlined(call, _, _) if !call.isEmpty => // The inlined call is normally ignored by TreeTraverser but we need to // record it as a dependency traverse(call) diff --git a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala index 8e1981c85e07..37965cdf0872 100644 --- a/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala +++ b/compiler/src/dotty/tools/dotc/transform/MegaPhase.scala @@ -360,7 +360,7 @@ class MegaPhase(val miniPhases: Array[MiniPhase]) extends Phase { case tree: Inlined => implicit val ctx = prepInlined(tree, start)(outerCtx) val bindings = transformSpecificTrees(tree.bindings, start) - val expansion = transformTree(tree.expansion, start) + val expansion = transformTree(tree.expansion, start)(inlineContext(tree.call)) goInlined(cpy.Inlined(tree)(tree.call, bindings, expansion), start) case tree: Return => implicit val ctx = prepReturn(tree, start)(outerCtx) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index f333dbe541a1..0e626ea66b08 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -222,7 +222,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case _ => super.transform(tree1) } - case Inlined(call, bindings, expansion) => + case Inlined(call, bindings, expansion) if !call.isEmpty => // Leave only a call trace consisting of // - a reference to the top-level class from which the call was inlined, // - the call's position diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 57360d71ce2e..6993ebf50abb 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -582,7 +582,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { val last = enteredSyms stats.foreach(markDef) mapOverTree(last) - case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) => + case Inlined(call, bindings, InlineSplice(expansion @ Select(body, name))) if !call.isEmpty => assert(call.symbol.is(Macro)) val tree2 = if (level == 0) { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 0df741e50b64..6f6088a306ce 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -275,6 +275,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => } + /** Make `tree` part of inlined expansion. This means its owner has to be changed + * from its `originalOwner`, and, if it comes from outside the inlined method + * itself, it has to be marked as an inlined argument. + */ + def integrate(tree: Tree, originalOwner: Symbol)(implicit ctx: Context) = { + val result = tree.changeOwner(originalOwner, ctx.owner) + if (!originalOwner.isContainedIn(inlinedMethod)) Inlined(EmptyTree, Nil, result) + else result + } + /** The Inlined node representing the inlined call */ def inlined(pt: Type) = { // Compute bindings for all parameters, appending them to bindingsBuf @@ -485,9 +495,9 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (paramProxies.contains(tree.typeOpt)) search(bindingsBuf).orElse(search(matchBindingsBuf)) match { case Some(vdef: ValDef) if vdef.symbol.is(Transparent) => - Some(vdef.rhs.changeOwner(vdef.symbol, ctx.owner)) + Some(integrate(vdef.rhs, vdef.symbol)) case Some(ddef: DefDef) => - Some(ddef.rhs.changeOwner(ddef.symbol, ctx.owner)) + Some(integrate(ddef.rhs, ddef.symbol)) case _ => None } else None @@ -767,7 +777,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { refCount.get(sym) match { case Some(1) if sym.is(Method) => bindingOfSym(sym) match { - case binding: ValOrDefDef => binding.rhs.changeOwner(sym, ctx.owner) + case binding: ValOrDefDef => integrate(binding.rhs, sym) } case none => t } diff --git a/library/src/scala/tasty/util/ShowSourceCode.scala b/library/src/scala/tasty/util/ShowSourceCode.scala index 41600488d159..71251847299f 100644 --- a/library/src/scala/tasty/util/ShowSourceCode.scala +++ b/library/src/scala/tasty/util/ShowSourceCode.scala @@ -384,7 +384,7 @@ class ShowSourceCode[T <: Tasty with Singleton](tasty0: T) extends Show[T](tasty this += lineBreak() += "}" } - case Term.Inlined(call, bindings, expansion) => + case Term.Inlined(call, bindings, expansion) => // FIXME: Don't print Inlined with empty calls? this += "{ // inlined" indented { printStats(bindings, expansion) diff --git a/tests/neg/inline-position/A_1.scala b/tests/neg/inline-position/A_1.scala new file mode 100644 index 000000000000..b031a5df69cd --- /dev/null +++ b/tests/neg/inline-position/A_1.scala @@ -0,0 +1,5 @@ +object A { + + transparent def f(x: => Object) = (x, x) + +} \ No newline at end of file diff --git a/tests/neg/inline-position/B_2.scala b/tests/neg/inline-position/B_2.scala new file mode 100644 index 000000000000..c3292081cbfc --- /dev/null +++ b/tests/neg/inline-position/B_2.scala @@ -0,0 +1,5 @@ +object B { + erased val y = Array('a') + + A.f(new String(y)) // error: value y is declared as erased but is in fact used +} \ No newline at end of file From dea677225354017d0a379e86d6c1d2a3fb4143e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 17:31:53 +0200 Subject: [PATCH 34/62] Reset Config option. Was accidentally committed with debug setting before. --- compiler/src/dotty/tools/dotc/config/Config.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/Config.scala b/compiler/src/dotty/tools/dotc/config/Config.scala index 05aa89fab7b5..ea96d9dcd5d9 100644 --- a/compiler/src/dotty/tools/dotc/config/Config.scala +++ b/compiler/src/dotty/tools/dotc/config/Config.scala @@ -160,7 +160,7 @@ object Config { final val showCompletions = false /** If set, enables tracing */ - final val tracingEnabled = true + final val tracingEnabled = false /** Initial capacity of uniques HashMap. * Note: This MUST BE a power of two to work with util.HashSet From 6f65a2053bdb5bb7479d32274d9023962b32fb53 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 18:05:05 +0200 Subject: [PATCH 35/62] Implement restrections where an implicit match can appear --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 ++ tests/neg/implicitMatch-syntax.scala | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 47206df62b3f..993d26f94217 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -974,6 +974,8 @@ class Typer extends Namer val unchecked = pt.isRef(defn.PartialFunctionClass) typed(desugar.makeCaseLambda(tree.cases, protoFormals.length, unchecked) withPos tree.pos, pt) case id @ untpd.ImplicitScrutinee() => + if (tree.getAttachment(PrepareTransparent.TopLevelMatch).isEmpty) + ctx.error(em"implicit match cannot be used here; it must occur as a toplevel match of a transparent method", tree.pos) val sel1 = id.withType(defn.ImplicitScrutineeTypeRef) typedMatchFinish(tree, sel1, sel1.tpe, pt) case _ => diff --git a/tests/neg/implicitMatch-syntax.scala b/tests/neg/implicitMatch-syntax.scala index ec508d89e78d..97a47f20f66b 100644 --- a/tests/neg/implicitMatch-syntax.scala +++ b/tests/neg/implicitMatch-syntax.scala @@ -23,4 +23,11 @@ object Test { case x :: xs => ??? // error: not a legal pattern } + transparent def f5[T] = locally { implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + }} + + def f6[T] = implicit match { // error: implicit match cannot be used here + case _ => new HashSet[T] + } } \ No newline at end of file From e78fc7966d8fb32d5a5c51720ab39f4b18ed3a41 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 18:05:53 +0200 Subject: [PATCH 36/62] Bump Tasty format major version --- compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 58d23810340c..be76009d54ac 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -243,7 +243,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion = 9 + val MajorVersion = 10 val MinorVersion = 0 /** Tags used to serialize names */ From cd87085059e61e7b9f7deff97fe5619349c5f492 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 18:07:00 +0200 Subject: [PATCH 37/62] Address reviewers comments --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 4 ++-- docs/docs/typelevel.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 75ca533616a9..ae8d5a4ae34c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -21,7 +21,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { override def isType = op.name.isTypeName } - /** A typed subtree of an untyped tree needs to be wrapped in a TypedSlice + /** A typed subtree of an untyped tree needs to be wrapped in a TypedSplice * @param owner The current owner at the time the tree was defined */ abstract case class TypedSplice(splice: tpd.Tree)(val owner: Symbol) extends ProxyTree { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 6f6088a306ce..39dd63b0d925 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -696,8 +696,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context) = typed(tree.cond, defn.BooleanType) match { case cond1 @ ConstantValue(b: Boolean) => - var selected = typed(if (b) tree.thenp else tree.elsep, pt) - if (selected.isEmpty) selected = tpd.Literal(Constant(())) + val selected0 = if (b) tree.thenp else tree.elsep + val selected = if (selected0.isEmpty) tpd.Literal(Constant(())) else typed(selected0, pt) if (isIdempotentExpr(cond1)) selected else Block(cond1 :: Nil, selected) case cond1 => diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 9327ea15b75c..67976b215205 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -293,7 +293,7 @@ It would be quite hard for even a good optimizer to undo the generic abstraction def dotProduct(xs: Array[Double], ys: Array[Double]): Double = { require(xs.length == ys.length) var i = 0 - var s: Double = n.zero + var s: Double = math.Numeric.DoubleIsFractional.zero while (i < xs.length) { s = s + xs(i) * ys(i) i += 1 From 3b98431a820fec18c36beef301d4cfd3aab6b105 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2018 19:00:36 +0200 Subject: [PATCH 38/62] Refine implicit match checking --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0aecef759123..ba63c6e000b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1091,7 +1091,10 @@ class Namer { typer: Typer => def dealiasIfUnit(tp: Type) = if (tp.isRef(defn.UnitClass)) defn.UnitType else tp var rhsCtx = ctx.addMode(Mode.InferringReturnType) - if (sym.isTransparentMethod) rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + if (sym.isTransparentMethod) { + rhsCtx = rhsCtx.addMode(Mode.TransparentBody) + PrepareTransparent.markTopLevelMatches(sym, mdef.rhs) + } def rhsType = typedAheadExpr(mdef.rhs, inherited orElse rhsProto)(rhsCtx).tpe // Approximate a type `tp` with a type that does not contain skolem types. From 4b7a240df577f7ae826e6c0db2cf3aac0da71531 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 Jul 2018 13:22:50 +0200 Subject: [PATCH 39/62] Doc fix --- docs/docs/typelevel.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 67976b215205..a376c0dab2cf 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -208,7 +208,7 @@ defaultValue[AnyVal] = None ``` As another example, consider the type-level inverse of `toNat`: given a _type_ representing a Peano number, return the integer _value_ corresponding to it. Here's how this can be defined: ```scala -transparent def toInt[N <: Nat]: Int = anyValue[N] match { +transparent def toInt[N <: Nat]: Int = erasedValue[N] match { case _: Z => 0 case _: S[n] => toInt[n] + 1 } @@ -431,4 +431,4 @@ One important difference between the two schemes is that the reflective call imp ## Acknowledgments -Many of the ideas in this proposal resulted from discussions with @gsps and @OlivierBlanvillain, the authors of the "TypeOf" approach (PR #4671). @gsps suggested the use of the `transparent` keyword. @OlivierBlanvillain suggested techniques like `anyValue` and `Typed` to lift term computations to types. The present proposal also benefited from feedback from @milessabin, @adriaanm, @sjrd, Andrei Alexandrescu, John Hughes, Conor McBride and Stephanie Weirich on earlier designs. The relationship with meta programming has a lot in common with the original inline and meta proposals in SIP 28 and SIP 29. +Many of the ideas in this proposal resulted from discussions with @gsps and @OlivierBlanvillain, the authors of the "TypeOf" approach (PR #4671). @gsps suggested the use of the `transparent` keyword. @OlivierBlanvillain suggested techniques like `erasedValue` and `Typed` to lift term computations to types. The present proposal also benefited from feedback from @milessabin, @adriaanm, @sjrd, Andrei Alexandrescu, John Hughes, Conor McBride and Stephanie Weirich on earlier designs. The relationship with meta programming has a lot in common with the original inline and meta proposals in SIP 28 and SIP 29. From e4a7e603163aff4612df8cf3ce35d6e7fed26f4b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 19 Jul 2018 14:36:58 +0200 Subject: [PATCH 40/62] Explain rewrite rules and fix implementation to conform --- .../src/dotty/tools/dotc/typer/Inliner.scala | 14 ++-- docs/docs/typelevel.md | 71 +++++++++++++++++-- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 39dd63b0d925..660649130385 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -521,7 +521,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** Rewrite an application * - * ((x1, ..., sn) => b)(e1, ..., en) + * ((x1, ..., xn) => b)(e1, ..., en) * * to * @@ -604,10 +604,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { if (name != nme.WILDCARD) newBinding(name, EmptyFlags, ref(scrut)) true } - case Ident(name) => - name == nme.WILDCARD || + case Ident(nme.WILDCARD) => + true + case pat: RefTree => scrut =:= pat.tpe || - scrut.widen =:= pat.tpe.widen && scrut.widen.classSymbol.is(Module) + scrut.widen =:= pat.tpe.widen && scrut.widen.classSymbol.is(Module) && { + scrut.prefix match { + case _: SingletonType | NoPrefix => true + case _ => false + } + } case UnApply(unapp, _, pats) => unapp.tpe.widen match { case mt: MethodType if mt.paramInfos.length == 1 => diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index a376c0dab2cf..2b69d0f9e059 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -164,14 +164,73 @@ Here is an expansion of `nthDynamic` applied to a tuple `as: (Int, String)` and ``` So the end result of the expansion is the expression `throw new IndexOutOfBoundsException`, which has type `Nothing`. It is important to note that programs are treated as _data_ in this process, so the exception will not be thrown at compile time, but only if the program is run after it compiles without errors. -**Rewrite Rules** The following rewrite rules are performed when simplifying inlined bodies: +## Rewrite Rules + +The following rewrite rules are performed when simplifying inlined bodies: + + - **Pattern matching:** Pattern matches in toplevel match expressions are evaluated and the case with the first + pattern that is statically known to match is chosen. A pattern match `E match { Case_1 ... Case_n }` is translated as follows: + + - We first create a binding `val $scrutinee_n = E` for the scrutinee of the match. + - Matching a pattern `P` takes as additional input the current scrutinee reference `S` (this is `$scrutinee_n` for toplevel patterns). It either returns with a sequence of additional bindings or fails. The rules depend on the form of the pattern `P`. + + - If `P` is a type test `Q: T`, the match fails if `S.type` does not conform to `T`. + Otherwise, proceed by matching `Q` against `S`. + - If `P` is a wildcard `_`, the match succeeds with no additional bindings. + - If `P` is some other pattern variable `x`, succeed with the additional binding `x = S`. + - If `P` is a variable binding `x @ Q`, proceed by matching `Q` against `S`. If that + succeeds, succeed with the additional binding `x = S`. + - If `P` is some non-variable reference `R`, the match succeeds with no additional bindings + if we statically know that `S` refers to the same value as `R`. + - If `P` is a constructor pattern `C(Q_1, ..., Q_n)`, where `C` refers to a case class with a compiler-generated + `unapply` method, the match fails if `S.type` does not conform to `C`. + Otherwise, proceed by matching each sub-pattern `Q_i` against the scrutinee `S._i`. If all sub-matches + succeed, collect the resulting bindings in order. + - If `P` is a pattern with a guard `Q if E`, the match succeeds if matching `Q` succeeds and + the guard `E` is statically known to be `true`. + + The first case `case P => E` with a matching pattern `P` is chosen. The match is then rewritten + to `{ Bs; E }` where `Bs` are the bindings generated by the match of `P`. + + - **Reduction of projections:** An expression `E_0(E_1, ..., E_i, ...E_n).f_i`, where + + - `E_0` is known to be - or to forward to - a class instance creation `new C`, + - `C` is known not to have initialization code, + - `f_i` is known to select the `i`'th field of `C`, + + is rewritten to `E_i`. If the prefix `E_0` or any of the other arguments `E_j` have side effects, they are + executed in order before the result of `E_i` is returned. + + - **Conditional simplification:** If the condition of an if-then-else is statically known, the conditional + is rewritten to its then- or else-part, depending on the known value of the condition. If the condition + is pure, it is dropped; otherwise the selected branch is preceded by evaluation of the condition, in order + not to drop any side effects. + + - **Reduction of function applications:** An application of a lambda `((x_1, ..., x_n) => B)(E_1, ..., E_n)` + is rewritten to + + { val/def x_1 = E_1 + ... + val/def x_n = E_n + B + } + + where `val`s are used for value parameter bindings and `def`s are used for by-name parameter bindings. + If an argument `E_i` is a simple variable reference `y`, the corresponding binding is omitted and `y` + is used instead of `x_i` in `B`. + + - **Constant folding:** If a pure expression evaluates to a constant value, it can be replaced by this value. + + - **Garbage collection:** Bindings for parameters and pattern-bound variables are dropped according to the + following rules: - - constant folding - - evaluation of pattern matches in toplevel match expressions - - reduction of projections - - evaluation of if-then-else with constant expressions + - A binding `val x = E` is dropped if `x` is not used in the rest of the expansion and `E` is pure. + - A binding `def x = E` for a by-name parameter is dropped if `x` is not used in the rest of the expansion. + - A binding `def x = E` for a by-name parameter that is used exactly once in the result of the + expansion is dropped and the reference to `x` is replaced by `E`. -(to be expanded) + Dropping a binding might make other bindings redundant. Garbage collection proceeds until no further bindings + can be dropped. ## Matching on Types From 22d6f669fb0206387c635ac55858be8b9b8b7161 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jul 2018 16:56:35 +0200 Subject: [PATCH 41/62] Drop redundant test Was a remainder form when we had inline and transparent --- compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala index c75bdb2a9cf8..d854482537ec 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeTypeMap.scala @@ -95,9 +95,7 @@ class TreeTypeMap( val (tmap2, vparamss1) = tmap1.transformVParamss(vparamss) val res = cpy.DefDef(ddef)(name, tparams1, vparamss1, tmap2.transform(tpt), tmap2.transform(ddef.rhs)) res.symbol.transformAnnotations { - case ann: BodyAnnotation => - if (res.symbol.isTransparentMethod) ann.derivedAnnotation(transform(ann.tree)) - else ann.derivedAnnotation(res.rhs) + case ann: BodyAnnotation => ann.derivedAnnotation(transform(ann.tree)) case ann => ann } res From 59ede6dbf2c0a8a01387836098c643302c6b5556 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jul 2018 17:03:47 +0200 Subject: [PATCH 42/62] Handle nested transparent methods A transparent nested inside another transparent method is not immediately inlineable; only its inlined copies are inlineable. --- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 6 ++++++ compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 8 ++++---- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index ddffa5ede5ca..f64980e6c9a2 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -791,6 +791,12 @@ object SymDenotations { def isTransparentMethod(implicit ctx: Context): Boolean = is(TransparentMethod, butNot = AccessorOrSynthetic) + /** A transparent method that is not nested inside another transparent method. + * Nested transparents are not inlineable yet, only their inlined copies are. + */ + def isTransparentInlineable(implicit ctx: Context): Boolean = + isTransparentMethod && !owner.ownersIterator.exists(_.is(TransparentMethod)) + /** ()T and => T types should be treated as equivalent for this symbol. * Note: For the moment, we treat Scala-2 compiled symbols as loose matching, * because the Scala library does not always follow the right conventions. diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 0e626ea66b08..f8c3f6c90b2b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -165,7 +165,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isTransparentMethod) c.owner.setFlag(Macro) + else if (c.owner.isTransparentInlineable) c.owner.setFlag(Macro) else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) if (sym.isSplice || sym.isQuote) { diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 660649130385..000c47676b8e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -47,7 +47,7 @@ object Inliner { * from Scala2x class files might be `@forceInline`, but still lack that body. */ def hasBodyToInline(sym: SymDenotation)(implicit ctx: Context): Boolean = - sym.isTransparentMethod && sym.hasAnnotation(defn.BodyAnnot) + sym.isTransparentInlineable && sym.hasAnnotation(defn.BodyAnnot) /** The body to inline for method `sym`. * @pre hasBodyToInline(sym) @@ -69,7 +69,7 @@ object Inliner { /** Is `meth` a transparent method that should be inlined in this context? */ def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = - meth.isTransparentMethod && isInlineable(meth) + meth.isTransparentInlineable && isInlineable(meth) /** Try to inline a call to a transparent method. Fail with error if the maximal * inline depth is exceeded. @@ -713,7 +713,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context) = tree.getAttachment(PrepareTransparent.TopLevelMatch) match { - case Some(_) => + case Some(_) if !ctx.owner.isTransparentMethod => // don't reduce match of nested transparent yet reduceTopLevelMatch(sel, selType, tree.cases, this) match { case Some((caseBindings, rhs)) => var rhsCtx = ctx.fresh.setNewScope @@ -731,7 +731,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { | | Hint: if you do not expect the match to be reduced, put it in a locally { ... } block.""") } - case None => + case _ => super.typedMatchFinish(tree, sel, selType, pt) } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ba63c6e000b8..b7fc00b6d290 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -760,7 +760,7 @@ class Namer { typer: Typer => } private def addInlineInfo(sym: Symbol) = original match { - case original: untpd.DefDef if sym.isTransparentMethod => + case original: untpd.DefDef if sym.isTransparentInlineable => PrepareTransparent.registerInlineInfo( sym, original.rhs, diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 993d26f94217..b15dfc4127a3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1460,7 +1460,7 @@ class Typer extends Namer } val rhs1 = typedExpr(ddef.rhs, tpt1.tpe)(rhsCtx) - if (sym.isTransparentMethod) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) + if (sym.isTransparentInlineable) PrepareTransparent.registerInlineInfo(sym, ddef.rhs, _ => rhs1) if (sym.isConstructor && !sym.isPrimaryConstructor) for (param <- tparams1 ::: vparamss1.flatten) @@ -1929,7 +1929,7 @@ class Typer extends Namer case none => typed(mdef) match { case mdef1: DefDef if Inliner.hasBodyToInline(mdef1.symbol) => - assert(mdef1.symbol.isTransparentMethod, mdef.symbol) + assert(mdef1.symbol.isTransparentInlineable, mdef.symbol) Inliner.bodyToInline(mdef1.symbol) // just make sure accessors are computed, buf += mdef1 // but keep original definition, since inline-expanded code // is pickled in this case. From 2336ae16c02fa252e0f93b0e3d45f6ea28505106 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jul 2018 17:05:17 +0200 Subject: [PATCH 43/62] Handle constant literals in toplevel matches - Don't widen selector type - Add case for pattern literals --- .../src/dotty/tools/dotc/typer/Inliner.scala | 6 ++- tests/pos/transparent-nested.scala | 39 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 tests/pos/transparent-nested.scala diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 000c47676b8e..41eba62412b8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -606,6 +606,8 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { } case Ident(nme.WILDCARD) => true + case pat: Literal => + scrut.widenTermRefExpr =:= pat.tpe case pat: RefTree => scrut =:= pat.tpe || scrut.widen =:= pat.tpe.widen && scrut.widen.classSymbol.is(Module) && { @@ -714,7 +716,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { override def typedMatchFinish(tree: untpd.Match, sel: Tree, selType: Type, pt: Type)(implicit ctx: Context) = tree.getAttachment(PrepareTransparent.TopLevelMatch) match { case Some(_) if !ctx.owner.isTransparentMethod => // don't reduce match of nested transparent yet - reduceTopLevelMatch(sel, selType, tree.cases, this) match { + reduceTopLevelMatch(sel, sel.tpe, tree.cases, this) match { case Some((caseBindings, rhs)) => var rhsCtx = ctx.fresh.setNewScope for (binding <- caseBindings) { @@ -726,7 +728,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { def guardStr(guard: untpd.Tree) = if (guard.isEmpty) "" else i" if $guard" def patStr(cdef: untpd.CaseDef) = i"case ${cdef.pat}${guardStr(cdef.guard)}" errorTree(tree, em"""cannot reduce top-level match of transparent function with - | scrutinee: $sel : $selType + | scrutinee: $sel : ${sel.tpe} | patterns : ${tree.cases.map(patStr).mkString("\n ")} | | Hint: if you do not expect the match to be reduced, put it in a locally { ... } block.""") diff --git a/tests/pos/transparent-nested.scala b/tests/pos/transparent-nested.scala new file mode 100644 index 000000000000..c379da31bdaf --- /dev/null +++ b/tests/pos/transparent-nested.scala @@ -0,0 +1,39 @@ +object Test0 { + + def f(x: Int) = { + transparent def g(x: Int) = x match { + case 0 => 0 + } + g(0) + transparent val Y = 0 + g(Y) + + transparent def h(x: Int) = x match { + case Y => 0 + } + h(0) + } + + f(0) + +} + +object Test1 { + + transparent def f(x: Int) = { + transparent def g(x: Int) = x match { + case 0 => 0 + } + g(0) + transparent val Y = 0 + g(Y) + + transparent def h(x: Int) = x match { + case Y => 0 + } + h(0) + } + + f(0) + +} \ No newline at end of file From ed7f10c6ddb26e0efd9c3a04dcf97a3908245c44 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 20 Jul 2018 17:42:26 +0200 Subject: [PATCH 44/62] Refined rules for overriding of typelevel methods Typelevel methods can override other methods as long as one of the overridden methods is concrete. This supports usecases like an efficient foreach on Range types that optimizes for step = +1. --- .../tools/dotc/transform/FirstTransform.scala | 21 +++++++++--- .../dotty/tools/dotc/typer/RefChecks.scala | 4 +-- docs/docs/typelevel.md | 32 +++++++++++++++++-- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index 1481d49136e1..daa56d8dc01d 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -109,14 +109,25 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => } override def transformDefDef(ddef: DefDef)(implicit ctx: Context) = { + val meth = ddef.symbol.asTerm def disable(stubKind: String) = { - ddef.symbol.resetFlag(Deferred) - DefDef(ddef.symbol.asTerm, - _ => ref(defn.Sys_errorR).withPos(ddef.pos) + meth.resetFlag(Deferred) + polyDefDef(meth, + _ => _ => ref(defn.Sys_errorR).withPos(ddef.pos) .appliedTo(Literal(Constant(s"$stubKind method stub")))) } - if (ddef.symbol.hasAnnotation(defn.NativeAnnot)) disable("native") - else if (ddef.symbol.is(TypeLevel)) disable("type level") + if (meth.hasAnnotation(defn.NativeAnnot)) disable("native") + else if (meth.is(TypeLevel)) + meth.allOverriddenSymbols.find(!_.is(Deferred)) match { + case Some(overridden) => + polyDefDef(meth, + targs => argss => + Super(This(meth.owner.asClass), tpnme.EMPTY, inConstrCall = false) + .select(overridden) + .appliedToTypes(targs).appliedToArgss(argss)) + case None => + disable("type level") + } else ddef } diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 95a7b5b917ff..362d1544160f 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -376,8 +376,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") - } else if (member.is(TypeLevel)) { // (1.9) - overrideError("is a type-level method, may not override anything") + } else if (member.is(TypeLevel) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) + overrideError("is a type-level method, may not override only deferred methods") } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) overrideError("is a macro, may not override anything") } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index 2b69d0f9e059..ec32a943234e 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -87,9 +87,6 @@ locally { ... } ``` block, which de-classifies it as a top-level expression. (`locally` is a function in `Predef` which simply returns its argument.) -Transparent methods are effectively final; they may not be overwritten. If a transparent -method has a top-level match expression then it can itself override no other method and it must always be fully applied. - As another example, consider the following two functions over tuples: ```scala @@ -232,6 +229,35 @@ The following rewrite rules are performed when simplifying inlined bodies: Dropping a binding might make other bindings redundant. Garbage collection proceeds until no further bindings can be dropped. +## Restrictions for Transparent and Typelevel Functions + +Transparent methods are effectively final; they may not be overwritten. + +If a transparent +method has a toplevel match expression or a toplevel splice `~` on its right-hand side, +it is classified as a typelevel method that can _only_ be executed at compile time. For typelevel methods two more restrictions apply: + + 1. They must be always fully applied. + 2. They may override other methods only if one of the overridden methods is concrete. + +The right hand side of a typelevel method is never invoked by dynamic dispatch. As an example consider a situation like the following: +```scala +class Iterable[T] { + def foreach(f: T => Unit): Unit = ... +} +class List[T] extends Iterable[T] { + override transparent def foreach(f: T => Unit): Unit = ... +} +val xs: Iterable[T] = ... +val ys: List[T] = ... +val zs: Iterable[T] = ys +xs.foreach(f) // calls Iterable's foreach +ys.foreach(f) // expands to the body of List's foreach +zs.foreach(f) // calls Iterable's foreach +``` +It follows that an overriding typelevel method should implement exactly the same semantics as the +method it overrides (but possibly more effeciiently). + ## Matching on Types We have seen so far transparent functions that take terms (tuples and integers) as parameters. What if we want to base case distinctions on types instead? For instance, one would like to be able to write a function `defaultValue`, that, given a type `T` From dbaf1245da25d36c86b6792d9364cca625fafbaf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 22 Jul 2018 14:05:42 +0200 Subject: [PATCH 45/62] Address review comments This addresses all of @smarter' comment except three: 1. That erased values should be eliminated after pickling. This one should be done in a separate commit. 2. That we should use some other modifier than `override` for transparents. This one requires further discussion. 3. No asserts that typeAtPos and accessorAtPos are unique. Instead there is a comment that explains why they are not. Also, some more tests. --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 1 + compiler/src/dotty/tools/dotc/ast/tpd.scala | 10 +++++ .../tools/dotc/core/tasty/TastyFormat.scala | 5 ++- .../tools/dotc/core/tasty/TreePickler.scala | 9 ++--- .../tools/dotc/core/tasty/TreeUnpickler.scala | 16 +++----- .../src/dotty/tools/dotc/typer/Inliner.scala | 18 ++++++--- .../tools/dotc/typer/PrepareTransparent.scala | 11 ++++++ docs/docs/typelevel.md | 2 +- tests/neg/shapeless-hcons.scala | 38 +++++++++++++++++++ tests/pos/transparent.scala | 11 ++++++ tests/run/generic-tuples.scala | 15 ++++++++ tests/run/typelevel-overrides.scala | 29 ++++++++++++++ 12 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 tests/neg/shapeless-hcons.scala create mode 100644 tests/pos/transparent.scala create mode 100644 tests/run/generic-tuples.scala create mode 100644 tests/run/typelevel-overrides.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 59a65ba2f5ef..ef038da36b24 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -709,6 +709,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => */ def qualifier(tree: Tree)(implicit ctx: Context) = tree match { case Select(qual, _) => qual + case tree: Ident => desugarIdentPrefix(tree) case _ => This(ctx.owner.enclosingClass.asClass) } diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 779ed69f621c..fabdf2746cd7 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -1043,6 +1043,15 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { else (trees.head.tpe eq trees1.head.tpe) && sameTypes(trees.tail, trees1.tail) } + /** If `tree`'s purity level is less than `level`, let-bind it so that it gets evaluated + * only once. I.e. produce a + * + * { val x = 'tree ; ~within('x) } + * + * instead of otherwise + * + * ~within('tree) + */ def letBindUnless(level: TreeInfo.PurityLevel, tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = { if (exprPurity(tree) >= level) within(tree) else { @@ -1051,6 +1060,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } } + /** Let bind `tree` unless `tree` is at least idempotent */ def evalOnce(tree: Tree)(within: Tree => Tree)(implicit ctx: Context) = letBindUnless(TreeInfo.Idempotent, tree)(within) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index be76009d54ac..8bb4f5ee09ce 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -212,6 +212,7 @@ Standard-Section: "ASTs" TopLevelStat* FUNCTION Length body_Term arg_Term* INFIXOP Length op_NameRef left_Term right_Term PATDEF Length type_Term rhs_Term pattern_Term* Modifier* + EMPTYTYPETREE Note: Tree tags are grouped into 5 categories that determine what follows, and thus allow to compute the size of the tagged tree in a generic way. @@ -318,6 +319,7 @@ object TastyFormat { final val ERASED = 35 final val PARAMsetter = 36 final val EMPTYTREE = 37 + final val EMPTYTYPETREE = 38 // Cat. 2: tag Nat @@ -452,7 +454,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int) = - firstSimpleTreeTag <= tag && tag <= EMPTYTREE || + firstSimpleTreeTag <= tag && tag <= EMPTYTYPETREE || firstNatTreeTag <= tag && tag <= SYMBOLconst || firstASTTreeTag <= tag && tag <= SINGLETONtpt || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -550,6 +552,7 @@ object TastyFormat { case STABLE => "STABLE" case PARAMsetter => "PARAMsetter" case EMPTYTREE => "EMPTYTREE" + case EMPTYTYPETREE => "EMPTYTYPETREE" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 8200deec15e5..05b49160a995 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -654,10 +654,7 @@ class TreePickler(pickler: TastyPickler) { def pickleDummyRef(): Unit = writeNat(0) - def pickleDummyType(): Unit = { - writeByte(SHAREDtype) - pickleDummyRef() - } + def pickleDummyType(): Unit = writeByte(EMPTYTYPETREE) def pickleUnlessEmpty(tree: untpd.Tree): Unit = if (!tree.isEmpty) pickleUntyped(tree) @@ -865,7 +862,9 @@ class TreePickler(pickler: TastyPickler) { if (trees.isEmpty) writeByte(EMPTYTREE) else trees.foreach(pickleUntyped) case _ => - pickleUntyped(desugar(tree)) + val tree1 = desugar(tree) + assert(tree1 `ne` tree, s"Cannot pickle untyped tree $tree") + pickleUntyped(tree1) } catch { case ex: AssertionError => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 0ed8904462a2..2e74f349be9f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -515,7 +515,7 @@ class TreeUnpickler(reader: TastyReader, val rhsStart = currentAddr val rhsIsEmpty = nothingButMods(end) if (!rhsIsEmpty) skipTree() - val (givenFlags, annotFns, privateWithin) = readModifiers(end, readAnnot, readWithin, NoSymbol) + val (givenFlags, annotFns, privateWithin) = readModifiers(end, readTypedAnnot, readTypedWithin, NoSymbol) pickling.println(i"creating symbol $name at $start with flags $givenFlags") val flags = normalizeFlags(tag, givenFlags, name, isAbsType, rhsIsEmpty) def adjustIfModule(completer: LazyType) = @@ -620,10 +620,10 @@ class TreeUnpickler(reader: TastyReader, (flags, annotFns.reverse, privateWithin) } - private val readWithin: Context => Symbol = + private val readTypedWithin: Context => Symbol = implicit ctx => readType().typeSymbol - private val readAnnot: Context => Symbol => Annotation = { + private val readTypedAnnot: Context => Symbol => Annotation = { implicit ctx => readByte() val end = readEnd() @@ -1209,10 +1209,8 @@ class TreeUnpickler(reader: TastyReader, val tag = readByte() pickling.println(s"reading term ${astTagToString(tag)} at $start") - def readDummyType(): Unit = { - assert(readByte() == SHAREDtype) - assert(readNat() == 0) - } + def readDummyType(): Unit = + assert(readByte() == EMPTYTYPETREE) def readIdent(): untpd.Ident = readUntyped().asInstanceOf[untpd.Ident] @@ -1266,9 +1264,7 @@ class TreeUnpickler(reader: TastyReader, untpd.NamedArg(readName(), readUntyped()) case EMPTYTREE => untpd.EmptyTree - case SHAREDtype => - val b = readNat() - assert(b == 0, i"bad shared type $b at $currentAddr when reading ${ctx.owner.ownersIterator.toList}%, %") + case EMPTYTYPETREE => untpd.TypeTree() case _ => untpd.Literal(readConstant(tag)) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 41eba62412b8..37e096ce716c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -389,12 +389,16 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { /** A utility object offering methods for rewriting inlined code */ object reducer { - /** A dditional bidnings established by reducing match expressions */ + /** Additional bindings established by reducing match expressions */ val matchBindingsBuf = new mutable.ListBuffer[MemberDef] - /** An extractor for terms equivalent to `new C(args)`, returning the class `C` - * and the arguments `args`. Can see inside blocks and Inlined nodes and can + /** An extractor for terms equivalent to `new C(args)`, returning the class `C`, + * a list of bindings, and the arguments `args`. Can see inside blocks and Inlined nodes and can * follow a reference to an inline value binding to its right hand side. + * @return optionally, a triple consisting of + * - the class `C` + * - the arguments `args` + * - any bindings that wrap the instance creation */ private object NewInstance { def unapply(tree: Tree)(implicit ctx: Context): Option[(Symbol, List[Tree], List[Tree])] = { @@ -552,6 +556,10 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case _ => tree } + /** The result type of reducing a match. It consists, optionally of a list of bindings + * for the pattern-bound variables and the RHS of the selected case. + * Returns `None` if not case was selected. + */ type MatchRedux = Option[(List[MemberDef], untpd.Tree)] /** Reduce a toplevel match of a transparent function @@ -610,7 +618,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { scrut.widenTermRefExpr =:= pat.tpe case pat: RefTree => scrut =:= pat.tpe || - scrut.widen =:= pat.tpe.widen && scrut.widen.classSymbol.is(Module) && { + scrut.widen.classSymbol.is(Module) && scrut.widen =:= pat.tpe.widen && { scrut.prefix match { case _: SingletonType | NoPrefix => true case _ => false @@ -621,7 +629,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { case mt: MethodType if mt.paramInfos.length == 1 => val paramType = mt.paramInfos.head val paramCls = paramType.classSymbol - (scrut <:< paramType && paramCls.is(Case) && unapp.symbol.is(Synthetic)) && { + paramCls.is(Case) && unapp.symbol.is(Synthetic) && scrut <:< paramType && { val caseAccessors = if (paramCls.is(Scala2x)) paramCls.caseAccessors.filter(_.is(Method)) else paramCls.asClass.paramAccessors diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 1c75fc23d2d3..f79e110b17b0 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -299,6 +299,12 @@ object PrepareTransparent { def registerAccessor(tree: Tree) = { inlining.println(i"accessor: $tree at ${tree.pos}") accessorAtPos(tree.pos.toSynthetic) = tree.symbol + // Note: It's possible that during traversals several accessors are stored under the same + // position. This could happen for instance for implicit conersions added around a tree. + // or for a setter containing a getter in an op-assignment node. + // In general, it's always the innermost tree that holds the relevant symbol. The traversal + // order guarantees that the innermost tree's symbol is stored last, and thereby replaces all previously + // stored symbols. } def traverse(tree: Tree)(implicit ctx: Context): Unit = { @@ -309,6 +315,11 @@ object PrepareTransparent { if (tree.symbol.exists && !isLocal(tree.symbol, inlineMethod)) { if (ctx.debug) inlining.println(i"type at $tree @ ${tree.pos.toSynthetic} = ${tree.tpe}") typeAtPos(tree.pos.toSynthetic) = tree.tpe + // Note: It's possible that during traversals several types are stored under the same + // position. This could happen for instance for implicit conersions added around a tree. + // In general, it's always the innermost tree that holds the relevant type. The traversal + // order guarantees that the innermost tree's type is stored last, and thereby replaces all previously + // stored types. } case _: Select => tree.symbol.name match { diff --git a/docs/docs/typelevel.md b/docs/docs/typelevel.md index ec32a943234e..8e09d9971e09 100644 --- a/docs/docs/typelevel.md +++ b/docs/docs/typelevel.md @@ -256,7 +256,7 @@ ys.foreach(f) // expands to the body of List's foreach zs.foreach(f) // calls Iterable's foreach ``` It follows that an overriding typelevel method should implement exactly the same semantics as the -method it overrides (but possibly more effeciiently). +method it overrides (but possibly more efficiently). ## Matching on Types diff --git a/tests/neg/shapeless-hcons.scala b/tests/neg/shapeless-hcons.scala new file mode 100644 index 000000000000..a38effea5d13 --- /dev/null +++ b/tests/neg/shapeless-hcons.scala @@ -0,0 +1,38 @@ +package shapeless { + +sealed trait HList extends Product with Serializable { + def :: (x: Any): HList = new ::(x, this) +} + +final case class ::[+H, +T <: HList](head : H, tail : T) extends HList { + override def toString = head match { + case _: ::[_, _] => s"($head) :: $tail" + case _ => s"$head :: $tail" + } +} + +sealed trait HNil extends HList { + override def toString = "HNil" +} + +case object HNil extends HNil + +} +import shapeless._ + +package test { + +object Test { + + val xs = 1 :: 2 :: Nil + val ys = (3, 4) + + (xs: Any) match { + case x :: xs => ??? + } + xs match { + case x :: xs => ??? // error: unreachable case + } + +} +} \ No newline at end of file diff --git a/tests/pos/transparent.scala b/tests/pos/transparent.scala new file mode 100644 index 000000000000..37501641f0eb --- /dev/null +++ b/tests/pos/transparent.scala @@ -0,0 +1,11 @@ +object Test { + + object double extends (Int => Int) { + transparent def apply(x: Int) = x * 2 + } + + transparent def twice(f: Int => Int): Int = f(f(2)) + + val res = twice(double) // inlined as 8: Int + +} \ No newline at end of file diff --git a/tests/run/generic-tuples.scala b/tests/run/generic-tuples.scala new file mode 100644 index 000000000000..a112a87114fc --- /dev/null +++ b/tests/run/generic-tuples.scala @@ -0,0 +1,15 @@ +package tuples { + +trait Tuple + +/** () in stdlib */ +class HNil extends Tuple +case object HNil extends HNil + +trait Pair[H, T <: Tuple] { + erased transparent def size = ??? +} +} +class Test extends App { + +} \ No newline at end of file diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala new file mode 100644 index 000000000000..856fcdf203a4 --- /dev/null +++ b/tests/run/typelevel-overrides.scala @@ -0,0 +1,29 @@ +trait T { + def f(x: Int): Int +} +trait U extends T { + override def f(x: Int): Int = x + 1 +} +class A extends T { + def f(x: Int) = x +} +class B extends A { + override transparent def f(x: Int) = x match { + case 0 => 0 + case x => x + } +} +class C extends A with U { + override transparent def f(x: Int) = x match { + case 0 => 0 + case x => x + } +} +object Test extends App { + val a: A = new B + assert(a.f(0) == 0) + val b: B = new B + assert(b.f(0) == 0) + val c: A = new C + assert(c.f(0) == 1) +} \ No newline at end of file From 44feda7a1cdd48c9bd98d57daaa019a5f75bd187 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 22 Jul 2018 14:55:45 +0200 Subject: [PATCH 46/62] Fix test --- tests/run/generic-tuples.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/run/generic-tuples.scala b/tests/run/generic-tuples.scala index a112a87114fc..8a90a410db3d 100644 --- a/tests/run/generic-tuples.scala +++ b/tests/run/generic-tuples.scala @@ -10,6 +10,8 @@ trait Pair[H, T <: Tuple] { erased transparent def size = ??? } } -class Test extends App { + + +object Test extends App { } \ No newline at end of file From 57853b0dbee617edce8c07b89cc8f0f6d40cb03e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Jul 2018 15:01:47 +0200 Subject: [PATCH 47/62] Merge TypeLevel and Erased Flags Merge TypeLevel and Erased flags - they mean the same thing. To support this, and also to pass more tests involving typelevel/erased methods, we need to refactor the way erased decls are handled. We now do the following steps - In Typer: no special treatment - In PostTyper - Replace synthesized implicit arguments to erased parameters by default values. - Remove inlined code in erased contexts The aim of both of these transformations is to reduce code size, while not affecting discoverability in IDEs. - In CutErasedDecls - Make all erased symbols private, using a SymTransformer. This is needed so that erased symbols don't poison generation of forwarders (in ResolveSuper) or bridges (in Erasure). - Replace all code in erased contexts by default values. This is needed so that IsInstanceOfChecker works correctly. - In Erasure - Drop all erased definitions, and check that no references to erased symbols remain in the code. This particular sequence of actions was chosen to address a number of constraints: - We can't start to eliminate erased definitions before pattern matching is completed as this could hide errors - We must eliminate erased members before ResolveSuper - It's most convenient to eliminate erased arguments during erasure, since that keeps applications and types in sync Since there is no group of phases between PatternMatcher and ResolveSuper, we were forced to employ the split approach where we first make the symbol invisible by making it private and then remove it later, in erasure. --- .../backend/jvm/DottyBackendInterface.scala | 2 +- compiler/src/dotty/tools/dotc/Compiler.scala | 7 +- .../src/dotty/tools/dotc/core/Flags.scala | 6 +- .../src/dotty/tools/dotc/core/Symbols.scala | 22 ++++- .../src/dotty/tools/dotc/core/TypeOps.scala | 2 +- .../tools/dotc/core/tasty/TastyFormat.scala | 4 +- .../tools/dotc/core/tasty/TreePickler.scala | 1 - .../tools/dotc/core/tasty/TreeUnpickler.scala | 1 - .../tools/dotc/transform/CutErasedDecls.scala | 54 +++++++++++ .../tools/dotc/transform/DropInlined.scala | 15 ---- .../tools/dotc/transform/ErasedDecls.scala | 50 ----------- .../dotty/tools/dotc/transform/Erasure.scala | 89 ++++++++++++------- .../tools/dotc/transform/FirstTransform.scala | 17 +--- .../dotty/tools/dotc/transform/MixinOps.scala | 2 +- .../tools/dotc/transform/PostTyper.scala | 30 +++---- .../tools/dotc/transform/ResolveSuper.scala | 4 +- .../tools/dotc/transform/TreeChecker.scala | 9 ++ .../src/dotty/tools/dotc/typer/Checking.scala | 8 +- .../tools/dotc/typer/PrepareTransparent.scala | 2 +- .../dotty/tools/dotc/typer/RefChecks.scala | 4 +- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +- tests/neg/erased-4.scala | 4 +- tests/neg/erased-case-class.scala | 2 +- tests/neg/typelevel-noeta.scala | 6 ++ tests/pos/transparent-nested.scala | 4 +- tests/run/typelevel-overrides.scala | 2 +- 26 files changed, 194 insertions(+), 159 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/DropInlined.scala delete mode 100644 compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index e7a64880dd61..75efba2b1ef6 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -433,7 +433,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma val Flag_METHOD: Flags = Flags.Method.bits val ExcludedForwarderFlags: Flags = { Flags.Specialized | Flags.Lifted | Flags.Protected | Flags.JavaStatic | - Flags.Bridge | Flags.Private | Flags.TypeLevel | Flags.Macro + Flags.Bridge | Flags.Private | Flags.Macro }.bits def isQualifierSafeToElide(qual: Tree): Boolean = tpd.isIdempotentExpr(qual) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 99d2ee745535..f5f69a0ab485 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -73,15 +73,15 @@ class Compiler { new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. new RefChecks) :: // Various checks mostly related to abstract members and overriding - List(new TryCatchPatterns, // Compile cases in try/catch + List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions + new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations new CrossCastAnd, // Normalize selections involving intersection types. new Splitter) :: // Expand selections involving union types into conditionals - List(new ErasedDecls, // Removes all erased defs and vals decls (except for parameters) - new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf` + List(new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf` new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods @@ -119,7 +119,6 @@ class Compiler { new SelectStatic, // get rid of selects that would be compiled into GetStatic new CollectEntryPoints, // Find classes with main methods new CollectSuperCalls, // Find classes that are called with super - new DropInlined, // Drop Inlined nodes, since backend has no use for them new LabelDefs) :: // Converts calls to labels to jumps Nil diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 638ae24f60a1..db636b0282e6 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -351,9 +351,6 @@ object Flags { /** A bridge method. Set by Erasure */ final val Bridge = termFlag(34, "") - /** Symbol is a typelevel method, cannot be used in normal code */ - final val TypeLevel = termFlag(35, "") - /** Symbol is a method which should be marked ACC_SYNCHRONIZED */ final val Synchronized = termFlag(36, "") @@ -563,6 +560,9 @@ object Flags { /** A transparent method */ final val TransparentMethod = allOf(Transparent, Method) + /** An erased transparent method */ + final val TypeLevelMethod = allOf(Transparent, Erased, Method) + /** A transparent parameter */ final val TransparentParam = allOf(Transparent, Param) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index e16145d5b048..f00ca26f215e 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -546,11 +546,29 @@ object Symbols { if (this is Module) this.moduleClass.validFor |= InitialPeriod } else this.owner.asClass.ensureFreshScopeAfter(phase) - if (!isPrivate) - assert(phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase") + assert(isPrivate || phase.changesMembers, i"$this entered in ${this.owner} at undeclared phase $phase") entered } + /** Remove symbol from scope of owning class */ + final def drop()(implicit ctx: Context): Unit = { + this.owner.asClass.delete(this) + if (this is Module) this.owner.asClass.delete(this.moduleClass) + } + + /** Remove symbol from scope of owning class after given `phase`. Create a fresh + * denotation for its owner class if the class has not yet already one that starts being valid after `phase`. + * @pre Symbol is a class member + */ + def dropAfter(phase: DenotTransformer)(implicit ctx: Context): Unit = + if (ctx.phaseId != phase.next.id) dropAfter(phase)(ctx.withPhase(phase.next)) + else { + assert (!this.owner.is(Package)) + this.owner.asClass.ensureFreshScopeAfter(phase) + assert(isPrivate || phase.changesMembers, i"$this deleted in ${this.owner} at undeclared phase $phase") + drop() + } + /** This symbol, if it exists, otherwise the result of evaluating `that` */ def orElse(that: => Symbol)(implicit ctx: Context) = if (this.exists) this else that diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index ec3343d83884..ce039351201f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -271,7 +271,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. violations.toList } - /** Are we in a transparent method body */ + /** Are we in a transparent method body? */ def inTransparentMethod = owner.ownersIterator.exists(_.isTransparentMethod) /** Is `feature` enabled in class `owner`? diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala index 8bb4f5ee09ce..59dd7e4314a7 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyFormat.scala @@ -298,7 +298,7 @@ object TastyFormat { final val LAZY = 14 final val OVERRIDE = 15 final val TRANSPARENT = 16 - final val TYPELEVEL = 17 + final val STATIC = 18 final val OBJECT = 19 final val TRAIT = 20 @@ -476,7 +476,6 @@ object TastyFormat { | LAZY | OVERRIDE | TRANSPARENT - | TYPELEVEL | MACRO | STATIC | OBJECT @@ -533,7 +532,6 @@ object TastyFormat { case LAZY => "LAZY" case OVERRIDE => "OVERRIDE" case TRANSPARENT => "TRANSPARENT" - case TYPELEVEL => "TYPELEVEL" case MACRO => "MACRO" case STATIC => "STATIC" case OBJECT => "OBJECT" diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 05b49160a995..f4f202650877 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -603,7 +603,6 @@ class TreePickler(pickler: TastyPickler) { if (flags is Case) writeByte(CASE) if (flags is Override) writeByte(OVERRIDE) if (flags is Transparent) writeByte(TRANSPARENT) - if (flags is TypeLevel) writeByte(TYPELEVEL) if (flags is Macro) writeByte(MACRO) if (flags is JavaStatic) writeByte(STATIC) if (flags is Module) writeByte(OBJECT) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 2e74f349be9f..aafad008fef6 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -585,7 +585,6 @@ class TreeUnpickler(reader: TastyReader, case LAZY => addFlag(Lazy) case OVERRIDE => addFlag(Override) case TRANSPARENT => addFlag(Transparent) - case TYPELEVEL => addFlag(TypeLevel) case MACRO => addFlag(Macro) case STATIC => addFlag(JavaStatic) case OBJECT => addFlag(Module) diff --git a/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala new file mode 100644 index 000000000000..ffe2b004f7ff --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala @@ -0,0 +1,54 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts._ +import DenotTransformers.SymTransformer +import Flags._ +import SymDenotations._ +import Types._ +import typer.RefChecks +import MegaPhase.MiniPhase +import ast.tpd + +/** This phase makes all erased term members of classes private so that they cannot + * conflict with non-erased members. This is needed so that subsequent phases like + * ResolveSuper that inspect class members work correctly. + * The phase also replaces all expressions that appear in an erased context by + * default values. This is necessary so that subsequent checking phases such + * as IsInstanceOfChecker don't give false negatives. + */ +class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform => + import tpd._ + + override def phaseName = CutErasedDecls.name + + override def changesMembers = true // makes erased members private + + override def runsAfterGroupsOf = Set(RefChecks.name) + + override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = + if (sym.is(Erased, butNot = Private) && sym.owner.isClass) + sym.copySymDenotation( + //name = UnlinkedErasedName.fresh(sym.name.asTermName), + initFlags = sym.flags | Private) + else sym + + override def transformApply(tree: Apply)(implicit ctx: Context) = + if (tree.fun.tpe.widen.isErasedMethod) + cpy.Apply(tree)(tree.fun, tree.args.map(arg => defaultValue(arg.tpe))) + else tree + + override def transformValDef(tree: ValDef)(implicit ctx: Context) = + if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) + cpy.ValDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + else tree + + override def transformDefDef(tree: DefDef)(implicit ctx: Context) = + if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) + cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + else tree +} +object CutErasedDecls { + val name = "cutErasedDecls" +} diff --git a/compiler/src/dotty/tools/dotc/transform/DropInlined.scala b/compiler/src/dotty/tools/dotc/transform/DropInlined.scala deleted file mode 100644 index 55dec8628a48..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/DropInlined.scala +++ /dev/null @@ -1,15 +0,0 @@ -package dotty.tools.dotc -package transform - -import typer.Inliner -import core.Contexts.Context -import MegaPhase.MiniPhase - -/** Drop Inlined nodes */ -class DropInlined extends MiniPhase { - import ast.tpd._ - override def phaseName = "dropInlined" - - override def transformInlined(tree: Inlined)(implicit ctx: Context): Tree = - Inliner.dropInlined(tree) -} diff --git a/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala deleted file mode 100644 index c5756dcf3d8f..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/ErasedDecls.scala +++ /dev/null @@ -1,50 +0,0 @@ -package dotty.tools.dotc.transform - -import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Contexts._ -import dotty.tools.dotc.core.DenotTransformers.InfoTransformer -import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Phases.Phase -import dotty.tools.dotc.core.Types._ -import dotty.tools.dotc.transform.MegaPhase.MiniPhase - -/** This phase removes erased declarations of val`s (except for parameters). - * - * `erased val x = ...` are removed - */ -class ErasedDecls extends MiniPhase with InfoTransformer { - import tpd._ - - override def phaseName: String = "erasedDecls" - - override def runsAfterGroupsOf = Set( - PatternMatcher.name // Make sure pattern match errors are emitted - ) - - /** Check what the phase achieves, to be called at any point after it is finished. */ - override def checkPostCondition(tree: Tree)(implicit ctx: Context): Unit = tree match { - case tree: ValOrDefDef if !tree.symbol.is(Param) => assert(!tree.symbol.is(Erased, butNot = Param)) - case _ => - } - - - /* Tree transform */ - - override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) - override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = transformValOrDefDef(tree) - - private def transformValOrDefDef(tree: ValOrDefDef)(implicit ctx: Context): Tree = - if (tree.symbol.is(Erased, butNot = Param)) EmptyTree else tree - - - /* Info transform */ - - override def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { - case tp: ClassInfo => tp.derivedClassInfo(decls = tp.decls.filteredScope(!_.is(Erased))) - case _ => tp - } - - override protected def mayChange(sym: Symbol)(implicit ctx: Context): Boolean = - sym.isClass && !sym.is(JavaDefined) && !sym.is(Scala2x) -} diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 811ac9c9ce04..88ac3e9900bc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -16,6 +16,7 @@ import core.Decorators._ import core.Constants._ import core.Definitions._ import typer.NoChecking +import typer.Inliner import typer.ProtoTypes._ import typer.ErrorReporting._ import core.TypeErasure._ @@ -37,7 +38,7 @@ class Erasure extends Phase with DenotTransformer { /** List of names of phases that should precede this phase */ override def runsAfter = Set(InterceptedMethods.name, Splitter.name, ElimRepeated.name) - override def changesMembers: Boolean = true // the phase adds bridges + override def changesMembers: Boolean = true // the phase adds bridges override def changesParents: Boolean = true // the phase drops Any def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { @@ -317,6 +318,17 @@ object Erasure { class Typer(erasurePhase: DenotTransformer) extends typer.ReTyper with NoChecking { import Boxing._ + private def checkNotErased(tree: Tree)(implicit ctx: Context): tree.type = { + if (tree.symbol.is(Flags.Erased) && !ctx.mode.is(Mode.Type)) + ctx.error(em"${tree.symbol} is declared as erased, but is in fact used", tree.pos) + tree + } + + def erasedDef(sym: Symbol)(implicit ctx: Context) = { + if (sym.owner.isClass) sym.dropAfter(erasurePhase) + tpd.EmptyTree + } + def erasedType(tree: untpd.Tree)(implicit ctx: Context): Type = { val tp = tree.typeOpt if (tree.isTerm) erasedRef(tp) else valueErasure(tp) @@ -356,6 +368,10 @@ object Erasure { else super.typedLiteral(tree) + override def typedIdent(tree: untpd.Ident, pt: Type)(implicit ctx: Context): Tree = { + checkNotErased(super.typedIdent(tree, pt)) + } + /** Type check select nodes, applying the following rewritings exhaustively * on selections `e.m`, where `OT` is the type of the owner of `m` and `ET` * is the erased type of the selection's original qualifier expression. @@ -436,7 +452,7 @@ object Erasure { } } - recur(typed(tree.qualifier, AnySelectionProto)) + checkNotErased(recur(typed(tree.qualifier, AnySelectionProto))) } override def typedThis(tree: untpd.This)(implicit ctx: Context): Tree = @@ -532,43 +548,52 @@ object Erasure { } } - override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): ValDef = - super.typedValDef(untpd.cpy.ValDef(vdef)( - tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) + override def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree = + super.typedInlined(tree, pt) match { + case tree: Inlined => Inliner.dropInlined(tree) + } + + override def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = + if (sym.is(Flags.Erased)) erasedDef(sym) + else + super.typedValDef(untpd.cpy.ValDef(vdef)( + tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) /** Besides normal typing, this function also compacts anonymous functions * with more than `MaxImplementedFunctionArity` parameters to ise a single * parameter of type `[]Object`. */ - override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = { - val restpe = - if (sym.isConstructor) defn.UnitType - else sym.info.resultType - var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil - var rhs1 = ddef.rhs match { - case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) - case _ => ddef.rhs - } - if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { - val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) - def selector(n: Int) = ref(bunchedParam) - .select(defn.Array_apply) - .appliedTo(Literal(Constant(n))) - val paramDefs = vparamss1.head.zipWithIndex.map { - case (paramDef, idx) => - assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) + override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context): Tree = + if (sym.is(Flags.Erased)) erasedDef(sym) + else { + val restpe = + if (sym.isConstructor) defn.UnitType + else sym.info.resultType + var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil + var rhs1 = ddef.rhs match { + case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) + case _ => ddef.rhs } - vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil - rhs1 = untpd.Block(paramDefs, rhs1) + if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { + val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) + def selector(n: Int) = ref(bunchedParam) + .select(defn.Array_apply) + .appliedTo(Literal(Constant(n))) + val paramDefs = vparamss1.head.zipWithIndex.map { + case (paramDef, idx) => + assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) + } + vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil + rhs1 = untpd.Block(paramDefs, rhs1) + } + vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased))) + val ddef1 = untpd.cpy.DefDef(ddef)( + tparams = Nil, + vparamss = vparamss1, + tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), + rhs = rhs1) + super.typedDefDef(ddef1, sym) } - vparamss1 = vparamss1.mapConserve(_.filterConserve(!_.symbol.is(Flags.Erased))) - val ddef1 = untpd.cpy.DefDef(ddef)( - tparams = Nil, - vparamss = vparamss1, - tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), - rhs = rhs1) - super.typedDefDef(ddef1, sym) - } override def typedClosure(tree: untpd.Closure, pt: Type)(implicit ctx: Context) = { val xxl = defn.isXXLFunctionClass(tree.typeOpt.typeSymbol) diff --git a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala index daa56d8dc01d..cd3bec37f0da 100644 --- a/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala +++ b/compiler/src/dotty/tools/dotc/transform/FirstTransform.scala @@ -110,24 +110,13 @@ class FirstTransform extends MiniPhase with InfoTransformer { thisPhase => override def transformDefDef(ddef: DefDef)(implicit ctx: Context) = { val meth = ddef.symbol.asTerm - def disable(stubKind: String) = { + if (meth.hasAnnotation(defn.NativeAnnot)) { meth.resetFlag(Deferred) polyDefDef(meth, _ => _ => ref(defn.Sys_errorR).withPos(ddef.pos) - .appliedTo(Literal(Constant(s"$stubKind method stub")))) + .appliedTo(Literal(Constant(s"native method stub")))) + } - if (meth.hasAnnotation(defn.NativeAnnot)) disable("native") - else if (meth.is(TypeLevel)) - meth.allOverriddenSymbols.find(!_.is(Deferred)) match { - case Some(overridden) => - polyDefDef(meth, - targs => argss => - Super(This(meth.owner.asClass), tpnme.EMPTY, inConstrCall = false) - .select(overridden) - .appliedToTypes(targs).appliedToArgss(argss)) - case None => - disable("type level") - } else ddef } diff --git a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala index d3e08d7f9b7e..921e3762f186 100644 --- a/compiler/src/dotty/tools/dotc/transform/MixinOps.scala +++ b/compiler/src/dotty/tools/dotc/transform/MixinOps.scala @@ -44,7 +44,7 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont */ def isCurrent(sym: Symbol) = ctx.atPhase(thisPhase) { implicit ctx => - cls.info.member(sym.name).hasAltWith(_.symbol == sym) + cls.info.nonPrivateMember(sym.name).hasAltWith(_.symbol == sym) // this is a hot spot, where we spend several seconds while compiling stdlib // unfortunately it will discard and recompute all the member chaches, // both making itself slow and slowing down anything that runs after it diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index f8c3f6c90b2b..4c873094ec9b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -174,17 +174,22 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase } } + private object dropInlines extends TreeMap { + override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match { + case Inlined(call, _, _) => Typed(call, TypeTree(tree.tpe)) + case _ => super.transform(tree) + } + } + override def transform(tree: Tree)(implicit ctx: Context): Tree = try tree match { case tree: Ident if !tree.isType => - checkNotErased(tree) handleMeta(tree.symbol) tree.tpe match { case tpe: ThisType => This(tpe.cls).withPos(tree.pos) case _ => tree } case tree @ Select(qual, name) => - checkNotErased(tree) handleMeta(tree.symbol) if (name.isTypeName) { Checking.checkRealizable(qual.tpe, qual.pos.focus) @@ -193,9 +198,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase else transformSelect(tree, Nil) case tree: Apply => + val methType = tree.fun.tpe.widen val app = - if (tree.fun.tpe.widen.isErasedMethod) - tpd.cpy.Apply(tree)(tree.fun, tree.args.map(arg => defaultValue(arg.tpe))) + if (methType.isErasedMethod) + tpd.cpy.Apply(tree)( + tree.fun, + tree.args.map(arg => + if (methType.isImplicitMethod && arg.pos.isSynthetic) defaultValue(arg.tpe) + else dropInlines.transform(arg))) else tree methPart(app) match { @@ -217,7 +227,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case sel: Select => val args1 = transform(args) val sel1 = transformSelect(sel, args1) - checkNotErased(sel) cpy.TypeApply(tree1)(sel1, args1) case _ => super.transform(tree1) @@ -329,15 +338,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase * Performed to shrink the tree that is known to be erased later. */ private def normalizeErasedRhs(rhs: Tree, sym: Symbol)(implicit ctx: Context) = - if (sym.is(Erased) && rhs.tpe.exists) defaultValue(rhs.tpe) else rhs - - private def checkNotErased(tree: RefTree)(implicit ctx: Context): Unit = { - if (tree.symbol.is(Erased) && !ctx.mode.is(Mode.Type) && !ctx.inTransparentMethod) { - val msg = - if (tree.symbol.is(CaseAccessor)) "First parameter list of case class may not contain `erased` parameters" - else i"${tree.symbol} is declared as erased, but is in fact used" - ctx.error(msg, tree.pos) - } - } + if (sym.is(Erased, butNot = Deferred)) dropInlines.transform(rhs) else rhs } } diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 940ce9199a08..2947f6d9e83d 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -53,6 +53,8 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. AugmentScala2Traits.name) + override def runsAfterGroupsOf = Set(CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly + override def changesMembers = true // the phase adds super accessors and method forwarders override def transformTemplate(impl: Template)(implicit ctx: Context) = { @@ -90,8 +92,6 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = } else ddef } - - private val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred } object ResolveSuper { diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 47f725deccf1..5c5bbebb3504 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -259,6 +259,13 @@ class TreeChecker extends Phase with SymTransformer { case _ => } + /** Exclude from double definition checks any erased symbols that were + * made `private` in phase `UnlinkErasedDecls`. These symbols will be removed + * completely in phase `Erasure` if they are defined in a currently compiled unit. + */ + override def excludeFromDoubleDeclCheck(sym: Symbol)(implicit ctx: Context) = + sym.is(PrivateErased) && !sym.initial.is(Private) + override def typed(tree: untpd.Tree, pt: Type = WildcardType)(implicit ctx: Context): tpd.Tree = { val tpdTree = super.typed(tree, pt) checkIdentNotJavaClass(tpdTree) @@ -505,4 +512,6 @@ object TreeChecker { tp } }.apply(tp0) + + private val PrivateErased = allOf(Private, Erased) } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index f939eb262df4..ca2dd9b3c1dd 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -680,6 +680,9 @@ trait Checking { } } + /** A hook to exclude selected symbols from double declaration check */ + def excludeFromDoubleDeclCheck(sym: Symbol)(implicit ctx: Context) = false + /** Check that class does not declare same symbol twice */ def checkNoDoubleDeclaration(cls: Symbol)(implicit ctx: Context): Unit = { val seen = new mutable.HashMap[Name, List[Symbol]] { @@ -690,7 +693,7 @@ trait Checking { def checkDecl(decl: Symbol): Unit = { for (other <- seen(decl.name)) { typr.println(i"conflict? $decl $other") - if (decl.matches(other)) { + if (decl.matches(other) /*&& !decl.is(Erased) && !other.is(Erased)*/) { def doubleDefError(decl: Symbol, other: Symbol): Unit = if (!decl.info.isErroneous && !other.info.isErroneous) ctx.error(DoubleDeclaration(decl, other), decl.pos) @@ -702,7 +705,8 @@ trait Checking { decl resetFlag HasDefaultParams } } - seen(decl.name) = decl :: seen(decl.name) + if (!excludeFromDoubleDeclCheck(decl)) + seen(decl.name) = decl :: seen(decl.name) } cls.info.decls.foreach(checkDecl) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index f79e110b17b0..812ec7366c5c 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -48,7 +48,7 @@ object PrepareTransparent { def markTopLevelMatches(meth: Symbol, tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { case tree: untpd.Match => - meth.setFlag(TypeLevel) + meth.setFlag(Erased) tree.putAttachment(TopLevelMatch, ()) tree.cases.foreach(markTopLevelMatches(meth, _)) case tree: untpd.Block => diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 362d1544160f..50c3efc5bad9 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -376,8 +376,8 @@ object RefChecks { overrideError("may not override a non-lazy value") } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { overrideError("must be declared lazy to override a lazy value") - } else if (member.is(TypeLevel) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) - overrideError("is a type-level method, may not override only deferred methods") + } else if (member.is(Erased) && member.allOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) + overrideError("is an erased method, may not override only deferred methods") } else if (member.is(Macro, butNot = Scala2x)) { // (1.9) overrideError("is a macro, may not override anything") } else if (other.is(Deferred) && member.is(Scala2Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.10) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index b15dfc4127a3..e58a91285878 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1159,7 +1159,7 @@ class Typer extends Namer } } - def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Inlined = { + def typedInlined(tree: untpd.Inlined, pt: Type)(implicit ctx: Context): Tree = { val (exprCtx, bindings1) = typedBlockStats(tree.bindings) val expansion1 = typed(tree.expansion, pt)(inlineContext(tree.call)(exprCtx)) assignType(cpy.Inlined(tree)(tree.call, bindings1.asInstanceOf[List[MemberDef]], expansion1), @@ -1385,7 +1385,7 @@ class Typer extends Namer typed(annot, defn.AnnotationType) } - def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context) = track("typedValDef") { + def typedValDef(vdef: untpd.ValDef, sym: Symbol)(implicit ctx: Context): Tree = track("typedValDef") { val ValDef(name, tpt, _) = vdef completeAnnotations(vdef, sym) val tpt1 = checkSimpleKinded(typedType(tpt)) @@ -2351,7 +2351,7 @@ class Typer extends Namer // - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that) if (arity >= 0 && !tree.symbol.isConstructor && - !tree.symbol.is(TypeLevel) && + !tree.symbol.is(TypeLevelMethod) && !ctx.mode.is(Mode.Pattern) && !(isSyntheticApply(tree) && !isExpandableApply)) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) diff --git a/tests/neg/erased-4.scala b/tests/neg/erased-4.scala index 99a09de2c404..2c60ad303dae 100644 --- a/tests/neg/erased-4.scala +++ b/tests/neg/erased-4.scala @@ -1,6 +1,8 @@ object Test { def main(args: Array[String]): Unit = { + def foo (erased i: Int) = 0 + val f: erased Int => Int = erased (x: Int) => { x // error @@ -10,8 +12,6 @@ object Test { erased (x: Int) => { foo(x) } - - def foo (erased i: Int) = 0 } } diff --git a/tests/neg/erased-case-class.scala b/tests/neg/erased-case-class.scala index d23a09ba24f0..692534d772b6 100644 --- a/tests/neg/erased-case-class.scala +++ b/tests/neg/erased-case-class.scala @@ -1 +1 @@ -case class Foo1(erased x: Int) // error +case class Foo1(erased x: Int) // error // error diff --git a/tests/neg/typelevel-noeta.scala b/tests/neg/typelevel-noeta.scala index a858ac169fae..c25cd6971b6b 100644 --- a/tests/neg/typelevel-noeta.scala +++ b/tests/neg/typelevel-noeta.scala @@ -12,6 +12,12 @@ object Test { case _: Char => } + erased def test3(x: Int) = x + 1 + + def f(g: Int => Int) = g(0) + + f(test3) // OK, so far, normal erased functions can be eta-expanded + val x: Any = test // error test // error diff --git a/tests/pos/transparent-nested.scala b/tests/pos/transparent-nested.scala index c379da31bdaf..7c7bd10941f4 100644 --- a/tests/pos/transparent-nested.scala +++ b/tests/pos/transparent-nested.scala @@ -20,8 +20,8 @@ object Test0 { object Test1 { - transparent def f(x: Int) = { - transparent def g(x: Int) = x match { + erased transparent def f(x: Int) = { + erased transparent def g(x: Int) = x match { case 0 => 0 } g(0) diff --git a/tests/run/typelevel-overrides.scala b/tests/run/typelevel-overrides.scala index 856fcdf203a4..67a9512048f5 100644 --- a/tests/run/typelevel-overrides.scala +++ b/tests/run/typelevel-overrides.scala @@ -25,5 +25,5 @@ object Test extends App { val b: B = new B assert(b.f(0) == 0) val c: A = new C - assert(c.f(0) == 1) + assert(c.f(0) == 1, c.f(0)) } \ No newline at end of file From 0b7710a234384766ce13c18498d8b2b1cd6d7866 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 23 Jul 2018 23:33:37 +0200 Subject: [PATCH 48/62] Merge IsInstanceOfChecker and TypeTestsCasts It turns out it is difficult to find a good home for CutErasedDecls. Further tests revealed it has to run after the group of ExplicitOuter, since erased defs still need to be analyzed to see whether they require outer references to be generated. But it has also to run before checking whether InstanceOfs can be computed at runtime. The only way to accommodate that is to merge IsInstanceOfChecker with TypeTestsCasts, so that it is run at erasure. That's more coherent and shorter anyway. --- compiler/src/dotty/tools/dotc/Compiler.scala | 5 +- .../tools/dotc/transform/CutErasedDecls.scala | 2 +- .../dotc/transform/IsInstanceOfChecker.scala | 151 ------------------ .../tools/dotc/transform/ResolveSuper.scala | 5 +- .../tools/dotc/transform/TypeTestsCasts.scala | 123 +++++++++++++- 5 files changed, 124 insertions(+), 162 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index f5f69a0ab485..bfde8b6bca22 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -73,15 +73,14 @@ class Compiler { new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. new RefChecks) :: // Various checks mostly related to abstract members and overriding - List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions - new TryCatchPatterns, // Compile cases in try/catch + List(new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches new ExplicitOuter, // Add accessors to outer classes from nested ones. new ExplicitSelf, // Make references to non-trivial self types explicit as casts new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations new CrossCastAnd, // Normalize selections involving intersection types. new Splitter) :: // Expand selections involving union types into conditionals - List(new IsInstanceOfChecker, // check runtime realisability for `isInstanceOf` + List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods diff --git a/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala index ffe2b004f7ff..35782a9612c4 100644 --- a/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala +++ b/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala @@ -25,7 +25,7 @@ class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform => override def changesMembers = true // makes erased members private - override def runsAfterGroupsOf = Set(RefChecks.name) + override def runsAfterGroupsOf = Set(RefChecks.name, ExplicitOuter.name) override def transformSym(sym: SymDenotation)(implicit ctx: Context): SymDenotation = if (sym.is(Erased, butNot = Private) && sym.owner.isClass) diff --git a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala b/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala deleted file mode 100644 index f2d10f3fc677..000000000000 --- a/compiler/src/dotty/tools/dotc/transform/IsInstanceOfChecker.scala +++ /dev/null @@ -1,151 +0,0 @@ -package dotty.tools.dotc -package transform - -import util.Positions._ -import MegaPhase.MiniPhase -import core._ -import Contexts.Context, Types._, Decorators._, Symbols._, typer._, ast._, NameKinds._ -import TypeUtils._, Flags._ -import config.Printers.{ transforms => debug } - -/** Check runtime realizability of type test, see the documentation for `Checkable`. - */ -class IsInstanceOfChecker extends MiniPhase { - - import ast.tpd._ - - val phaseName = "isInstanceOfChecker" - - override def transformTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = { - def ensureCheckable(qual: Tree, pt: Tree): Tree = { - if (!Checkable.checkable(qual.tpe, pt.tpe, tree.pos)) - ctx.warning( - s"the type test for ${pt.show} cannot be checked at runtime", - tree.pos - ) - - tree - } - - tree.fun match { - case fn: Select - if fn.symbol == defn.Any_typeTest || fn.symbol == defn.Any_isInstanceOf => - ensureCheckable(fn.qualifier, tree.args.head) - case _ => tree - } - } -} - -object Checkable { - import Inferencing._ - import ProtoTypes._ - - /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? - * - * First do the following substitution: - * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType - * (b) replace pattern binder types (e.g., `_$1`) in X: - * - variance = 1 : hiBound - * - variance = -1 : loBound - * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds - * - * Then check: - * - * 1. if `X <:< P`, TRUE - * 2. if `P` is a singleton type, TRUE - * 3. if `P` refers to an abstract type member or type parameter, FALSE - * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. - * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: - * (a) replace `Ts` with fresh type variables `Xs` - * (b) constrain `Xs` with `pre.F[Xs] <:< X` - * (c) instantiate Xs and check `pre.F[Xs] <:< P` - * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). - * 7. if `P` is a refinement type, FALSE - * 8. otherwise, TRUE - */ - def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { - def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass - def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) - - def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap { - def apply(tp: Type) = tp match { - case tref: TypeRef - if isPatternTypeSymbol(tref.typeSymbol) => WildcardType - case AnnotatedType(_, annot) - if annot.symbol == defn.UncheckedAnnot => WildcardType - case _ => mapOver(tp) - } - }.apply(tp) - - def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap { - def apply(tp: Type) = tp match { - case tref: TypeRef - if isPatternTypeSymbol(tref.typeSymbol) => - if (variance == 1) tref.info.hiBound - else if (variance == -1) tref.info.loBound - else OrType(defn.AnyType, defn.NothingType) - case _ => mapOver(tp) - } - }.apply(tp) - - /** Approximate type parameters depending on variance */ - def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap { - def apply(tp: Type): Type = tp match { - case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] => - val lo = apply(tp.info.loBound) - val hi = apply(tp.info.hiBound) - range(lo, hi) - case _ => - mapOver(tp) - } - }.apply(tp) - - def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { - val AppliedType(tycon, _) = P - val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] - val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) - val P1 = tycon.appliedTo(tvars) - - debug.println("P : " + P) - debug.println("P1 : " + P1) - debug.println("X : " + X) - - P1 <:< X // constraint P1 - - // use fromScala2x to avoid generating pattern bound symbols - maximizeType(P1, pos, fromScala2x = true) - - val res = P1 <:< P - debug.println("P1 : " + P1) - debug.println("P1 <:< P = " + res) - - res - } - - def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { - case _: SingletonType => true - case _: TypeProxy - if isAbstract(P) => false - case defn.ArrayOf(tpT) => - X match { - case defn.ArrayOf(tpE) => recur(tpE, tpT) - case _ => recur(defn.AnyType, tpT) - } - case tpe: AppliedType => - // first try withou striping type parameters for performance - isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) || - isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState()) - case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) - case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) - case AnnotatedType(t, _) => recur(X, t) - case _: RefinedType => false - case _ => true - }) - - val res = recur(replaceX(X.widen), replaceP(P)) - - debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") - - res - } -} diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 2947f6d9e83d..243810df7ad2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -51,9 +51,8 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = override def phaseName: String = ResolveSuper.name override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. - AugmentScala2Traits.name) - - override def runsAfterGroupsOf = Set(CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly + AugmentScala2Traits.name, + CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly override def changesMembers = true // the phase adds super accessors and method forwarders diff --git a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala index 39ea428c5ac9..fa32333739cb 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeTestsCasts.scala @@ -4,6 +4,7 @@ package transform import core._ import Contexts._, Symbols._, Types._, Constants._, StdNames._, Decorators._ import ast.Trees._ +import ast.untpd import Erasure.Boxing._ import TypeErasure._ import ValueClasses._ @@ -12,7 +13,7 @@ import core.Flags._ import util.Positions._ import reporting.diagnostic.messages.TypeTestAlwaysSucceeds import reporting.trace - +import config.Printers.{ transforms => debug } /** This transform normalizes type tests and type casts, * also replacing type tests with singleton argument type with reference equality check @@ -21,12 +22,122 @@ import reporting.trace * - have a reference type as receiver * - can be translated directly to machine instructions * - * * Unfortunately this phase ended up being not Y-checkable unless types are erased. A cast to an ConstantType(3) or x.type - * cannot be rewritten before erasure. + * cannot be rewritten before erasure. That's why TypeTestsCasts is called from Erasure. */ object TypeTestsCasts { import ast.tpd._ + import typer.Inferencing.maximizeType + import typer.ProtoTypes.constrained + + /** Whether `(x:X).isInstanceOf[P]` can be checked at runtime? + * + * First do the following substitution: + * (a) replace `T @unchecked` and pattern binder types (e.g., `_$1`) in P with WildcardType + * (b) replace pattern binder types (e.g., `_$1`) in X: + * - variance = 1 : hiBound + * - variance = -1 : loBound + * - variance = 0 : OrType(Any, Nothing) // TODO: use original type param bounds + * + * Then check: + * + * 1. if `X <:< P`, TRUE + * 2. if `P` is a singleton type, TRUE + * 3. if `P` refers to an abstract type member or type parameter, FALSE + * 4. if `P = Array[T]`, checkable(E, T) where `E` is the element type of `X`, defaults to `Any`. + * 5. if `P` is `pre.F[Ts]` and `pre.F` refers to a class which is not `Array`: + * (a) replace `Ts` with fresh type variables `Xs` + * (b) constrain `Xs` with `pre.F[Xs] <:< X` + * (c) instantiate Xs and check `pre.F[Xs] <:< P` + * 6. if `P = T1 | T2` or `P = T1 & T2`, checkable(X, T1) && checkable(X, T2). + * 7. if `P` is a refinement type, FALSE + * 8. otherwise, TRUE + */ + def checkable(X: Type, P: Type, pos: Position)(implicit ctx: Context): Boolean = { + def isAbstract(P: Type) = !P.dealias.typeSymbol.isClass + def isPatternTypeSymbol(sym: Symbol) = !sym.isClass && sym.is(Case) + + def replaceP(tp: Type)(implicit ctx: Context) = new TypeMap { + def apply(tp: Type) = tp match { + case tref: TypeRef + if isPatternTypeSymbol(tref.typeSymbol) => WildcardType + case AnnotatedType(_, annot) + if annot.symbol == defn.UncheckedAnnot => WildcardType + case _ => mapOver(tp) + } + }.apply(tp) + + def replaceX(tp: Type)(implicit ctx: Context) = new TypeMap { + def apply(tp: Type) = tp match { + case tref: TypeRef + if isPatternTypeSymbol(tref.typeSymbol) => + if (variance == 1) tref.info.hiBound + else if (variance == -1) tref.info.loBound + else OrType(defn.AnyType, defn.NothingType) + case _ => mapOver(tp) + } + }.apply(tp) + + /** Approximate type parameters depending on variance */ + def stripTypeParam(tp: Type)(implicit ctx: Context) = new ApproximatingTypeMap { + def apply(tp: Type): Type = tp match { + case tp: TypeRef if tp.underlying.isInstanceOf[TypeBounds] => + val lo = apply(tp.info.loBound) + val hi = apply(tp.info.hiBound) + range(lo, hi) + case _ => + mapOver(tp) + } + }.apply(tp) + + def isClassDetermined(X: Type, P: AppliedType)(implicit ctx: Context) = { + val AppliedType(tycon, _) = P + val typeLambda = tycon.ensureLambdaSub.asInstanceOf[TypeLambda] + val tvars = constrained(typeLambda, untpd.EmptyTree, alwaysAddTypeVars = true)._2.map(_.tpe) + val P1 = tycon.appliedTo(tvars) + + debug.println("P : " + P) + debug.println("P1 : " + P1) + debug.println("X : " + X) + + P1 <:< X // constraint P1 + + // use fromScala2x to avoid generating pattern bound symbols + maximizeType(P1, pos, fromScala2x = true) + + val res = P1 <:< P + debug.println("P1 : " + P1) + debug.println("P1 <:< P = " + res) + + res + } + + def recur(X: Type, P: Type): Boolean = (X <:< P) || (P match { + case _: SingletonType => true + case _: TypeProxy + if isAbstract(P) => false + case defn.ArrayOf(tpT) => + X match { + case defn.ArrayOf(tpE) => recur(tpE, tpT) + case _ => recur(defn.AnyType, tpT) + } + case tpe: AppliedType => + // first try withou striping type parameters for performance + isClassDetermined(X, tpe)(ctx.fresh.setNewTyperState()) || + isClassDetermined(stripTypeParam(X), tpe)(ctx.fresh.setNewTyperState()) + case AndType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) + case OrType(tp1, tp2) => recur(X, tp1) && recur(X, tp2) + case AnnotatedType(t, _) => recur(X, t) + case _: RefinedType => false + case _ => true + }) + + val res = recur(replaceX(X.widen), replaceP(P)) + + debug.println(i"checking ${X.show} isInstanceOf ${P} = $res") + + res + } def interceptTypeApply(tree: TypeApply)(implicit ctx: Context): Tree = trace(s"transforming ${tree.show}", show = true) { tree.fun match { @@ -156,8 +267,12 @@ object TypeTestsCasts { transformIsInstanceOf(expr, erasure(testType), flagUnrelated) } - if (sym.isTypeTest) + if (sym.isTypeTest) { + val argType = tree.args.head.tpe + if (!checkable(expr.tpe, argType, tree.pos)) + ctx.warning(s"the type test for $argType cannot be checked at runtime", tree.pos) transformTypeTest(expr, tree.args.head.tpe, flagUnrelated = true) + } else if (sym eq defn.Any_asInstanceOf) transformAsInstanceOf(erasure(tree.args.head.tpe)) else tree From beca150687872c7749b87dabd591cdf75705de23 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 10:48:19 +0200 Subject: [PATCH 49/62] Fix inlining of right-associative transparent methods The stricter rules that transparent methods must be inlined revealed that right-associative curried methods such as /: were never inlined. To fix this: - take Blocks into account when deciding whether a tree is inlineable - fix typedApply, so that Blocks are moved outwards over curried applications - fix inlineCall so that we inline inside a block --- .../dotty/tools/dotc/typer/Applications.scala | 45 +++++++++++-------- .../src/dotty/tools/dotc/typer/Inliner.scala | 29 +++++++----- .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 0008aff5d044..c74c0a1d05c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -738,7 +738,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => } fun1.tpe match { - case err: ErrorType => untpd.cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) + case err: ErrorType => cpy.Apply(tree)(fun1, proto.typedArgs).withType(err) case TryDynamicCallType => typedDynamicApply(tree, pt) case _ => if (originalProto.isDropped) fun1 @@ -777,27 +777,34 @@ trait Applications extends Compatibility { self: Typer with Dynamic => wrapDefs(liftedDefs, typed(assign)) } - if (untpd.isOpAssign(tree)) - tryEither { - implicit ctx => realApply - } { (failedVal, failedState) => + val app1 = + if (untpd.isOpAssign(tree)) tryEither { - implicit ctx => typedOpAssign - } { (_, _) => - failedState.commit() - failedVal + implicit ctx => realApply + } { (failedVal, failedState) => + tryEither { + implicit ctx => typedOpAssign + } { (_, _) => + failedState.commit() + failedVal + } } + else { + val app = realApply + app match { + case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => + val op = fn.symbol + if (op == defn.Any_== || op == defn.Any_!=) + checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) + case _ => + } + app } - else { - val app = realApply - app match { - case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType => - val op = fn.symbol - if (op == defn.Any_== || op == defn.Any_!=) - checkCanEqual(left.tpe.widen, right.tpe.widen, app.pos) - case _ => - } - app + app1 match { + case Apply(Block(stats, fn), args) => + tpd.cpy.Block(app1)(stats, tpd.cpy.Apply(app1)(fn, args)) + case _ => + app1 } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 37e096ce716c..ab79038fa6e1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -67,6 +67,12 @@ object Inliner { hasBodyToInline(meth) && !suppressInline } + /** Should call be inlined in this context? */ + def isInlineable(tree: Tree)(implicit ctx: Context): Boolean = tree match { + case Block(_, expr) => isInlineable(expr) + case _ => isInlineable(tree.symbol) + } + /** Is `meth` a transparent method that should be inlined in this context? */ def isTransparentInlineable(meth: Symbol)(implicit ctx: Context): Boolean = meth.isTransparentInlineable && isInlineable(meth) @@ -79,8 +85,10 @@ object Inliner { * @return An `Inlined` node that refers to the original call and the inlined bindings * and body that replace it. */ - def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = - if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) { + def inlineCall(tree: Tree, pt: Type)(implicit ctx: Context): Tree = tree match { + case Block(stats, expr) => + cpy.Block(tree)(stats, inlineCall(expr, pt)) + case _ if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) => val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors if (ctx.reporter.hasErrors) tree else { @@ -89,14 +97,15 @@ object Inliner { else ctx.fresh.setProperty(InlineBindings, newMutableSymbolMap[Tree]) new Inliner(tree, body)(inlinerCtx).inlined(pt) } - } - else errorTree( - tree, - i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, - |Maybe this is caused by a recursive transparent method? - |You can use -Xmax:inlines to change the limit.""", - (tree :: enclosingInlineds).last.pos - ) + case _ => + errorTree( + tree, + i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded, + |Maybe this is caused by a recursive transparent method? + |You can use -Xmax:inlines to change the limit.""", + (tree :: enclosingInlineds).last.pos + ) + } /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: tpd.Inlined)(implicit ctx: Context): Tree = { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e58a91285878..d945dac97f2c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2378,7 +2378,7 @@ class Typer extends Namer checkEqualityEvidence(tree, pt) tree } - else if (Inliner.isInlineable(tree.symbol)) { + else if (Inliner.isInlineable(tree)) { tree.tpe <:< wildApprox(pt) readaptSimplified(Inliner.inlineCall(tree, pt)) } From 39cd368f94d374e62a82db8fe53ead11dfeb272b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 13:57:10 +0200 Subject: [PATCH 50/62] Make Transparent method imply Erased Transparent methods are now always assumed to be erased. This required updating quote a few tests. --- .../tools/dotc/core/CheckRealizable.scala | 2 +- .../src/dotty/tools/dotc/core/Flags.scala | 3 --- .../tools/dotc/transform/PostTyper.scala | 5 +++- .../dotty/tools/dotc/transform/Splicer.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 1 + .../tools/dotc/typer/PrepareTransparent.scala | 1 - .../src/dotty/tools/dotc/typer/Typer.scala | 2 +- library/src/dotty/runtime/LazyVals.scala | 16 ++++++------- tests/{pos => neg}/inline-i1773.scala | 2 +- tests/pos/i3873.scala | 2 +- tests/pos/inline-i2570.scala | 2 +- tests/pos/inline-t9232a.scala | 11 --------- tests/pos/inline-t9232b.scala | 23 ------------------- tests/pos/transparent.scala | 11 --------- tests/run/i2895.scala | 17 -------------- tests/run/transparentAssign.scala | 4 ++-- tests/run/transparentByName.scala | 2 +- tests/run/typelevel-patmat.scala | 14 +++-------- tests/run/typelevel.scala | 12 +++++----- tests/run/typelevel1.scala | 6 ++--- tests/run/typelevel3.scala | 6 ++--- 21 files changed, 37 insertions(+), 107 deletions(-) rename tests/{pos => neg}/inline-i1773.scala (76%) delete mode 100644 tests/pos/inline-t9232a.scala delete mode 100644 tests/pos/inline-t9232b.scala delete mode 100644 tests/pos/transparent.scala delete mode 100644 tests/run/i2895.scala diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index e4009c863738..48c842a284f5 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -158,7 +158,7 @@ class CheckRealizable(implicit ctx: Context) { private def memberRealizability(tp: Type) = { def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = sofar andAlso { - if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy | Erased)) + if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | LateInitialized)) // if field is private it cannot be part of a visible path // if field is mutable it cannot be part of a path // if field is lazy or erased it does not need to be initialized when the owning object is diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index db636b0282e6..399685e67f96 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -560,9 +560,6 @@ object Flags { /** A transparent method */ final val TransparentMethod = allOf(Transparent, Method) - /** An erased transparent method */ - final val TypeLevelMethod = allOf(Transparent, Erased, Method) - /** A transparent parameter */ final val TransparentParam = allOf(Transparent, Param) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 4c873094ec9b..3a17556e8700 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -165,7 +165,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def markAsMacro(c: Context): Unit = if (c.owner eq c.outer.owner) markAsMacro(c.outer) - else if (c.owner.isTransparentInlineable) c.owner.setFlag(Macro) + else if (c.owner.isTransparentMethod) { + c.owner.setFlag(Macro) + c.owner.resetFlag(Erased) // FIXME: Macros should be Erased, but that causes problems right now + } else if (!c.outer.owner.is(Package)) markAsMacro(c.outer) if (sym.isSplice || sym.isQuote) { diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 3c121c0517c7..7c4b942429f7 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -154,7 +154,7 @@ object Splicer { try clazz.getMethod(name.toString, paramClasses: _*) catch { case _: NoSuchMethodException => - val msg = s"Could not find transparent macro method ${clazz.getCanonicalName}.$name with parameters $paramClasses$extraMsg" + val msg = em"Could not find macro method ${clazz.getCanonicalName}.$name with parameters ($paramClasses%, %)$extraMsg" throw new StopInterpretation(msg, pos) } } diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index b7fc00b6d290..5423a9aa5503 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1195,6 +1195,7 @@ class Namer { typer: Typer => instantiateDependent(restpe, typeParams, termParamss) ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } + if (sym.is(Transparent)) sym.setFlag(Erased) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 812ec7366c5c..68d6ef02900a 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -48,7 +48,6 @@ object PrepareTransparent { def markTopLevelMatches(meth: Symbol, tree: untpd.Tree)(implicit ctx: Context): Unit = tree match { case tree: untpd.Match => - meth.setFlag(Erased) tree.putAttachment(TopLevelMatch, ()) tree.cases.foreach(markTopLevelMatches(meth, _)) case tree: untpd.Block => diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d945dac97f2c..b367146e3ffc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2351,7 +2351,7 @@ class Typer extends Namer // - the current tree is a synthetic apply which is not expandable (eta-expasion would simply undo that) if (arity >= 0 && !tree.symbol.isConstructor && - !tree.symbol.is(TypeLevelMethod) && + !tree.symbol.is(TransparentMethod) && !ctx.mode.is(Mode.Pattern) && !(isSyntheticApply(tree) && !isExpandableApply)) simplify(typed(etaExpand(tree, wtp, arity), pt), pt, locked) diff --git a/library/src/dotty/runtime/LazyVals.scala b/library/src/dotty/runtime/LazyVals.scala index 04114a156c06..b9533230c836 100644 --- a/library/src/dotty/runtime/LazyVals.scala +++ b/library/src/dotty/runtime/LazyVals.scala @@ -25,20 +25,20 @@ object LazyVals { final val LAZY_VAL_MASK = 3L final val debug = false - @forceInline def STATE(cur: Long, ord: Int) = { + @inline def STATE(cur: Long, ord: Int) = { val r = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK if (debug) println(s"STATE($cur, $ord) = $r") r } - @forceInline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = { + @inline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = { if (debug) println(s"CAS($t, $offset, $e, $v, $ord)") val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL)) compareAndSet(t, offset, e, n) } - @forceInline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = { + @inline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = { if (debug) println(s"setFlag($t, $offset, $v, $ord)") var retry = true @@ -57,7 +57,7 @@ object LazyVals { } } } - @forceInline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = { + @inline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = { if (debug) println(s"wait4Notification($t, $offset, $cur, $ord)") var retry = true @@ -75,8 +75,8 @@ object LazyVals { } } - @forceInline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v) - @forceInline def get(t: Object, off: Long) = { + @inline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v) + @inline def get(t: Object, off: Long) = { if (debug) println(s"get($t, $off)") unsafe.getLongVolatile(t, off) @@ -88,7 +88,7 @@ object LazyVals { x => new Object() }.toArray - @forceInline def getMonitor(obj: Object, fieldId: Int = 0) = { + @inline def getMonitor(obj: Object, fieldId: Int = 0) = { var id = ( /*java.lang.System.identityHashCode(obj) + */ // should be here, but #548 fieldId) % base @@ -97,7 +97,7 @@ object LazyVals { monitors(id) } - @forceInline def getOffset(clz: Class[_], name: String) = { + @inline def getOffset(clz: Class[_], name: String) = { val r = unsafe.objectFieldOffset(clz.getDeclaredField(name)) if (debug) println(s"getOffset($clz, $name) = $r") diff --git a/tests/pos/inline-i1773.scala b/tests/neg/inline-i1773.scala similarity index 76% rename from tests/pos/inline-i1773.scala rename to tests/neg/inline-i1773.scala index adf601f3d69e..9e377b23213f 100644 --- a/tests/pos/inline-i1773.scala +++ b/tests/neg/inline-i1773.scala @@ -7,7 +7,7 @@ object Test { } def main(args: Array[String]): Unit = { - val q"class $name extends $parent" = new Object + val q"class $name extends $parent" = new Object // error: method unapply is used println(name) println(parent) } diff --git a/tests/pos/i3873.scala b/tests/pos/i3873.scala index 850ef1687097..ea9aa41c8ec6 100644 --- a/tests/pos/i3873.scala +++ b/tests/pos/i3873.scala @@ -2,5 +2,5 @@ object Test { transparent def sum2(ys: List[Int]): Unit = { ys.foldLeft(1) } - val h1: ((List[Int]) => Unit) = sum2 + val h1 = (xs: List[Int]) => sum2(xs) } diff --git a/tests/pos/inline-i2570.scala b/tests/pos/inline-i2570.scala index d5d4859d4829..e520878c356c 100644 --- a/tests/pos/inline-i2570.scala +++ b/tests/pos/inline-i2570.scala @@ -1,4 +1,4 @@ object Test { transparent def sum2(ys: List[Int]): Int = (1 /: ys)(_ + _) - val h1: ((List[Int]) => Int) = sum2 + val h1 = (xs: List[Int]) => sum2(xs) } diff --git a/tests/pos/inline-t9232a.scala b/tests/pos/inline-t9232a.scala deleted file mode 100644 index 2a9dcdb14cf6..000000000000 --- a/tests/pos/inline-t9232a.scala +++ /dev/null @@ -1,11 +0,0 @@ -final class Foo(val value: Int) - -object Foo { - transparent def unapply(foo: Foo): Some[Int] = Some(foo.value) -} - -object Test { - def transformTree(f: Foo): Any = f match { - case Foo(_) => ??? - } -} diff --git a/tests/pos/inline-t9232b.scala b/tests/pos/inline-t9232b.scala deleted file mode 100644 index a833e8e62f7c..000000000000 --- a/tests/pos/inline-t9232b.scala +++ /dev/null @@ -1,23 +0,0 @@ -final class Foo(val value: Int) - -object Foo { - transparent def unapplySeq(foo: Foo): Some[Seq[Int]] = Some(List(foo.value)) -} - -sealed trait Tree -case class Node1(foo: Foo) extends Tree -case class Node2() extends Tree - -object Test { - def transformTree(tree: Tree): Any = tree match { - case Node1(Foo(_: _*)) => ??? - } - - def transformTree2(tree: Tree): Any = tree match { - case Node1(Foo(1, _: _*)) => ??? - } - - def transformTree3(tree: Tree): Any = tree match { - case Node1(Foo(x, _: _*)) => ??? - } -} diff --git a/tests/pos/transparent.scala b/tests/pos/transparent.scala deleted file mode 100644 index 37501641f0eb..000000000000 --- a/tests/pos/transparent.scala +++ /dev/null @@ -1,11 +0,0 @@ -object Test { - - object double extends (Int => Int) { - transparent def apply(x: Int) = x * 2 - } - - transparent def twice(f: Int => Int): Int = f(f(2)) - - val res = twice(double) // inlined as 8: Int - -} \ No newline at end of file diff --git a/tests/run/i2895.scala b/tests/run/i2895.scala deleted file mode 100644 index e68b918c03b5..000000000000 --- a/tests/run/i2895.scala +++ /dev/null @@ -1,17 +0,0 @@ -object Foo extends (Int => Int) { - transparent def apply(x: Int): Int = impl(x) - def impl(x: Int): Int = x + 1 -} - -object Test { - - def test(foo: Foo.type): Int = foo(41) - - def test2(f: Int => Int): Int = f(41) - - def main(args: Array[String]): Unit = { - assert(test(Foo) == 42) - assert(test2(Foo) == 42) - } - -} diff --git a/tests/run/transparentAssign.scala b/tests/run/transparentAssign.scala index 89147c554305..5851550f857f 100644 --- a/tests/run/transparentAssign.scala +++ b/tests/run/transparentAssign.scala @@ -10,8 +10,8 @@ object Test { def main(args: Array[String]) = { var x = 1 var y = 2 - transparent def setX(z: Int) = x = z - transparent def setY(z: Int) = y = z + def setX(z: Int) = x = z + def setY(z: Int) = y = z swap(x, setX, y, setY) assert(x == 2 && y == 1) diff --git a/tests/run/transparentByName.scala b/tests/run/transparentByName.scala index b45451d4feda..837ecc7ff26a 100644 --- a/tests/run/transparentByName.scala +++ b/tests/run/transparentByName.scala @@ -25,7 +25,7 @@ object Test { assert(j == 45, j) twice { x => j = j - x } thrice { j = j + 1 } - val f = new Range(1, 10).foreach + val f = (g: Int => Unit) => new Range(1, 10).foreach(g) f(j -= _) assert(j == 0, j) new Range(1, 10).foreach { i1 => diff --git a/tests/run/typelevel-patmat.scala b/tests/run/typelevel-patmat.scala index f64019831d49..6bacf57e2037 100644 --- a/tests/run/typelevel-patmat.scala +++ b/tests/run/typelevel-patmat.scala @@ -3,17 +3,9 @@ object typelevel { case class Typed[T](value: T) { type Type = T } } -trait Nat { - def toInt: Int -} - -case object Z extends Nat { - transparent def toInt = 0 -} - -case class S[N <: Nat](n: N) extends Nat { - transparent def toInt = n.toInt + 1 -} +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat trait HList diff --git a/tests/run/typelevel.scala b/tests/run/typelevel.scala index 03fa1673820b..ee7bf50521f2 100644 --- a/tests/run/typelevel.scala +++ b/tests/run/typelevel.scala @@ -1,18 +1,18 @@ trait Nat { - def toInt: Int + def toInt: Int = ??? } case object Z extends Nat { - transparent def toInt = 0 + transparent override def toInt = 0 } case class S[N <: Nat](n: N) extends Nat { - transparent def toInt = n.toInt + 1 + transparent override def toInt = n.toInt + 1 } trait HList { - def length: Int + def length: Int = ??? def head: Any def tail: HList transparent def isEmpty: Boolean = @@ -21,7 +21,7 @@ trait HList { // () case object HNil extends HList { - transparent def length = 0 + transparent override def length = 0 def head: Nothing = ??? def tail: Nothing = ??? } @@ -29,7 +29,7 @@ case object HNil extends HList { // (H, T) @annotation.showAsInfix(true) case class HCons [H, T <: HList](hd: H, tl: T) extends HList { - transparent def length = 1 + tl.length + transparent override def length = 1 + tl.length def head: H = this.hd def tail: T = this.tl } diff --git a/tests/run/typelevel1.scala b/tests/run/typelevel1.scala index 3a490b596384..d6de13790965 100644 --- a/tests/run/typelevel1.scala +++ b/tests/run/typelevel1.scala @@ -1,6 +1,6 @@ trait HList { - def length: Int + def length: Int = ??? def head: Any def tail: HList @@ -8,13 +8,13 @@ trait HList { } case object HNil extends HList { - transparent def length = 0 + transparent override def length = 0 def head: Nothing = ??? def tail: Nothing = ??? } case class :: [H, T <: HList] (hd: H, tl: T) extends HList { - transparent def length = 1 + tl.length + transparent override def length = 1 + tl.length def head: H = this.hd def tail: T = this.tl } diff --git a/tests/run/typelevel3.scala b/tests/run/typelevel3.scala index 9e6dc742e375..ca115f81ad6b 100644 --- a/tests/run/typelevel3.scala +++ b/tests/run/typelevel3.scala @@ -1,6 +1,6 @@ trait HList { - def length: Int + def length: Int = 0 def head: Any def tail: HList @@ -8,13 +8,13 @@ trait HList { } case object HNil extends HList { - transparent def length = 0 + transparent override def length = 0 def head: Nothing = ??? def tail: Nothing = ??? } case class HCons[H, T <: HList](hd: H, tl: T) extends HList { - transparent def length = 1 + tl.length + transparent override def length = 1 + tl.length def head: H = this.hd def tail: T = this.tl } From 5d8448696655ca1f448360a94e46821f516729e5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 15:32:40 +0200 Subject: [PATCH 51/62] Use ??? instead of defaultValue for erased code --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 2 +- .../{CutErasedDecls.scala => PruneErasedDecls.scala} | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) rename compiler/src/dotty/tools/dotc/transform/{CutErasedDecls.scala => PruneErasedDecls.scala} (89%) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 3a17556e8700..7d30e3260dd3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -207,7 +207,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase tpd.cpy.Apply(tree)( tree.fun, tree.args.map(arg => - if (methType.isImplicitMethod && arg.pos.isSynthetic) defaultValue(arg.tpe) + if (methType.isImplicitMethod && arg.pos.isSynthetic) ref(defn.Predef_undefined) else dropInlines.transform(arg))) else tree diff --git a/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala similarity index 89% rename from compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala rename to compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala index 35782a9612c4..529bbaf4cfd0 100644 --- a/compiler/src/dotty/tools/dotc/transform/CutErasedDecls.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala @@ -6,6 +6,7 @@ import Contexts._ import DenotTransformers.SymTransformer import Flags._ import SymDenotations._ +import Symbols._ import Types._ import typer.RefChecks import MegaPhase.MiniPhase @@ -36,17 +37,17 @@ class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform => override def transformApply(tree: Apply)(implicit ctx: Context) = if (tree.fun.tpe.widen.isErasedMethod) - cpy.Apply(tree)(tree.fun, tree.args.map(arg => defaultValue(arg.tpe))) + cpy.Apply(tree)(tree.fun, tree.args.map(arg => ref(defn.Predef_undefined))) else tree override def transformValDef(tree: ValDef)(implicit ctx: Context) = if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) - cpy.ValDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + cpy.ValDef(tree)(rhs = ref(defn.Predef_undefined)) else tree override def transformDefDef(tree: DefDef)(implicit ctx: Context) = if (tree.symbol.is(Erased) && !tree.rhs.isEmpty) - cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe)) + cpy.DefDef(tree)(rhs = ref(defn.Predef_undefined)) else tree } object CutErasedDecls { From 1112b5ecb2fde484c32a3b532abb80249ec737db Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 15:33:30 +0200 Subject: [PATCH 52/62] Enter implicit scrutinee ... so that it can be found in FromTasty compilatiom --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index c5a58b820111..d8e9ebef4258 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -334,7 +334,7 @@ class Definitions { lazy val RuntimeNullModuleRef = ctx.requiredModuleRef("scala.runtime.Null") lazy val ImplicitScrutineeTypeSym = - newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty) + newSymbol(ScalaPackageClass, tpnme.IMPLICITkw, EmptyFlags, TypeBounds.empty).entered def ImplicitScrutineeTypeRef: TypeRef = ImplicitScrutineeTypeSym.typeRef lazy val ScalaPredefModuleRef = ctx.requiredModuleRef("scala.Predef") @@ -1217,7 +1217,7 @@ class Definitions { /** Lists core methods that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */ lazy val syntheticCoreMethods = - AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod) + AnyMethods ++ ObjectMethods ++ List(String_+, throwMethod, ImplicitScrutineeTypeSym) lazy val reservedScalaClassNames: Set[Name] = syntheticScalaClasses.map(_.name).toSet From 1e9e474cc39bcad3c93ef73635c2a7fcb6922a13 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 15:46:08 +0200 Subject: [PATCH 53/62] Renaming --- compiler/src/dotty/tools/dotc/Compiler.scala | 2 +- .../{PruneErasedDecls.scala => PruneErasedDefs.scala} | 8 ++++---- .../src/dotty/tools/dotc/transform/ResolveSuper.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename compiler/src/dotty/tools/dotc/transform/{PruneErasedDecls.scala => PruneErasedDefs.scala} (90%) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index bfde8b6bca22..7e5ef0d7d2a3 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -80,7 +80,7 @@ class Compiler { new StringInterpolatorOpt, // Optimizes raw and s string interpolators by rewriting them to string concatentations new CrossCastAnd, // Normalize selections involving intersection types. new Splitter) :: // Expand selections involving union types into conditionals - List(new CutErasedDecls, // Drop erased definitions from scopes and simplify erased expressions + List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions new VCInlineMethods, // Inlines calls to value class methods new SeqLiterals, // Express vararg arguments as arrays new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods diff --git a/compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala similarity index 90% rename from compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala rename to compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala index 529bbaf4cfd0..38a05099da02 100644 --- a/compiler/src/dotty/tools/dotc/transform/PruneErasedDecls.scala +++ b/compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala @@ -19,10 +19,10 @@ import ast.tpd * default values. This is necessary so that subsequent checking phases such * as IsInstanceOfChecker don't give false negatives. */ -class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform => +class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform => import tpd._ - override def phaseName = CutErasedDecls.name + override def phaseName = PruneErasedDefs.name override def changesMembers = true // makes erased members private @@ -50,6 +50,6 @@ class CutErasedDecls extends MiniPhase with SymTransformer { thisTransform => cpy.DefDef(tree)(rhs = ref(defn.Predef_undefined)) else tree } -object CutErasedDecls { - val name = "cutErasedDecls" +object PruneErasedDefs { + val name = "pruneErasedDefs" } diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 243810df7ad2..4a9e4409ffa6 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -52,7 +52,7 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase = override def runsAfter = Set(ElimByName.name, // verified empirically, need to figure out what the reason is. AugmentScala2Traits.name, - CutErasedDecls.name) // Erased decls make `isCurrent` work incorrectly + PruneErasedDefs.name) // Erased decls make `isCurrent` work incorrectly override def changesMembers = true // the phase adds super accessors and method forwarders From e4863844f65c4dfb7001516b6c59eb0bb2185ef5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 16:50:56 +0200 Subject: [PATCH 54/62] Treat @forceInline specially. The comment in Namer explains why this is necessary. --- compiler/src/dotty/tools/dotc/typer/Namer.scala | 10 +++++++++- library/src/dotty/runtime/LazyVals.scala | 16 ++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5423a9aa5503..bca80fad2687 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1195,7 +1195,15 @@ class Namer { typer: Typer => instantiateDependent(restpe, typeParams, termParamss) ctx.methodType(tparams map symbolOfTree, termParamss, restpe, isJava = ddef.mods is JavaDefined) } - if (sym.is(Transparent)) sym.setFlag(Erased) + if (sym.is(Transparent) && + sym.unforcedAnnotation(defn.ForceInlineAnnot).isEmpty) + // Need to keep @forceInline annotated methods around to get to parity with Scala. + // This is necessary at least until we have full bootstrap. Right now + // dotty-bootstrapped involves running the Dotty compiler compiled with Scala 2 with + // a Dotty runtime library compiled with Dotty. If we erase @forceInline annotated + // methods, this means that the support methods in dotty.runtime.LazyVals vanish. + // But they are needed for running the lazy val implementations in the Scala-2 compiled compiler. + sym.setFlag(Erased) if (isConstructor) { // set result type tree to unit, but take the current class as result type of the symbol typedAheadType(ddef.tpt, defn.UnitType) diff --git a/library/src/dotty/runtime/LazyVals.scala b/library/src/dotty/runtime/LazyVals.scala index b9533230c836..04114a156c06 100644 --- a/library/src/dotty/runtime/LazyVals.scala +++ b/library/src/dotty/runtime/LazyVals.scala @@ -25,20 +25,20 @@ object LazyVals { final val LAZY_VAL_MASK = 3L final val debug = false - @inline def STATE(cur: Long, ord: Int) = { + @forceInline def STATE(cur: Long, ord: Int) = { val r = (cur >> (ord * BITS_PER_LAZY_VAL)) & LAZY_VAL_MASK if (debug) println(s"STATE($cur, $ord) = $r") r } - @inline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = { + @forceInline def CAS(t: Object, offset: Long, e: Long, v: Int, ord: Int) = { if (debug) println(s"CAS($t, $offset, $e, $v, $ord)") val mask = ~(LAZY_VAL_MASK << ord * BITS_PER_LAZY_VAL) val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL)) compareAndSet(t, offset, e, n) } - @inline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = { + @forceInline def setFlag(t: Object, offset: Long, v: Int, ord: Int) = { if (debug) println(s"setFlag($t, $offset, $v, $ord)") var retry = true @@ -57,7 +57,7 @@ object LazyVals { } } } - @inline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = { + @forceInline def wait4Notification(t: Object, offset: Long, cur: Long, ord: Int) = { if (debug) println(s"wait4Notification($t, $offset, $cur, $ord)") var retry = true @@ -75,8 +75,8 @@ object LazyVals { } } - @inline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v) - @inline def get(t: Object, off: Long) = { + @forceInline def compareAndSet(t: Object, off: Long, e: Long, v: Long) = unsafe.compareAndSwapLong(t, off, e, v) + @forceInline def get(t: Object, off: Long) = { if (debug) println(s"get($t, $off)") unsafe.getLongVolatile(t, off) @@ -88,7 +88,7 @@ object LazyVals { x => new Object() }.toArray - @inline def getMonitor(obj: Object, fieldId: Int = 0) = { + @forceInline def getMonitor(obj: Object, fieldId: Int = 0) = { var id = ( /*java.lang.System.identityHashCode(obj) + */ // should be here, but #548 fieldId) % base @@ -97,7 +97,7 @@ object LazyVals { monitors(id) } - @inline def getOffset(clz: Class[_], name: String) = { + @forceInline def getOffset(clz: Class[_], name: String) = { val r = unsafe.objectFieldOffset(clz.getDeclaredField(name)) if (debug) println(s"getOffset($clz, $name) = $r") From 33523d189c9fd010996e95075f1b0e4f2c05847a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 18:41:52 +0200 Subject: [PATCH 55/62] Redo transparent overloading resolution also for Idents as refs So far this was only done if the reference was a Select. --- .../tools/dotc/typer/PrepareTransparent.scala | 17 ++++++++++---- tests/pos/transparent-overload.scala | 22 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 tests/pos/transparent-overload.scala diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index 68d6ef02900a..ec312c43de71 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -307,13 +307,22 @@ object PrepareTransparent { } def traverse(tree: Tree)(implicit ctx: Context): Unit = { + val sym = tree.symbol tree match { case Ident(nme.WILDCARD) => case _: Ident | _: This => //println(i"leaf: $tree at ${tree.pos}") - if (tree.symbol.exists && !isLocal(tree.symbol, inlineMethod)) { + if (sym.exists && !isLocal(sym, inlineMethod)) { if (ctx.debug) inlining.println(i"type at $tree @ ${tree.pos.toSynthetic} = ${tree.tpe}") - typeAtPos(tree.pos.toSynthetic) = tree.tpe + tree.tpe match { + case tp: NamedType if tp.prefix.member(sym.name).isOverloaded => + // refer to prefix instead of to ident directly, so that overloading can be resolved + // again at expansion site + println(i"RECORD start for $tree") + typeAtPos(tree.pos.startPos) = tp.prefix + case _ => + typeAtPos(tree.pos.toSynthetic) = tree.tpe + } // Note: It's possible that during traversals several types are stored under the same // position. This could happen for instance for implicit conersions added around a tree. // In general, it's always the innermost tree that holds the relevant type. The traversal @@ -321,13 +330,13 @@ object PrepareTransparent { // stored types. } case _: Select => - tree.symbol.name match { + sym.name match { case InlineAccessorName(UniqueInlineName(_, _)) => return // was already recorded in Apply case InlineAccessorName(_) => registerAccessor(tree) case _ => } case Apply(_: RefTree | _: TypeApply, receiver :: Nil) => - tree.symbol.name match { + sym.name match { case InlineAccessorName(UniqueInlineName(_, _)) => registerAccessor(tree) case _ => } diff --git a/tests/pos/transparent-overload.scala b/tests/pos/transparent-overload.scala new file mode 100644 index 000000000000..66189d16e6d9 --- /dev/null +++ b/tests/pos/transparent-overload.scala @@ -0,0 +1,22 @@ +object Test { + + def f(x: Int): Int = x + def f(x: String): String = x + def f(x: Any): Any = x + + transparent def g(x: Any) = f(x) + transparent def h(x: Any) = this.f(x) + + locally { + val x1 = g(1) + val x2 = g("bb") + val y1: Int = x1 + val y2: String = x2 + } + locally { + val x1 = h(1) + val x2 = h("bb") + val y1: Int = x1 + val y2: String = x2 + } +} \ No newline at end of file From 43f4b55225bd84d3472b94ca87dfe17f98977ad5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 19:11:50 +0200 Subject: [PATCH 56/62] New test and drop println --- .../tools/dotc/typer/PrepareTransparent.scala | 1 - tests/run/typelevel-vector1.scala | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tests/run/typelevel-vector1.scala diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala index ec312c43de71..927ce6f4f053 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareTransparent.scala @@ -318,7 +318,6 @@ object PrepareTransparent { case tp: NamedType if tp.prefix.member(sym.name).isOverloaded => // refer to prefix instead of to ident directly, so that overloading can be resolved // again at expansion site - println(i"RECORD start for $tree") typeAtPos(tree.pos.startPos) = tp.prefix case _ => typeAtPos(tree.pos.toSynthetic) = tree.tpe diff --git a/tests/run/typelevel-vector1.scala b/tests/run/typelevel-vector1.scala new file mode 100644 index 000000000000..d48dadd12331 --- /dev/null +++ b/tests/run/typelevel-vector1.scala @@ -0,0 +1,30 @@ +object typelevel { + case class Typed[T](value: T) { type Type = T } + erased def erasedValue[T]: T = ??? +} + +trait Nat +case object Z extends Nat +case class S[N <: Nat](n: N) extends Nat + +case class Vec[T, N <: Nat](elems: List[T]) + +object Test { + import typelevel._ + type Z = Z.type + + transparent def add(x: Nat, y: Nat): Nat = x match { + case Z => y + case S(x1) => S(add(x1, y)) + } + + val x = S(S(Z)) + val y = add(x, x) + val z: S[S[S[S[Z]]]] = y + + transparent def concat[T, N1 <: Nat, N2 <: Nat](xs: Vec[T, N1], ys: Vec[T, N2]): Vec[T, _] = { + val length = Typed(add(erasedValue[N1], erasedValue[N2])) + Vec[T, length.Type](xs.elems ++ ys.elems) + } +} + From bf5e9c89d48e02bcf45fabb7a4a846c544bd9cb9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 24 Jul 2018 23:48:19 +0200 Subject: [PATCH 57/62] Reclassify test Nothing to run here... --- tests/{run => pos}/typelevel-vector1.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{run => pos}/typelevel-vector1.scala (100%) diff --git a/tests/run/typelevel-vector1.scala b/tests/pos/typelevel-vector1.scala similarity index 100% rename from tests/run/typelevel-vector1.scala rename to tests/pos/typelevel-vector1.scala From 101daf5e8473dfb71c570cb5daecc19df2d8578c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jul 2018 13:37:21 +0200 Subject: [PATCH 58/62] Improvements to Inliner - Allow to recognize Inlined nodes as closure defs - Inline also pure valdefs that are used only once (was restricted to methods only) - Beta reduce again after inlining closures that are used only once --- .../src/dotty/tools/dotc/ast/TreeInfo.scala | 12 +++- .../src/dotty/tools/dotc/typer/Inliner.scala | 28 ++++---- tests/run-with-compiler/i3876-d.check | 5 +- tests/run/transparent-foreach.scala | 70 +++++++++++++++++++ 4 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 tests/run/transparent-foreach.scala diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index ef038da36b24..7be5b53515a5 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -418,6 +418,8 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => def isPureExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Pure def isIdempotentExpr(tree: Tree)(implicit ctx: Context) = exprPurity(tree) >= Idempotent + def isPureBinding(tree: Tree)(implicit ctx: Context) = statPurity(tree) >= Pure + /** The purity level of this reference. * @return * SimplyPure if reference is (nonlazy and stable) or to a parameterized function @@ -579,11 +581,15 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => /** An extractor for def of a closure contained the block of the closure. */ object closureDef { - def unapply(tree: Tree): Option[DefDef] = tree match { - case Block(Nil, expr) => unapply(expr) + def unapply(tree: Tree)(implicit ctx: Context): Option[DefDef] = tree match { case Block((meth @ DefDef(nme.ANON_FUN, _, _, _, _)) :: Nil, closure: Closure) => Some(meth) - case _ => None + case Block(Nil, expr) => + unapply(expr) + case Inlined(_, bindings, expr) if bindings.forall(isPureBinding) => + unapply(expr) + case _ => + None } } diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index ab79038fa6e1..516ded001526 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -794,22 +794,24 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { countRefs.traverse(tree) for (binding <- bindings) countRefs.traverse(binding) val inlineBindings = new TreeMap { - override def transform(t: Tree)(implicit ctx: Context) = - super.transform { - t match { - case t: RefTree => - val sym = t.symbol - refCount.get(sym) match { - case Some(1) if sym.is(Method) => - bindingOfSym(sym) match { - case binding: ValOrDefDef => integrate(binding.rhs, sym) - } - case none => t + override def transform(t: Tree)(implicit ctx: Context) = t match { + case t: RefTree => + val sym = t.symbol + val t1 = refCount.get(sym) match { + case Some(1) => + bindingOfSym(sym) match { + case binding: ValOrDefDef => integrate(binding.rhs, sym) } - case _ => t + case none => t } - } + super.transform(t1) + case t: Apply => + val t1 = super.transform(t) + if (t1 `eq` t) t else reducer.betaReduce(t1) + case _ => + super.transform(t) } + } def retain(binding: MemberDef) = refCount.get(binding.symbol) match { case Some(x) => x > 1 || x == 1 && !binding.symbol.is(Method) case none => true diff --git a/tests/run-with-compiler/i3876-d.check b/tests/run-with-compiler/i3876-d.check index 53f5ee0793e4..746b7f8778a7 100644 --- a/tests/run-with-compiler/i3876-d.check +++ b/tests/run-with-compiler/i3876-d.check @@ -1,8 +1,5 @@ 6 { val x$1: scala.Int = 3 - - { // inlined - x$1.+(x$1) - } + x$1.+(x$1) } diff --git a/tests/run/transparent-foreach.scala b/tests/run/transparent-foreach.scala new file mode 100644 index 000000000000..a13134f4047c --- /dev/null +++ b/tests/run/transparent-foreach.scala @@ -0,0 +1,70 @@ +// A demonstrator how `transparent override` can be used to sepcialize inherited methods +trait Iterable[+A] { + def foreach(f: A => Unit): Unit +} +// The unconventional definition of `Range` and `UntilRange` is needed so +// that `UntiRange` is recognized as a Noinits class. Our purity predictor +// for classes currently bails out for arguments passed to parent classes +// and for concrete `val`s. That's because the predictor runs on untyped trees +// so there's no way to predict whether an expression is pure or not. +// It would be nice if we could improve the predictor to work with typed trees. +// The tricky bit is doing this without causing cycles. That's the price we +// pay for making inlining a typelevel computation. +// One possible way to do it would be to make purity a property that's computed on demand, +// just like info, but evaluated later. Then we might still cause cycles, but these +// would be "justified" by inlining attempts. I.e. you could avoid a cycle by +// inlining less. +abstract class Range extends Iterable[Int] { + val start: Int + val end: Int + def step: Int + def inclusive: Boolean + def foreach(f: Int => Unit): Unit = { + var idx = start + while ( + if (step > 0) + if (inclusive) idx <= end else idx < end + else + if (inclusive) idx >= end else idx > end + ) { + f(idx) + idx = idx + step + } + } +} +class UntilRange(val start: Int, val end: Int) extends Range { + def step = 1 + def inclusive = false + transparent override def foreach(f: Int => Unit): Unit = { + var idx = start + while (idx < end) { + f(idx) + idx += 1 + } + } +} +object Test extends App { + var x = 0 + new UntilRange(1, 10).foreach(x += _) + // Expands to: + // var idx: Int = 1 + // while idx < 10 do + // x = x * idx + // idx = idx + 1 + // } + + class IntDeco(val x: Int) extends AnyVal { + transparent def until(y: Int) = new UntilRange(x, y) + } + implicit transparent def intDeco(x: Int): IntDeco = new IntDeco(x) + // So far, the decorator has to be an explicit def, since + // we can inline only methods defined in source. So an implicit class + // will not work. We can make this work by making `Desugar` smarter + // and generate a transparent with pre-defined "BodyToInline" annotation. + // It's doable, just extra work. + + (1 until 10).foreach(x += _) + // expands to same as above + + assert(x == 90) +} From 52e0560bbd7dc931fc9840df69b8f168ab5998c2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jul 2018 15:02:59 +0200 Subject: [PATCH 59/62] Fix #4557 rework: Keep original type untpd.New We need to keep the original type - to allow IDE naviagtion - to make PrepareTransparent work correctly --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 17 ++++++++++------- tests/neg/wildbase.scala | 4 ++-- tests/run/tasty-extractors-2.check | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index ae8d5a4ae34c..b663a827679c 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -301,20 +301,23 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { // ------ Additional creation methods for untyped only ----------------- - /** new pre.C[Ts](args1)...(args_n) + /** new T(args1)...(args_n) * ==> - * (new pre.C).[Ts](args1)...(args_n) + * new T.[Ts](args1)...(args_n) + * + * where `Ts` are the class type arguments of `T` or its class type alias. + * Note: we also keep any type arguments as parts of `T`. This is necessary to allow + * navigation into these arguments from the IDE, and to do the right thing in + * PrepareTransparent. */ def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree = { val (tycon, targs) = tpt match { case AppliedTypeTree(tycon, targs) => (tycon, targs) case TypedSplice(tpt1: tpd.Tree) => - val tp = tpt1.tpe.dealias - val tycon = tp.typeConstructor - val argTypes = tp.argTypesLo - def wrap(tpe: Type) = TypeTree(tpe) withPos tpt.pos - (wrap(tycon), argTypes map wrap) + val argTypes = tpt1.tpe.dealias.argTypesLo + def wrap(tpe: Type) = TypeTree(tpe).withPos(tpt.pos) + (tpt, argTypes.map(wrap)) case _ => (tpt, Nil) } diff --git a/tests/neg/wildbase.scala b/tests/neg/wildbase.scala index b96607ae4e4a..b33abe6346b9 100644 --- a/tests/neg/wildbase.scala +++ b/tests/neg/wildbase.scala @@ -1,5 +1,5 @@ class A[T] -class B extends A[_] // OK +class B extends A[_] // error: type argument must be fully defined -class C extends A[_ >: Any <: Nothing] // error: conflicting bounds +class C extends A[_ >: Any <: Nothing] // error: conflicting bounds // error: type argument must be fully defined diff --git a/tests/run/tasty-extractors-2.check b/tests/run/tasty-extractors-2.check index 2baf5ba3e382..582c6e072cb8 100644 --- a/tests/run/tasty-extractors-2.check +++ b/tests/run/tasty-extractors-2.check @@ -91,7 +91,7 @@ Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef(" Term.Block(List(ClassDef("Foo12", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(ValDef("a", TypeTree.Synthetic(), Some(Term.Literal(Constant.Int(11))))))), Term.Literal(Constant.Unit())) Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef("scala", _), NoPrefix()))) -Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, Test$._$Foo))), Nil)), None, Nil)), Term.Literal(Constant.Unit())) +Term.Block(List(ClassDef("Foo", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.TypeIdent("Foo")), "", Some(Signature(Nil, Test$._$Foo))), Nil)), None, Nil)), Term.Literal(Constant.Unit())) Type.SymRef(ClassDef("Unit", _, _, _, _), Type.ThisType(Type.SymRef(PackageDef("scala", _), NoPrefix()))) Term.Block(List(ClassDef("Foo2", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, Nil), ClassDef("Bar", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil), TypeTree.TypeIdent("Foo2")), None, Nil)), Term.Literal(Constant.Unit())) From d827f65be8f5b08395dd996d78625324a2bce53e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jul 2018 15:46:38 +0200 Subject: [PATCH 60/62] Fix FromTasty tests - 3 tests saw minor tweaks to decompiled file. - 1 test had to be blacklisted, sicne superArgs in ShowTasty is not yet implemented. --- compiler/test/dotc/run-from-tasty.blacklist | 3 +++ tests/pos/i2104b.decompiled | 4 ++-- tests/pos/simpleCaseClass-3.decompiled | 2 +- tests/run/valueclasses-pavlov.decompiled | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/compiler/test/dotc/run-from-tasty.blacklist b/compiler/test/dotc/run-from-tasty.blacklist index 46615b510186..e8ebd4579ec2 100644 --- a/compiler/test/dotc/run-from-tasty.blacklist +++ b/compiler/test/dotc/run-from-tasty.blacklist @@ -1,2 +1,5 @@ # Closure type miss match eff-dependent.scala + +# superArgs missing (see TODO in tastyreflect/FromSymbol.scala) +reduce-projection.scala diff --git a/tests/pos/i2104b.decompiled b/tests/pos/i2104b.decompiled index 37893eea43f5..9be9677bea4a 100644 --- a/tests/pos/i2104b.decompiled +++ b/tests/pos/i2104b.decompiled @@ -31,7 +31,7 @@ case class Pair[A, B](_1: A, _2: B) { throw new java.lang.IndexOutOfBoundsException(n.toString()) } } -object Pair +object Pair extends scala.AnyRef() /** Decompiled from out/posTestFromTasty/pos/i2104b/Test.class */ object Test { def main(args: scala.Array[scala.Predef.String]): scala.Unit = { @@ -40,4 +40,4 @@ object Test { () } } -} +} \ No newline at end of file diff --git a/tests/pos/simpleCaseClass-3.decompiled b/tests/pos/simpleCaseClass-3.decompiled index d2a3acf557ea..6dcd9d72ab22 100644 --- a/tests/pos/simpleCaseClass-3.decompiled +++ b/tests/pos/simpleCaseClass-3.decompiled @@ -22,4 +22,4 @@ case class A[T](x: T) { throw new java.lang.IndexOutOfBoundsException(n.toString()) } } -object A +object A extends scala.AnyRef() \ No newline at end of file diff --git a/tests/run/valueclasses-pavlov.decompiled b/tests/run/valueclasses-pavlov.decompiled index 36da557e9f4c..593489526077 100644 --- a/tests/run/valueclasses-pavlov.decompiled +++ b/tests/run/valueclasses-pavlov.decompiled @@ -8,7 +8,7 @@ final class Box1(val value: scala.Predef.String) extends scala.AnyVal() { false } } -object Box1 +object Box1 extends scala.AnyRef() /** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/Box2.class */ final class Box2(val value: scala.Predef.String) extends scala.AnyVal() with Foo { def box1(x: Box1): scala.Predef.String = "box1: ok" @@ -21,7 +21,7 @@ final class Box2(val value: scala.Predef.String) extends scala.AnyVal() with Foo false } } -object Box2 +object Box2 extends scala.AnyRef() /** Decompiled from out/runTestFromTasty/run/valueclasses-pavlov/C.class */ class C(x: scala.Predef.String) { def this() = { @@ -43,4 +43,4 @@ object Test { scala.Predef.println(f.box1(b1)) scala.Predef.println(f.box2(b2)) } -} +} \ No newline at end of file From 3bee2846577740f8e6ac93b3ebd041e71461523b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jul 2018 16:12:02 +0200 Subject: [PATCH 61/62] Fix FromSymbol.classDef --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 4 +++- compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala | 4 ++-- compiler/test/dotc/run-from-tasty.blacklist | 3 --- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index fabdf2746cd7..1facbdfc6690 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -251,8 +251,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { val constr = firstParent.decl(nme.CONSTRUCTOR).suchThat(constr => isApplicable(constr.info)) New(firstParent, constr.symbol.asTerm, superArgs) } - val parents = superRef :: otherParents.map(TypeTree(_)) + ClassDefWithParents(cls, constr, superRef :: otherParents.map(TypeTree(_)), body) + } + def ClassDefWithParents(cls: ClassSymbol, constr: DefDef, parents: List[Tree], body: List[Tree])(implicit ctx: Context): TypeDef = { val selfType = if (cls.classInfo.selfInfo ne NoType) ValDef(ctx.newSelfSym(cls)) else EmptyValDef diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala b/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala index 150149b38e0f..c4fcfce1305a 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/FromSymbol.scala @@ -23,9 +23,9 @@ object FromSymbol { val constrSym = cls.unforcedDecls.find(_.isPrimaryConstructor) if (!constrSym.exists) return tpd.EmptyTree val constr = tpd.DefDef(constrSym.asTerm) + val parents = cls.classParents.map(tpd.TypeTree(_)) val body = cls.unforcedDecls.filter(!_.isPrimaryConstructor).map(s => definition(s)) - val superArgs = Nil // TODO - tpd.ClassDef(cls, constr, body, superArgs) + tpd.ClassDefWithParents(cls, constr, parents, body) } def typeDef(sym: TypeSymbol)(implicit ctx: Context): tpd.TypeDef = tpd.TypeDef(sym) diff --git a/compiler/test/dotc/run-from-tasty.blacklist b/compiler/test/dotc/run-from-tasty.blacklist index e8ebd4579ec2..46615b510186 100644 --- a/compiler/test/dotc/run-from-tasty.blacklist +++ b/compiler/test/dotc/run-from-tasty.blacklist @@ -1,5 +1,2 @@ # Closure type miss match eff-dependent.scala - -# superArgs missing (see TODO in tastyreflect/FromSymbol.scala) -reduce-projection.scala From 88e7d180574eda8a71037d1735ac6a3ff76a1c08 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jul 2018 16:47:48 +0200 Subject: [PATCH 62/62] Update check file --- tests/run/tasty-extractors-owners.check | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/run/tasty-extractors-owners.check b/tests/run/tasty-extractors-owners.check index a7c3a8e3b31b..84e8f1b6be6d 100644 --- a/tests/run/tasty-extractors-owners.check +++ b/tests/run/tasty-extractors-owners.check @@ -17,11 +17,11 @@ baz2 ValDef("foo2", TypeTree.Synthetic(), None) -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) b -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) b2 -ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(Term.Apply(Term.Select(Term.New(TypeTree.Synthetic()), "", Some(Signature(Nil, java.lang.Object))), Nil)), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None))) +ClassDef("A", DefDef("", Nil, List(Nil), TypeTree.Synthetic(), None), List(TypeTree.Synthetic()), None, List(TypeDef("B", SyntheticBounds()), DefDef("b", Nil, Nil, TypeTree.Synthetic(), None), ValDef("b2", TypeTree.Synthetic(), None)))