diff options
Diffstat (limited to 'src')
104 files changed, 2117 insertions, 2554 deletions
diff --git a/src/build/bnd/scala-compiler-doc.bnd b/src/build/bnd/scala-compiler-doc.bnd deleted file mode 100644 index 5b662e8cef..0000000000 --- a/src/build/bnd/scala-compiler-doc.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala Documentation Generator -Bundle-SymbolicName: org.scala-lang.modules.scala-compiler-doc_@SCALA_BINARY_VERSION@ -ver: @SCALA_COMPILER_DOC_VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-compiler-interactive.bnd b/src/build/bnd/scala-compiler-interactive.bnd deleted file mode 100644 index fbfff60801..0000000000 --- a/src/build/bnd/scala-compiler-interactive.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala Interactive Compiler -Bundle-SymbolicName: org.scala-lang.modules.scala-compiler-interactive_@SCALA_BINARY_VERSION@ -ver: @SCALA_COMPILER_INTERACTIVE_VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-compiler.bnd b/src/build/bnd/scala-compiler.bnd deleted file mode 100644 index c12c84c3f9..0000000000 --- a/src/build/bnd/scala-compiler.bnd +++ /dev/null @@ -1,12 +0,0 @@ -Bundle-Name: Scala Compiler -Bundle-SymbolicName: org.scala-lang.scala-compiler -ver: @VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: jline.*;resolution:=optional, \ - org.apache.tools.ant.*;resolution:=optional, \ - scala.xml.*;version="${range;[====,====];@XML_VERSION@}";resolution:=optional, \ - scala.*;version="${range;[==,=+);${ver}}", \ - * -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-library.bnd b/src/build/bnd/scala-library.bnd deleted file mode 100644 index e211c5d1ad..0000000000 --- a/src/build/bnd/scala-library.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala Standard Library -Bundle-SymbolicName: org.scala-lang.scala-library -ver: @VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: sun.misc;resolution:=optional, * -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-parser-combinators.bnd b/src/build/bnd/scala-parser-combinators.bnd deleted file mode 100644 index 515084f4a8..0000000000 --- a/src/build/bnd/scala-parser-combinators.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala Parser Combinators Library -Bundle-SymbolicName: org.scala-lang.modules.scala-parser-combinators -ver: @PARSER_COMBINATORS_VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-reflect.bnd b/src/build/bnd/scala-reflect.bnd deleted file mode 100644 index 59db311f8d..0000000000 --- a/src/build/bnd/scala-reflect.bnd +++ /dev/null @@ -1,10 +0,0 @@ -Bundle-Name: Scala Reflect -Bundle-SymbolicName: org.scala-lang.scala-reflect -ver: @VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);${ver}}", \ - scala.tools.nsc;resolution:=optional;version="${range;[==,=+);${ver}}", \ - * -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-swing.bnd b/src/build/bnd/scala-swing.bnd deleted file mode 100644 index 24cd9f6f90..0000000000 --- a/src/build/bnd/scala-swing.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala Swing -Bundle-SymbolicName: org.scala-lang.modules.scala-swing -ver: @SCALA_SWING_VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/bnd/scala-xml.bnd b/src/build/bnd/scala-xml.bnd deleted file mode 100644 index b7b19824e8..0000000000 --- a/src/build/bnd/scala-xml.bnd +++ /dev/null @@ -1,8 +0,0 @@ -Bundle-Name: Scala XML Library -Bundle-SymbolicName: org.scala-lang.modules.scala-xml -ver: @XML_VERSION@ -Bundle-Version: ${ver} -Export-Package: *;version=${ver} -Import-Package: scala.*;version="${range;[==,=+);@VERSION@}",* -Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Include-Resource: @@SOURCE_JARNAME@ diff --git a/src/build/maven/scala-compiler-doc-pom.xml b/src/build/maven/scala-compiler-doc-pom.xml deleted file mode 100644 index 86ca3f865b..0000000000 --- a/src/build/maven/scala-compiler-doc-pom.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-compiler-doc_@SCALA_BINARY_VERSION@</artifactId> - <packaging>jar</packaging> - <version>@SCALA_COMPILER_DOC_VERSION@</version> - <name>Scala Documentation Generator</name> - <description>Documentation generator for the Scala Programming Language</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-compiler</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-xml_@SCALA_BINARY_VERSION@</artifactId> - <version>@XML_VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-compiler-interactive-pom.xml b/src/build/maven/scala-compiler-interactive-pom.xml deleted file mode 100644 index d3e5e0b834..0000000000 --- a/src/build/maven/scala-compiler-interactive-pom.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-compiler-interactive_@SCALA_BINARY_VERSION@</artifactId> - <packaging>jar</packaging> - <version>@SCALA_COMPILER_INTERACTIVE_VERSION@</version> - <name>Scala Interactive Compiler</name> - <description>Interactive Compiler for the Scala Programming Language</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-compiler</artifactId> - <version>@VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-compiler-pom.xml b/src/build/maven/scala-compiler-pom.xml deleted file mode 100644 index 9c157d17d9..0000000000 --- a/src/build/maven/scala-compiler-pom.xml +++ /dev/null @@ -1,65 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scala-compiler</artifactId> - <packaging>jar</packaging> - <version>@VERSION@</version> - <name>Scala Compiler</name> - <description>Compiler for the Scala Programming Language</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-reflect</artifactId> - <version>@VERSION@</version> - </dependency> - <!-- TODO modularize compiler: these dependencies will disappear when the compiler is modularized --> - <dependency> <!-- for scala-compiler-doc --> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-xml_@SCALA_BINARY_VERSION@</artifactId> - <version>@XML_VERSION@</version> - </dependency> - <dependency> <!-- for scala-compiler-repl; once it moves there, make it required --> - <groupId>jline</groupId> - <artifactId>jline</artifactId> - <version>@JLINE_VERSION@</version> - <optional>true</optional> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-dist-pom.xml b/src/build/maven/scala-dist-pom.xml deleted file mode 100644 index ce511661b0..0000000000 --- a/src/build/maven/scala-dist-pom.xml +++ /dev/null @@ -1,69 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scala-dist</artifactId> - <packaging>jar</packaging> - <version>@VERSION@</version> - <name>Scala Distribution Artifacts</name> - <description>The Artifacts Distributed with Scala</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library-all</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-compiler</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scalap</artifactId> - <version>@VERSION@</version> - </dependency> - <!-- duplicated from scala-compiler, where it's optional, - so that resolving scala-dist's transitive dependencies does not include jline, - even though we need to include it in the dist, but macros depending on the compiler - shouldn't have to require jline... - another reason to modularize and move the dependency to scala-compiler-repl - TODO: remove duplication once we have the scala-compiler-repl module --> - <dependency> - <groupId>jline</groupId> - <artifactId>jline</artifactId> - <version>@JLINE_VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-library-all-pom.xml b/src/build/maven/scala-library-all-pom.xml deleted file mode 100644 index 4620c620dc..0000000000 --- a/src/build/maven/scala-library-all-pom.xml +++ /dev/null @@ -1,68 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library-all</artifactId> - <packaging>pom</packaging> - <version>@VERSION@</version> - <name>Scala Library Powerpack</name> - <description>The Scala Standard Library and Official Modules</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-reflect</artifactId> - <version>@VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-xml_@SCALA_BINARY_VERSION@</artifactId> - <version>@XML_VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-parser-combinators_@SCALA_BINARY_VERSION@</artifactId> - <version>@PARSER_COMBINATORS_VERSION@</version> - </dependency> - <dependency> - <groupId>org.scala-lang.modules</groupId> - <artifactId>scala-swing_@SCALA_BINARY_VERSION@</artifactId> - <version>@SCALA_SWING_VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-library-pom.xml b/src/build/maven/scala-library-pom.xml deleted file mode 100644 index e27f8fb12f..0000000000 --- a/src/build/maven/scala-library-pom.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - <packaging>jar</packaging> - <version>@VERSION@</version> - <name>Scala Library</name> - <description>Standard library for the Scala Programming Language</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <properties> - <info.apiURL>http://www.scala-lang.org/api/@VERSION@/</info.apiURL> - </properties> - <dependencies> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scala-reflect-pom.xml b/src/build/maven/scala-reflect-pom.xml deleted file mode 100644 index f7f3c8bc08..0000000000 --- a/src/build/maven/scala-reflect-pom.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scala-reflect</artifactId> - <packaging>jar</packaging> - <version>@VERSION@</version> - <name>Scala Compiler</name> - <description>Compiler for the Scala Programming Language</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <properties> - <info.apiURL>http://www.scala-lang.org/api/@VERSION@/</info.apiURL> - </properties> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-library</artifactId> - <version>@VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/build/maven/scalap-pom.xml b/src/build/maven/scalap-pom.xml deleted file mode 100644 index acdd44f19b..0000000000 --- a/src/build/maven/scalap-pom.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0"?> -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.scala-lang</groupId> - <artifactId>scalap</artifactId> - <packaging>jar</packaging> - <version>@VERSION@</version> - <name>Scalap</name> - <description>bytecode analysis tool</description> - <url>http://www.scala-lang.org/</url> - <inceptionYear>2002</inceptionYear> - <organization> - <name>LAMP/EPFL</name> - <url>http://lamp.epfl.ch/</url> - </organization> - <licenses> - <license> - <name>BSD 3-Clause</name> - <url>http://www.scala-lang.org/license.html</url> - <distribution>repo</distribution> - </license> - </licenses> - <scm> - <connection>scm:git:git://github.com/scala/scala.git</connection> - <url>https://github.com/scala/scala.git</url> - </scm> - <issueManagement> - <system>JIRA</system> - <url>https://issues.scala-lang.org/</url> - </issueManagement> - <dependencies> - <dependency> - <groupId>org.scala-lang</groupId> - <artifactId>scala-compiler</artifactId> - <version>@VERSION@</version> - </dependency> - </dependencies> - <developers> - <developer> - <id>lamp</id> - <name>EPFL LAMP</name> - </developer> - <developer> - <id>Lightbend</id> - <name>Lightbend, Inc.</name> - </developer> - </developers> -</project> diff --git a/src/compiler/scala/reflect/reify/phases/Reshape.scala b/src/compiler/scala/reflect/reify/phases/Reshape.scala index 091d42bb6d..581ce8256a 100644 --- a/src/compiler/scala/reflect/reify/phases/Reshape.scala +++ b/src/compiler/scala/reflect/reify/phases/Reshape.scala @@ -49,13 +49,13 @@ trait Reshape { if (discard) hk else ta case classDef @ ClassDef(mods, name, params, impl) => val Template(parents, self, body) = impl - var body1 = trimAccessors(classDef, reshapeLazyVals(body)) + var body1 = trimAccessors(classDef, body) body1 = trimSyntheticCaseClassMembers(classDef, body1) val impl1 = Template(parents, self, body1).copyAttrs(impl) ClassDef(mods, name, params, impl1).copyAttrs(classDef) case moduledef @ ModuleDef(mods, name, impl) => val Template(parents, self, body) = impl - var body1 = trimAccessors(moduledef, reshapeLazyVals(body)) + var body1 = trimAccessors(moduledef, body) body1 = trimSyntheticCaseClassMembers(moduledef, body1) val impl1 = Template(parents, self, body1).copyAttrs(impl) ModuleDef(mods, name, impl1).copyAttrs(moduledef) @@ -63,10 +63,10 @@ trait Reshape { val discardedParents = parents collect { case tt: TypeTree => tt } filter isDiscarded if (reifyDebug && discardedParents.length > 0) println("discarding parents in Template: " + discardedParents.mkString(", ")) val parents1 = parents diff discardedParents - val body1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(body)) + val body1 = trimSyntheticCaseClassCompanions(body) Template(parents1, self, body1).copyAttrs(template) case block @ Block(stats, expr) => - val stats1 = reshapeLazyVals(trimSyntheticCaseClassCompanions(stats)) + val stats1 = trimSyntheticCaseClassCompanions(stats) Block(stats1, expr).copyAttrs(block) case unapply @ UnApply(Unapplied(Select(fun, nme.unapply | nme.unapplySeq)), args) => if (reifyDebug) println("unapplying unapply: " + tree) @@ -306,34 +306,6 @@ trait Reshape { stats1 } - private def reshapeLazyVals(stats: List[Tree]): List[Tree] = { - val lazyvaldefs:Map[Symbol, DefDef] = stats.collect({ case ddef: DefDef if ddef.mods.isLazy => ddef }). - map((ddef: DefDef) => ddef.symbol -> ddef).toMap - // lazy valdef and defdef are in the same block. - // only that valdef needs to have its rhs rebuilt from defdef - stats flatMap (stat => stat match { - case vdef: ValDef if vdef.symbol.isLazy => - if (reifyDebug) println(s"reconstructing original lazy value for $vdef") - val ddefSym = vdef.symbol.lazyAccessor - val vdef1 = lazyvaldefs.get(ddefSym) match { - case Some(ddef) => - toPreTyperLazyVal(ddef) - case None => - if (reifyDebug) println("couldn't find corresponding lazy val accessor") - vdef - } - if (reifyDebug) println(s"reconstructed lazy val is $vdef1") - vdef1::Nil - case ddef: DefDef if ddef.symbol.isLazy => - if (isUnitType(ddef.symbol.info)) { - // since lazy values of type Unit don't have val's - // we need to create them from scratch - toPreTyperLazyVal(ddef) :: Nil - } else Nil - case _ => stat::Nil - }) - } - private def trimSyntheticCaseClassMembers(deff: Tree, stats: List[Tree]): List[Tree] = stats filterNot (memberDef => memberDef.isDef && { val isSynthetic = memberDef.symbol.isSynthetic diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index e9d1dfe4d2..511572f6f3 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -88,7 +88,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { object CompilerPhase extends PermissibleValue { val values = List("namer", "typer", "pickler", "refchecks", "uncurry", "tailcalls", "specialize", "explicitouter", - "erasure", "lazyvals", "lambdalift", "constructors", + "erasure", "fields", "lambdalift", "constructors", "flatten", "mixin", "delambdafy", "cleanup", "jvm", "terminal") } diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala index 9b8e9fa330..24da6ba487 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -103,15 +103,7 @@ class CompilerCommand(arguments: List[String], val settings: Settings) { val components = global.phaseNames // global.phaseDescriptors // one initializes s"Phase graph of ${components.size} components output to ${genPhaseGraph.value}*.dot." } - // would be nicer if we could ask all the options for their helpful messages - else { - val sb = new StringBuilder - allSettings foreach { - case s: MultiChoiceSetting[_] if s.isHelping => sb append s.help - case _ => - } - sb.toString - } + else allSettings.filter(_.isHelping).map(_.help).mkString("\n\n") } /** diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index af866e1a6f..64ed687c07 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -516,17 +516,11 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val runsRightAfter = Some("erasure") } with PostErasure - // phaseName = "lazyvals" - object lazyVals extends { - val global: Global.this.type = Global.this - val runsAfter = List("erasure") - val runsRightAfter = None - } with LazyVals // phaseName = "lambdalift" object lambdaLift extends { val global: Global.this.type = Global.this - val runsAfter = List("lazyvals") + val runsAfter = List("erasure") val runsRightAfter = None } with LambdaLift @@ -620,13 +614,12 @@ class Global(var currentSettings: Settings, var reporter: Reporter) pickler -> "serialize symbol tables", refChecks -> "reference/override checking, translate nested objects", uncurry -> "uncurry, translate function values to anonymous classes", - fields -> "synthesize accessors and fields", + fields -> "synthesize accessors and fields, add bitmaps for lazy vals", tailCalls -> "replace tail calls by jumps", specializeTypes -> "@specialized-driven class and method specialization", explicitOuter -> "this refs to outer pointers", erasure -> "erase types, add interfaces for traits", postErasure -> "clean up erased inline classes", - lazyVals -> "allocate bitmaps, translate lazy vals into lazified defs", lambdaLift -> "move nested functions to top level", constructors -> "move field definitions into constructors", mixer -> "mixin composition", @@ -683,7 +676,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) } /** A description of the phases that will run in this configuration, or all if -Ydebug. */ - def phaseDescriptions: String = phaseHelp("description", elliptically = true, phasesDescMap) + def phaseDescriptions: String = phaseHelp("description", elliptically = !settings.debug, phasesDescMap) /** Summary of the per-phase values of nextFlags and newFlags, shown under -Xshow-phases -Ydebug. */ def phaseFlagDescriptions: String = { @@ -694,7 +687,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) else if (ph.phaseNewFlags != 0L && ph.phaseNextFlags != 0L) fstr1 + " " + fstr2 else fstr1 + fstr2 } - phaseHelp("new flags", elliptically = false, fmt) + phaseHelp("new flags", elliptically = !settings.debug, fmt) } /** Emit a verbose phase table. @@ -706,7 +699,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) * @param elliptically whether to truncate the description with an ellipsis (...) * @param describe how to describe a component */ - def phaseHelp(title: String, elliptically: Boolean, describe: SubComponent => String) = { + private def phaseHelp(title: String, elliptically: Boolean, describe: SubComponent => String): String = { val Limit = 16 // phase names should not be absurdly long val MaxCol = 80 // because some of us edit on green screens val maxName = phaseNames map (_.length) max @@ -721,13 +714,13 @@ class Global(var currentSettings: Settings, var reporter: Reporter) // built-in string precision merely truncates import java.util.{ Formattable, FormattableFlags, Formatter } def dotfmt(s: String) = new Formattable { - def elliptically(s: String, max: Int) = ( + def foreshortened(s: String, max: Int) = ( if (max < 0 || s.length <= max) s else if (max < 4) s.take(max) else s.take(max - 3) + "..." ) override def formatTo(formatter: Formatter, flags: Int, width: Int, precision: Int) { - val p = elliptically(s, precision) + val p = foreshortened(s, precision) val w = if (width > 0 && p.length < width) { import FormattableFlags.LEFT_JUSTIFY val leftly = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY @@ -753,7 +746,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) else (p.phaseName, describe(p)) fmt.format(name, idOf(p), text) } - line1 :: line2 :: (phaseDescriptors map mkText) mkString + (line1 :: line2 :: (phaseDescriptors map mkText)).mkString } /** Returns List of (phase, value) pairs, including only those @@ -1258,7 +1251,6 @@ class Global(var currentSettings: Settings, var reporter: Reporter) val explicitouterPhase = phaseNamed("explicitouter") val erasurePhase = phaseNamed("erasure") val posterasurePhase = phaseNamed("posterasure") - // val lazyvalsPhase = phaseNamed("lazyvals") val lambdaliftPhase = phaseNamed("lambdalift") // val constructorsPhase = phaseNamed("constructors") val flattenPhase = phaseNamed("flatten") diff --git a/src/compiler/scala/tools/nsc/ast/TreeGen.scala b/src/compiler/scala/tools/nsc/ast/TreeGen.scala index 5dddf30c96..762456c9c9 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeGen.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeGen.scala @@ -91,7 +91,7 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { ) /** Make a synchronized block on 'monitor'. */ - def mkSynchronized(monitor: Tree, body: Tree): Tree = + def mkSynchronized(monitor: Tree)(body: Tree): Tree = Apply(Select(monitor, Object_synchronized), List(body)) def mkAppliedTypeForCase(clazz: Symbol): Tree = { @@ -233,26 +233,6 @@ abstract class TreeGen extends scala.reflect.internal.TreeGen with TreeDSL { else Block(prefix, containing) setPos (prefix.head.pos union containing.pos) } - /** Return the synchronized part of the double-checked locking idiom around the syncBody tree. It guards with `cond` and - * synchronizes on `clazz.this`. Additional statements can be included after initialization, - * (outside the synchronized block). - * - * The idiom works only if the condition is using a volatile field. - * - * @see http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html - */ - def mkSynchronizedCheck(clazz: Symbol, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = - mkSynchronizedCheck(mkAttributedThis(clazz), cond, syncBody, stats) - - def mkSynchronizedCheck(attrThis: Tree, cond: Tree, syncBody: List[Tree], stats: List[Tree]): Tree = { - def blockOrStat(stats: List[Tree]): Tree = stats match { - case head :: Nil => head - case _ => Block(stats : _*) - } - val sync = mkSynchronized(attrThis, If(cond, blockOrStat(syncBody), EmptyTree)) - blockOrStat(sync :: stats) - } - /** Creates a tree representing new Object { stats }. * To make sure an anonymous subclass of Object is created, * if there are no stats, a () is added. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala index 630b2b6c7f..1982c7f643 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/AsmUtils.scala @@ -5,11 +5,15 @@ package scala.tools.nsc.backend.jvm -import scala.tools.asm.tree.{InsnList, AbstractInsnNode, ClassNode, MethodNode} -import java.io.{StringWriter, PrintWriter} -import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier} -import scala.tools.asm.{ClassReader, ClassWriter, Attribute} +import scala.tools.asm.tree.{AbstractInsnNode, ClassNode, FieldNode, InsnList, MethodNode} +import java.io.{PrintWriter, StringWriter} +import java.util + +import scala.tools.asm.util.{CheckClassAdapter, Textifier, TraceClassVisitor, TraceMethodVisitor} +import scala.tools.asm.{Attribute, ClassReader, ClassWriter} import scala.collection.JavaConverters._ +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, Future} import scala.tools.nsc.backend.jvm.analysis.InitialProducer import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype @@ -64,21 +68,37 @@ object AsmUtils { bytes } - def textifyClassStably(bytes: Array[Byte]): Unit = { + def classFromBytes(bytes: Array[Byte]): ClassNode = { val node = new ClassNode() new ClassReader(bytes).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) - node.fields = node.fields.asScala.sortBy(_.name).asJava - node.methods = node.methods.asScala.sortBy(_.name).asJava - node.visibleAnnotations = null - node.attrs = null - node.invisibleAnnotations = null + node + } + +// def main(args: Array[String]): Unit = println(textify(sortedClassRead(classBytes(args.head)))) + + def sortClassMembers(node: ClassNode): node.type = { + node.fields.sort(_.name compareTo _.name) + node.methods.sort(_.name compareTo _.name) + node + } + + // drop ScalaSig annotation and class attributes + def zapScalaClassAttrs(node: ClassNode): node.type = { + if (node.visibleAnnotations != null) + node.visibleAnnotations = node.visibleAnnotations.asScala.filterNot(a => a == null || a.desc.contains("Lscala/reflect/ScalaSignature")).asJava - println(textify(node)) + node.attrs = null + node } - def main(args: Array[String]): Unit = { - textifyClassStably(classBytes(args.head)) + def main(args: Array[String]): Unit = args.par.foreach { classFileName => + val node = zapScalaClassAttrs(sortClassMembers(classFromBytes(classBytes(classFileName)))) + + val pw = new PrintWriter(classFileName + ".asm") + val trace = new TraceClassVisitor(pw) + node.accept(trace) + pw.close() } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index bac84a4959..0b07e12917 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -297,14 +297,14 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case app : Apply => generatedType = genApply(app, expectedType) - case app @ ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: staticAndDynamicArgs) => - val numStaticArgs = boostrapMethodRef.paramss.head.size - 3 /*JVM provided args*/ + case app @ ApplyDynamic(qual, Literal(Constant(bootstrapMethodRef: Symbol)) :: staticAndDynamicArgs) => + val numStaticArgs = bootstrapMethodRef.paramss.head.size - 3 /*JVM provided args*/ val (staticArgs, dynamicArgs) = staticAndDynamicArgs.splitAt(numStaticArgs) - val boostrapDescriptor = staticHandleFromSymbol(boostrapMethodRef) + val bootstrapDescriptor = staticHandleFromSymbol(bootstrapMethodRef) val bootstrapArgs = staticArgs.map({case t @ Literal(c: Constant) => bootstrapMethodArg(c, t.pos)}) val descriptor = methodBTypeFromMethodType(qual.symbol.info, false) genLoadArguments(dynamicArgs, qual.symbol.info.params.map(param => typeToBType(param.info))) - mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, descriptor.descriptor, boostrapDescriptor, bootstrapArgs : _*) + mnode.visitInvokeDynamicInsn(qual.symbol.name.encoded, descriptor.descriptor, bootstrapDescriptor, bootstrapArgs : _*) case ApplyDynamic(qual, args) => sys.error("No invokedynamic support yet.") @@ -613,7 +613,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } argsSize match { case 1 => bc newarray elemKind - case _ => // this is currently dead code is Scalac, unlike in Dotty + case _ => // this is currently dead code in Scalac, unlike in Dotty val descr = ("[" * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor mnode.visitMultiANewArrayInsn(descr, argsSize) } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index 3e53419573..466793010f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -73,9 +73,11 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { /* ------ (4) exception-handler version of monitor-exit code. * Reached upon abrupt termination of (2). * Protected by whatever protects the whole synchronized expression. + * null => "any" exception in bytecode, like we emit for finally. + * Important not to use j/l/Throwable which dooms the method to a life of interpretation! (SD-233) * ------ */ - protect(startProtected, endProtected, currProgramPoint(), jlThrowableRef) + protect(startProtected, endProtected, currProgramPoint(), null) locals.load(monitor) emit(asm.Opcodes.MONITOREXIT) emit(asm.Opcodes.ATHROW) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 09e82de89b..edb75514e8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -114,7 +114,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { if (classSym == NothingClass) srNothingRef else if (classSym == NullClass) srNullRef else { - val internalName = classSym.javaBinaryName.toString + val internalName = classSym.javaBinaryNameString classBTypeFromInternalName.getOrElse(internalName, { // The new ClassBType is added to the map in its constructor, before we set its info. This // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. @@ -625,7 +625,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { */ def mirrorClassClassBType(moduleClassSym: Symbol): ClassBType = { assert(isTopLevelModuleClass(moduleClassSym), s"not a top-level module class: $moduleClassSym") - val internalName = moduleClassSym.javaBinaryName.dropModule.toString + val internalName = moduleClassSym.javaBinaryNameString.stripSuffix(nme.MODULE_SUFFIX_STRING) classBTypeFromInternalName.getOrElse(internalName, { val c = ClassBType(internalName) // class info consistent with BCodeHelpers.genMirrorClass @@ -642,7 +642,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { } def beanInfoClassClassBType(mainClass: Symbol): ClassBType = { - val internalName = mainClass.javaBinaryName.toString + "BeanInfo" + val internalName = mainClass.javaBinaryNameString + "BeanInfo" classBTypeFromInternalName.getOrElse(internalName, { val c = ClassBType(internalName) c.info = Right(ClassInfo( diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala index 0a54767f76..6593d4b725 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenBCode.scala @@ -337,7 +337,7 @@ abstract class GenBCode extends BCodeSyncAndTry { bTypes.initializeCoreBTypes() bTypes.javaDefinedClasses.clear() bTypes.javaDefinedClasses ++= currentRun.symSource collect { - case (sym, _) if sym.isJavaDefined => sym.javaBinaryName.toString + case (sym, _) if sym.isJavaDefined => sym.javaBinaryNameString } Statistics.stopTimer(BackendStats.bcodeInitTimer, initStart) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala index b088b5ee48..e0fd77bb54 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/CallGraph.scala @@ -27,7 +27,7 @@ class CallGraph[BT <: BTypes](val btypes: BT) { * * Indexing the call graph by the containing MethodNode and the invocation MethodInsnNode allows * finding callsites efficiently. For example, an inlining heuristic might want to know all - * callsites withing a callee method. + * callsites within a callee method. * * Note that the call graph is not guaranteed to be complete: callsites may be missing. In * particular, if a method is very large, all of its callsites might not be in the hash map. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 081830d61d..35ee5ba13d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -325,8 +325,7 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { insns.insertBefore(invocation, new InsnNode(DUP)) INVOKESPECIAL } - val isInterface = bodyOpcode == INVOKEINTERFACE - val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface) + val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, lambdaBodyHandle.isInterface) ownerMethod.instructions.insertBefore(invocation, bodyInvocation) val bodyReturnType = Type.getReturnType(lambdaBodyHandle.getDesc) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala index fedacdac41..65d1e20d69 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/LocalOpt.scala @@ -47,7 +47,7 @@ import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._ * note that eliminating empty handlers and stale local variable descriptors is required for * correctness, see the comment in the body of `methodOptimizations`. * - * box-unbox elimination (eliminates box-unbox pairs withing the same method) + * box-unbox elimination (eliminates box-unbox pairs within the same method) * + enables UPSTREAM: * - nullness optimizations (a box extraction operation (unknown nullness) may be rewritten to * a read of a non-null local. example in doc comment of box-unbox implementation) diff --git a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala index 8386722b63..9d643825f6 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsScalaSettings.scala @@ -30,8 +30,8 @@ trait AbsScalaSettings { type OutputSetting <: Setting def BooleanSetting(name: String, descr: String): BooleanSetting - def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting - def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String): ChoiceSetting + def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): ChoiceSetting + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String] = Nil): ChoiceSetting def IntSetting(name: String, descr: String, default: Int, range: Option[(Int, Int)], parser: String => Option[Int]): IntSetting def MultiStringSetting(name: String, helpArg: String, descr: String): MultiStringSetting def MultiChoiceSetting[E <: MultiChoiceEnumeration](name: String, helpArg: String, descr: String, domain: E, default: Option[List[String]]): MultiChoiceSetting[E] diff --git a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala index 060a24d8d4..08fa56d8e9 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala @@ -88,6 +88,12 @@ trait AbsSettings extends scala.reflect.internal.settings.AbsSettings { /** Issue error and return */ def errorAndValue[T](msg: String, x: T): T = { errorFn(msg) ; x } + /** If this method returns true, print the [[help]] message and exit. */ + def isHelping: Boolean = false + + /** The help message to be printed if [[isHelping]]. */ + def help: String = "" + /** After correct Setting has been selected, tryToSet is called with the * remainder of the command line. It consumes any applicable arguments and * returns the unconsumed ones. diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 9cc8faf8c2..822e0f16bf 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -219,10 +219,10 @@ class MutableSettings(val errorFn: String => Unit) } def BooleanSetting(name: String, descr: String) = add(new BooleanSetting(name, descr)) - def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String) = - add(new ChoiceSetting(name, helpArg, descr, choices, default)) - def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String) = - ChoiceSetting(name, helpArg, descr, choices, default).withPostSetHook(sett => + def ChoiceSetting(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) = + add(new ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp)) + def ChoiceSettingForcedDefault(name: String, helpArg: String, descr: String, choices: List[String], default: String, choicesHelp: List[String]) = + ChoiceSetting(name, helpArg, descr, choices, default, choicesHelp).withPostSetHook(sett => if (sett.value != default) { sett.withDeprecationMessage(s"${name}:${sett.value} is deprecated, forcing use of $default") sett.value = default @@ -627,7 +627,7 @@ class MutableSettings(val errorFn: String => Unit) descr: String, val domain: E, val default: Option[List[String]] - ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list") with Clearable { + ) extends Setting(name, s"$descr: `_' for all, `$name:help' to list choices.") with Clearable { withHelpSyntax(s"$name:<_,$helpArg,-$helpArg>") @@ -748,14 +748,22 @@ class MutableSettings(val errorFn: String => Unit) def contains(choice: domain.Value): Boolean = value contains choice - def isHelping: Boolean = sawHelp + override def isHelping: Boolean = sawHelp - def help: String = { - val choiceLength = choices.map(_.length).max + 1 - val formatStr = s" %-${choiceLength}s %s" - choices.zipAll(descriptions, "", "").map { - case (arg, descr) => formatStr.format(arg, descr) - } mkString (f"$descr%n", f"%n", "") + override def help: String = { + val describe: ((String, String)) => String = { + val choiceWidth = choices.map(_.length).max + 1 + val formatStr = s" %-${choiceWidth}s %s" + locally { + case (choice, description) => formatStr.format(choice, description) + } + } + val verboseDefault = default match { + case Some("_" :: Nil) => Some("All choices are enabled by default." :: Nil) + case _ => default + } + val orelse = verboseDefault.map(_.mkString(f"%nDefault: ", ", ", f"%n")).getOrElse("") + choices.zipAll(descriptions, "", "").map(describe).mkString(f"${descr}%n", f"%n", orelse) } def clear(): Unit = { @@ -808,18 +816,33 @@ class MutableSettings(val errorFn: String => Unit) helpArg: String, descr: String, override val choices: List[String], - val default: String) - extends Setting(name, descr + choices.mkString(" (", ",", ") default:" + default)) { + val default: String, + val choicesHelp: List[String]) + extends Setting(name, + if (choicesHelp.isEmpty) s"$descr Choices: ${choices.mkString("(", ",", ")")}, default: $default." + else s"$descr Default: `$default', `help' to list choices.") { type T = String protected var v: T = default def indexOfChoice: Int = choices indexOf value - private def usageErrorMessage = f"Usage: $name:<$helpArg>%n where <$helpArg> choices are ${choices mkString ", "} (default: $default)%n" + private def choicesHelpMessage = if (choicesHelp.isEmpty) "" else { + val choiceLength = choices.map(_.length).max + 1 + val formatStr = s" %-${choiceLength}s %s%n" + choices.zipAll(choicesHelp, "", "").map({ + case (choice, desc) => formatStr.format(choice, desc) + }).mkString("") + } + private def usageErrorMessage = f"Usage: $name:<$helpArg> where <$helpArg> choices are ${choices mkString ", "} (default: $default).%n$choicesHelpMessage" + + private var sawHelp = false + override def isHelping = sawHelp + override def help = usageErrorMessage def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None) override def tryToSetColon(args: List[String]) = args match { case Nil => errorAndValue(usageErrorMessage, None) + case List("help") => sawHelp = true; Some(Nil) case List(x) if choices contains x => value = x ; Some(Nil) case List(x) => errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None) case xs => errorAndValue("'" + name + "' does not accept multiple arguments.", None) diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala index dae8539c66..5eb99e0d98 100644 --- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala @@ -38,11 +38,8 @@ trait ScalaSettings extends AbsScalaSettings /** If any of these settings is enabled, the compiler should print a message and exit. */ def infoSettings = List[Setting](version, help, Xhelp, Yhelp, showPlugins, showPhases, genPhaseGraph) - /** Any -multichoice:help? Nicer if any option could report that it had help to offer. */ - private def multihelp = allSettings exists { case s: MultiChoiceSetting[_] => s.isHelping case _ => false } - - /** Is an info setting set? */ - def isInfo = (infoSettings exists (_.isSetByUser)) || multihelp + /** Is an info setting set? Any -option:help? */ + def isInfo = infoSettings.exists(_.isSetByUser) || allSettings.exists(_.isHelping) /** Disable a setting */ def disable(s: Setting) = allSettings -= s @@ -133,7 +130,22 @@ trait ScalaSettings extends AbsScalaSettings val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.") val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.") - val XgenMixinForwarders = BooleanSetting("-Xgen-mixin-forwarders", "Generate forwarder methods in classes inhering concrete methods from traits.") + + val XmixinForceForwarders = ChoiceSetting( + name = "-Xmixin-force-forwarders", + helpArg = "mode", + descr = "Generate forwarder methods in classes inhering concrete methods from traits.", + choices = List("true", "junit", "false"), + default = "true", + choicesHelp = List( + "Always generate mixin forwarders.", + "Generate mixin forwarders for JUnit-annotated methods (JUnit 4 does not support default methods).", + "Only generate mixin forwarders required for program correctness.")) + + object mixinForwarderChoices { + def isTruthy = XmixinForceForwarders.value == "true" + def isAtLeastJunit = isTruthy || XmixinForceForwarders.value == "junit" + } // XML parsing options object XxmlSettings extends MultiChoiceEnumeration { @@ -143,7 +155,7 @@ trait ScalaSettings extends AbsScalaSettings val Xxml = MultiChoiceSetting( name = "-Xxml", helpArg = "property", - descr = "Configure XML parsing", + descr = "Configure XML parsing.", domain = XxmlSettings ) @@ -169,7 +181,7 @@ trait ScalaSettings extends AbsScalaSettings val Ycompacttrees = BooleanSetting ("-Ycompact-trees", "Use compact tree printer when displaying trees.") val noCompletion = BooleanSetting ("-Yno-completion", "Disable tab-completion in the REPL.") val debug = BooleanSetting ("-Ydebug", "Increase the quantity of debugging output.") - val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts", List("package", "object", "error"), "error") + val termConflict = ChoiceSetting ("-Yresolve-term-conflict", "strategy", "Resolve term conflicts.", List("package", "object", "error"), "error") val log = PhasesSetting ("-Ylog", "Log operations during") val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") val Ynogenericsig = BooleanSetting ("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") @@ -193,7 +205,7 @@ trait ScalaSettings extends AbsScalaSettings val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.") val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true) val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.") - val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal) + val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler.", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal) val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None) val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup") val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects") diff --git a/src/compiler/scala/tools/nsc/settings/Warnings.scala b/src/compiler/scala/tools/nsc/settings/Warnings.scala index 7ef606b6ef..839e734abc 100644 --- a/src/compiler/scala/tools/nsc/settings/Warnings.scala +++ b/src/compiler/scala/tools/nsc/settings/Warnings.scala @@ -25,8 +25,6 @@ trait Warnings { // currently considered too noisy for general use val warnUnusedImport = BooleanSetting("-Ywarn-unused-import", "Warn when imports are unused.") - val nowarnDefaultJunitMethods = BooleanSetting("-Ynowarn-default-junit-methods", "Don't warn when a JUnit @Test method is generated as a default method (not supported in JUnit 4).") - // Experimental lint warnings that are turned off, but which could be turned on programmatically. // They are not activated by -Xlint and can't be enabled on the command line because they are not // created using the standard factory methods. diff --git a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala index c2d0f5ccec..d3c7ba4d76 100644 --- a/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/BrowsingLoaders.scala @@ -87,16 +87,16 @@ abstract class BrowsingLoaders extends GlobalSymbolLoaders { if (packagePrefix == root.fullName) { enterClass(root, name.toString, new SourcefileLoader(src)) entered += 1 - } else println("prefixes differ: "+packagePrefix+","+root.fullName) + } else log("prefixes differ: "+packagePrefix+","+root.fullName) case ModuleDef(_, name, _) => if (packagePrefix == root.fullName) { val module = enterModule(root, name.toString, new SourcefileLoader(src)) entered += 1 if (name == nme.PACKAGEkw) { - println("open package module: "+module) + log("open package module: "+module) openPackageModule(module, root) } - } else println("prefixes differ: "+packagePrefix+","+root.fullName) + } else log("prefixes differ: "+packagePrefix+","+root.fullName) case _ => } } @@ -121,7 +121,7 @@ abstract class BrowsingLoaders extends GlobalSymbolLoaders { browseTopLevel(root, src) } catch { case ex: syntaxAnalyzer.MalformedInput => - println("[%s] caught malformed input exception at offset %d: %s".format(src, ex.offset, ex.msg)) + log(s"[$src] caught malformed input exception at offset ${ex.offset}: ${ex.msg}") super.enterToplevelsFromSource(root, name, src) } } diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index b36d5d4ef1..d948d151a6 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -52,20 +52,28 @@ abstract class SymbolLoaders { }) } + def newClass(owner: Symbol, name: String): ClassSymbol = owner.newClass(newTypeName(name)) + /** Enter class with given `name` into scope of `root` * and give them `completer` as type. */ - def enterClass(owner: Symbol, name: String, completer: SymbolLoader): Symbol = { - val clazz = owner.newClass(newTypeName(name)) + def enterClass(owner: Symbol, name: String, completer: SymbolLoader): Symbol = + enterClass(owner, newClass(owner, name), completer) + + def enterClass(owner: Symbol, clazz: ClassSymbol, completer: SymbolLoader): Symbol = { clazz setInfo completer enterIfNew(owner, clazz, completer) } + def newModule(owner: Symbol, name: String): ModuleSymbol = owner.newModule(newTermName(name)) + /** Enter module with given `name` into scope of `root` * and give them `completer` as type. */ - def enterModule(owner: Symbol, name: String, completer: SymbolLoader): Symbol = { - val module = owner.newModule(newTermName(name)) + def enterModule(owner: Symbol, name: String, completer: SymbolLoader): Symbol = + enterModule(owner, newModule(owner, name), completer) + + def enterModule(owner: Symbol, module: ModuleSymbol, completer: SymbolLoader): Symbol = { module setInfo completer module.moduleClass setInfo moduleClassLoader enterIfNew(owner, module, completer) @@ -113,9 +121,12 @@ abstract class SymbolLoaders { /** Enter class and module with given `name` into scope of `root` * and give them `completer` as type. */ - def enterClassAndModule(root: Symbol, name: String, completer: SymbolLoader) { - val clazz = enterClass(root, name, completer) - val module = enterModule(root, name, completer) + def enterClassAndModule(root: Symbol, name: String, getCompleter: (ClassSymbol, ModuleSymbol) => SymbolLoader) { + val clazz = newClass(root, name) + val module = newModule(root, name) + val completer = getCompleter(clazz, module) + enterClass(root, clazz, completer) + enterModule(root, module, completer) if (!clazz.isAnonymousClass) { // Diagnostic for SI-7147 def msg: String = { @@ -136,7 +147,7 @@ abstract class SymbolLoaders { * (overridden in interactive.Global). */ def enterToplevelsFromSource(root: Symbol, name: String, src: AbstractFile) { - enterClassAndModule(root, name, new SourcefileLoader(src)) + enterClassAndModule(root, name, (_, _) => new SourcefileLoader(src)) } /** The package objects of scala and scala.reflect should always @@ -162,17 +173,10 @@ abstract class SymbolLoaders { if (settings.verbose) inform("[symloader] no class, picked up source file for " + src.path) enterToplevelsFromSource(owner, classRep.name, src) case (Some(bin), _) => - enterClassAndModule(owner, classRep.name, newClassLoader(bin)) + enterClassAndModule(owner, classRep.name, new ClassfileLoader(bin, _, _)) } } - /** Create a new loader from a binary classfile. - * This is intended as a hook allowing to support loading symbols from - * files other than .class files. - */ - protected def newClassLoader(bin: AbstractFile): SymbolLoader = - new ClassfileLoader(bin) - /** * A lazy type that completes itself by calling parameter doComplete. * Any linked modules/classes or module classes are also initialized. @@ -277,7 +281,7 @@ abstract class SymbolLoaders { } } - class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader with FlagAssigningCompleter { + class ClassfileLoader(val classfile: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol) extends SymbolLoader with FlagAssigningCompleter { private object classfileParser extends { val symbolTable: SymbolLoaders.this.symbolTable.type = SymbolLoaders.this.symbolTable } with ClassfileParser { @@ -304,13 +308,7 @@ abstract class SymbolLoaders { protected def doComplete(root: Symbol) { val start = if (Statistics.canEnable) Statistics.startTimer(classReadNanos) else null - - // Running the classfile parser after refchecks can lead to "illegal class file dependency" - // errors. More concretely, the classfile parser calls "sym.companionModule", which calls - // "isModuleNotMethod" on the companion. After refchecks, this method forces the info, which - // may run the classfile parser. This produces the error. - enteringPhase(phaseBeforeRefchecks)(classfileParser.parse(classfile, root)) - + classfileParser.parse(classfile, clazz, module) if (root.associatedFile eq NoAbstractFile) { root match { // In fact, the ModuleSymbol forwards its setter to the module class diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index f8c1a0d082..7e81fad606 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -10,6 +10,7 @@ package classfile import java.io.{File, IOException} import java.lang.Integer.toHexString + import scala.collection.{immutable, mutable} import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.annotation.switch @@ -18,6 +19,7 @@ import scala.reflect.internal.pickling.{ByteCodecs, PickleBuffer} import scala.reflect.io.NoAbstractFile import scala.tools.nsc.util.ClassPath import scala.tools.nsc.io.AbstractFile +import scala.util.control.NonFatal /** This abstract class implements a class file parser. * @@ -53,18 +55,18 @@ abstract class ClassfileParser { protected type ThisConstantPool <: ConstantPool protected def newConstantPool: ThisConstantPool - protected var file: AbstractFile = _ // the class file - protected var in: AbstractFileReader = _ // the class file reader - protected var clazz: Symbol = _ // the class symbol containing dynamic members - protected var staticModule: Symbol = _ // the module symbol containing static members - protected var instanceScope: Scope = _ // the scope of all instance definitions - protected var staticScope: Scope = _ // the scope of all static definitions - protected var pool: ThisConstantPool = _ // the classfile's constant pool - protected var isScala: Boolean = _ // does class file describe a scala class? - protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation? - protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info - protected var busy: Symbol = _ // lock to detect recursive reads - protected var currentClass: Name = _ // JVM name of the current class + protected var file: AbstractFile = _ // the class file + protected var in: AbstractFileReader = _ // the class file reader + protected var clazz: ClassSymbol = _ // the class symbol containing dynamic members + protected var staticModule: ModuleSymbol = _ // the module symbol containing static members + protected var instanceScope: Scope = _ // the scope of all instance definitions + protected var staticScope: Scope = _ // the scope of all static definitions + protected var pool: ThisConstantPool = _ // the classfile's constant pool + protected var isScala: Boolean = _ // does class file describe a scala class? + protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation? + protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info + protected var busy: Symbol = _ // lock to detect recursive reads + protected var currentClass: Name = _ // JVM name of the current class protected var classTParams = Map[Name,Symbol]() protected var srcfile0 : Option[AbstractFile] = None protected def moduleClass: Symbol = staticModule.moduleClass @@ -132,17 +134,21 @@ abstract class ClassfileParser { finally loaders.parentsLevel -= 1 } - def parse(file: AbstractFile, root: Symbol): Unit = { - debuglog("[class] >> " + root.fullName) - + /** + * `clazz` and `module` are the class and module symbols corresponding to the classfile being + * parsed. Note that the ClassfileLoader unconditionally creates both of these symbols, they may + * may get invalidated later on (.exists). + * + * Note that using `companionModule` / `companionClass` does not always work to navigate between + * those two symbols, namely when they are shadowed by a type / value in the a package object + * (scala-dev#248). + */ + def parse(file: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol): Unit = { this.file = file - pushBusy(root) { + pushBusy(clazz) { this.in = new AbstractFileReader(file) - this.clazz = if (root.isModule) root.companionClass else root - // WARNING! do no use clazz.companionModule to find staticModule. - // In a situation where root can be defined, but its companionClass not, - // this would give incorrect results (see SI-5031 in separate compilation scenario) - this.staticModule = if (root.isModule) root else root.companionModule + this.clazz = clazz + this.staticModule = module this.isScala = false parseHeader() @@ -271,7 +277,7 @@ abstract class ClassfileParser { * arrays are considered to be class types, they might * appear as entries in 'newarray' or 'cast' opcodes. */ - def getClassOrArrayType(index: Int): Type = ( + def getClassOrArrayType(index: Int): Type = { if (index <= 0 || len <= index) errorBadIndex(index) else values(index) match { case tp: Type => tp @@ -283,7 +289,7 @@ abstract class ClassfileParser { case _ => recordAtIndex(classNameToSymbol(name), index).tpe_* } } - ) + } def getType(index: Int): Type = getType(null, index) def getType(sym: Symbol, index: Int): Type = sigToType(sym, getExternalName(index)) @@ -356,63 +362,43 @@ abstract class ClassfileParser { abort(s"bad constant pool tag ${in.buf(start)} at byte $start") } - private def loadClassSymbol(name: Name): Symbol = { - val file = classPath findClassFile name.toString getOrElse { - // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented - // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects - // that are not in their correct place (see bug for details) - - // TODO More consistency with use of stub symbols in `Unpickler` - // - better owner than `NoSymbol` - // - remove eager warning - val msg = s"Class $name not found - continuing with a stub." - if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg) - return NoSymbol.newStubSymbol(name.toTypeName, msg) - } - val completer = new loaders.ClassfileLoader(file) - var owner: Symbol = rootMirror.RootClass - var sym: Symbol = NoSymbol - var ss: Name = null - var start = 0 - var end = name indexOf '.' - - while (end > 0) { - ss = name.subName(start, end) - sym = owner.info.decls lookup ss - if (sym == NoSymbol) { - sym = owner.newPackage(ss.toTermName) setInfo completer - sym.moduleClass setInfo completer - owner.info.decls enter sym - } - owner = sym.moduleClass - start = end + 1 - end = name.indexOf('.', start) - } - ss = name.subName(0, start) - owner.info.decls lookup ss orElse { - sym = owner.newClass(ss.toTypeName) setInfoAndEnter completer - debuglog("loaded "+sym+" from file "+file) - sym - } + def stubClassSymbol(name: Name): Symbol = { + // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented + // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects + // that are not in their correct place (see bug for details) + + // TODO More consistency with use of stub symbols in `Unpickler` + // - better owner than `NoSymbol` + // - remove eager warning + val msg = s"Class $name not found - continuing with a stub." + if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg) + NoSymbol.newStubSymbol(name.toTypeName, msg) } - /** FIXME - we shouldn't be doing ad hoc lookups in the empty package. - * The method called "getClassByName" should either return the class or not. - */ - private def lookupClass(name: Name) = ( + private def lookupClass(name: Name) = try { if (name containsChar '.') - rootMirror getClassByName name // see tickets #2464, #3756 + rootMirror getClassByName name else + // FIXME - we shouldn't be doing ad hoc lookups in the empty package, getClassByName should return the class definitions.getMember(rootMirror.EmptyPackageClass, name.toTypeName) - ) + } catch { + // The handler + // - prevents crashes with deficient InnerClassAttributes (SI-2464, 0ce0ad5) + // - was referenced in the bugfix commit for SI-3756 (4fb0d53), not sure why + // - covers the case when a type alias in a package object shadows a class symbol, + // getClassByName throws a MissingRequirementError (scala-dev#248) + case _: FatalError => + // getClassByName can throw a MissingRequirementError (which extends FatalError) + // definitions.getMember can throw a FatalError, for example in pos/t5165b + stubClassSymbol(name) + } /** Return the class symbol of the given name. */ def classNameToSymbol(name: Name): Symbol = { if (innerClasses contains name) innerClasses innerSymbol name else - try lookupClass(name) - catch { case _: FatalError => loadClassSymbol(name) } + lookupClass(name) } def parseClass() { @@ -441,13 +427,10 @@ abstract class ClassfileParser { } val isTopLevel = !(currentClass containsChar '$') // Java class name; *don't* try to to use Scala name decoding (SI-7532) - - val c = if (isTopLevel) pool.getClassSymbol(nameIdx) else clazz if (isTopLevel) { - if (c != clazz) { - if ((clazz eq NoSymbol) && (c ne NoSymbol)) clazz = c - else mismatchError(c) - } + val c = pool.getClassSymbol(nameIdx) + // scala-dev#248: when a type alias (in a package object) shadows a class symbol, getClassSymbol returns a stub + if (!c.isInstanceOf[StubSymbol] && c != clazz) mismatchError(c) } addEnclosingTParams(clazz) @@ -848,16 +831,19 @@ abstract class ClassfileParser { // Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME case tpnme.RuntimeAnnotationATTR => if (isScalaAnnot || !isScala) { - val scalaSigAnnot = parseAnnotations(attrLen) - if (isScalaAnnot) - scalaSigAnnot match { - case Some(san: AnnotationInfo) => - val bytes = - san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes - unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name) - case None => - throw new RuntimeException("Scala class file does not contain Scala annotation") - } + // For Scala classfiles we are only interested in the scala signature annotations. Other + // annotations should be skipped (the pickle contains the symbol's annotations). + // Skipping them also prevents some spurious warnings / errors related to SI-7014, + // SI-7551, pos/5165b + val scalaSigAnnot = parseAnnotations(onlyScalaSig = isScalaAnnot) + if (isScalaAnnot) scalaSigAnnot match { + case Some(san: AnnotationInfo) => + val bytes = + san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes + unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name) + case None => + throw new RuntimeException("Scala class file does not contain Scala annotation") + } debuglog("[class] << " + sym.fullName + sym.annotationsString) } else @@ -891,6 +877,24 @@ abstract class ClassfileParser { } } + def skipAnnotArg(): Unit = { + u1 match { + case STRING_TAG | BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | + INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG | CLASS_TAG => + in.skip(2) + + case ENUM_TAG => + in.skip(4) + + case ARRAY_TAG => + val num = u2 + for (i <- 0 until num) skipAnnotArg() + + case ANNOTATION_TAG => + parseAnnotation(u2, onlyScalaSig = true) + } + } + def parseAnnotArg: Option[ClassfileAnnotArg] = { val tag = u1 val index = u2 @@ -924,7 +928,7 @@ abstract class ClassfileParser { if (hasError) None else Some(ArrayAnnotArg(arr.toArray)) case ANNOTATION_TAG => - parseAnnotation(index) map (NestedAnnotArg(_)) + parseAnnotation(index, onlyScalaSig = false) map (NestedAnnotArg(_)) } } @@ -951,7 +955,7 @@ abstract class ClassfileParser { /* Parse and return a single annotation. If it is malformed, * return None. */ - def parseAnnotation(attrNameIndex: Int): Option[AnnotationInfo] = try { + def parseAnnotation(attrNameIndex: Int, onlyScalaSig: Boolean): Option[AnnotationInfo] = try { val attrType = pool.getType(attrNameIndex) val nargs = u2 val nvpairs = new ListBuffer[(Name, ClassfileAnnotArg)] @@ -972,18 +976,17 @@ abstract class ClassfileParser { case None => hasError = true } else - parseAnnotArg match { + if (onlyScalaSig) skipAnnotArg() + else parseAnnotArg match { case Some(c) => nvpairs += ((name, c)) case None => hasError = true } } if (hasError) None else Some(AnnotationInfo(attrType, List(), nvpairs.toList)) - } - catch { - case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found - case ex: java.lang.Error => throw ex - case ex: Throwable => + } catch { + case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found + case NonFatal(ex) => // We want to be robust when annotations are unavailable, so the very least // we can do is warn the user about the exception // There was a reference to ticket 1135, but that is outdated: a reference to a class not on @@ -992,7 +995,6 @@ abstract class ClassfileParser { // and that should never be swallowed silently. warning(s"Caught: $ex while parsing annotations in ${in.file}") if (settings.debug) ex.printStackTrace() - None // ignore malformed annotations } @@ -1014,19 +1016,18 @@ abstract class ClassfileParser { /* Parse a sequence of annotations and attaches them to the * current symbol sym, except for the ScalaSignature annotation that it returns, if it is available. */ - def parseAnnotations(len: Int): Option[AnnotationInfo] = { + def parseAnnotations(onlyScalaSig: Boolean): Option[AnnotationInfo] = { val nAttr = u2 var scalaSigAnnot: Option[AnnotationInfo] = None - for (n <- 0 until nAttr) - parseAnnotation(u2) match { - case Some(scalaSig) if (scalaSig.atp == ScalaSignatureAnnotation.tpe) => - scalaSigAnnot = Some(scalaSig) - case Some(scalaSig) if (scalaSig.atp == ScalaLongSignatureAnnotation.tpe) => - scalaSigAnnot = Some(scalaSig) - case Some(annot) => - sym.addAnnotation(annot) - case None => - } + for (n <- 0 until nAttr) parseAnnotation(u2, onlyScalaSig) match { + case Some(scalaSig) if scalaSig.atp == ScalaSignatureAnnotation.tpe => + scalaSigAnnot = Some(scalaSig) + case Some(scalaSig) if scalaSig.atp == ScalaLongSignatureAnnotation.tpe => + scalaSigAnnot = Some(scalaSig) + case Some(annot) => + sym.addAnnotation(annot) + case None => + } scalaSigAnnot } @@ -1043,7 +1044,6 @@ abstract class ClassfileParser { def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile) { def jflags = entry.jflags - val completer = new loaders.ClassfileLoader(file) val name = entry.originalName val sflags = jflags.toScalaFlags val owner = ownerForFlags(jflags) @@ -1054,8 +1054,11 @@ abstract class ClassfileParser { val (innerClass, innerModule) = if (file == NoAbstractFile) { (newStub(name.toTypeName), newStub(name.toTermName)) } else { - val cls = owner.newClass(name.toTypeName, NoPosition, sflags) setInfo completer - val mod = owner.newModule(name.toTermName, NoPosition, sflags) setInfo completer + val cls = owner.newClass(name.toTypeName, NoPosition, sflags) + val mod = owner.newModule(name.toTermName, NoPosition, sflags) + val completer = new loaders.ClassfileLoader(file, cls, mod) + cls setInfo completer + mod setInfo completer mod.moduleClass setInfo loaders.moduleClassLoader List(cls, mod.moduleClass) foreach (_.associatedFile = file) (cls, mod) @@ -1098,8 +1101,6 @@ abstract class ClassfileParser { val attrName = readTypeName() val attrLen = u4 attrName match { - case tpnme.SignatureATTR => - in.skip(attrLen) case tpnme.ScalaSignatureATTR => isScala = true val pbuf = new PickleBuffer(in.buf, in.bp, in.bp + attrLen) @@ -1166,10 +1167,10 @@ abstract class ClassfileParser { private def innerSymbol(entry: InnerClassEntry): Symbol = { val name = entry.originalName.toTypeName val enclosing = entry.enclosing - val member = ( + val member = { if (enclosing == clazz) entry.scope lookup name else lookupMemberAtTyperPhaseIfPossible(enclosing, name) - ) + } def newStub = enclosing.newStubSymbol(name, s"Unable to locate class corresponding to inner class entry for $name in owner ${entry.outerName}") member.orElse(newStub) } diff --git a/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala new file mode 100644 index 0000000000..a1923ead21 --- /dev/null +++ b/src/compiler/scala/tools/nsc/transform/AccessorSynthesis.scala @@ -0,0 +1,403 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2013 LAMP/EPFL and Lightbend, Inc + */ + +package scala.tools.nsc +package transform + +import symtab._ +import Flags._ +import scala.collection.mutable + +trait AccessorSynthesis extends Transform with ast.TreeDSL { + import global._ + import definitions._ + import CODE._ + + val EmptyThicket = EmptyTree + def Thicket(trees: List[Tree]) = if (trees.isEmpty) EmptyTree else Block(trees, EmptyTree) + def mustExplodeThicket(tree: Tree): Boolean = + tree match { + case EmptyTree => true + case Block(_, EmptyTree) => true + case _ => false + } + def explodeThicket(tree: Tree): List[Tree] = tree match { + case EmptyTree => Nil + case Block(thicket, EmptyTree) => thicket + case stat => stat :: Nil + } + + + trait AccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree + + // used while we still need to synthesize some accessors in mixins: paramaccessors and presupers + class UncheckedAccessorSynth(protected val clazz: Symbol){ + protected val _newDefs = mutable.ListBuffer[Tree]() + + def newDefs = _newDefs.toList + + /** Add tree at given position as new definition */ + protected def addDef(tree: ValOrDefDef): Unit = _newDefs += typedPos(position(tree.symbol))(tree) + + /** The position of given symbol, or, if this is undefined, + * the position of the current class. + */ + private def position(sym: Symbol) = if (sym.pos == NoPosition) clazz.pos else sym.pos + + /** Add new method definition. + * + * @param sym The method symbol. + * @param rhs The method body. + */ + def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(DefDef(sym, rhs)) + def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(ValDef(sym, rhs)) + + /** Complete `stats` with init checks and bitmaps, + * removing any abstract method definitions in `stats` that are + * matched by some symbol defined by a tree previously passed to `addDef`. + */ + def implementWithNewDefs(stats: List[Tree]): List[Tree] = { + val newDefs = _newDefs.toList + val newSyms = newDefs map (_.symbol) + def isNotDuplicate(tree: Tree) = tree match { + case DefDef(_, _, _, _, _, _) => + val sym = tree.symbol + !(sym.isDeferred && + (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe)))) + case _ => true + } + if (newDefs.isEmpty) stats + else newDefs ::: (stats filter isNotDuplicate) + } + + def accessorBody(sym: Symbol) = + if (sym.isSetter) setterBody(sym, sym.getterIn(clazz)) else getterBody(sym) + + protected def getterBody(getter: Symbol): Tree = { + assert(getter.isGetter) + assert(getter.hasFlag(PARAMACCESSOR)) + + fieldAccess(getter) + } + + protected def setterBody(setter: Symbol, getter: Symbol): Tree = { + assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") + + Assign(fieldAccess(setter), Ident(setter.firstParam)) + } + + private def fieldAccess(accessor: Symbol) = + Select(This(clazz), accessor.accessed) + + } + } + + case class BitmapInfo(symbol: Symbol, mask: Literal) { + def storageClass: ClassSymbol = symbol.info.typeSymbol.asClass + } + + + // TODO: better way to communicate from info transform to tree transfor? + private[this] val _bitmapInfo = perRunCaches.newMap[Symbol, BitmapInfo] + private[this] val _slowPathFor = perRunCaches.newMap[Symbol, Symbol]() + + def checkedAccessorSymbolSynth(clz: Symbol) = + if (settings.checkInit) new CheckInitAccessorSymbolSynth { val clazz = clz } + else new CheckedAccessorSymbolSynth { val clazz = clz } + + // base trait, with enough functionality for lazy vals -- CheckInitAccessorSymbolSynth adds logic for -Xcheckinit + trait CheckedAccessorSymbolSynth { + protected val clazz: Symbol + + protected def defaultPos = clazz.pos.focus + protected def isTrait = clazz.isTrait + protected def hasTransientAnnot(field: Symbol) = field.accessedOrSelf hasAnnotation TransientAttr + + def needsBitmap(sym: Symbol): Boolean = !(isTrait || sym.isDeferred) && sym.isMethod && sym.isLazy && !sym.isSpecialized + + + /** Examines the symbol and returns a name indicating what brand of + * bitmap it requires. The possibilities are the BITMAP_* vals + * defined in StdNames. If it needs no bitmap, nme.NO_NAME. + * + * bitmaps for checkinit fields are not inherited + */ + protected def bitmapCategory(sym: Symbol): Name = { + // ensure that nested objects are transformed TODO: still needed? + sym.initialize + + import nme._ + + if (needsBitmap(sym) && sym.isLazy) + if (hasTransientAnnot(sym)) BITMAP_TRANSIENT else BITMAP_NORMAL + else NO_NAME + } + + + def bitmapFor(sym: Symbol): BitmapInfo = _bitmapInfo(sym) + protected def hasBitmap(sym: Symbol): Boolean = _bitmapInfo isDefinedAt sym + + + /** Fill the map from fields to bitmap infos. + * + * Instead of field symbols, the map keeps their getter symbols. This makes code generation easier later. + */ + def computeBitmapInfos(decls: List[Symbol]): List[Symbol] = { + def doCategory(fields: List[Symbol], category: Name) = { + val nbFields = fields.length // we know it's > 0 + val (bitmapClass, bitmapCapacity) = + if (nbFields == 1) (BooleanClass, 1) + else if (nbFields <= 8) (ByteClass, 8) + else if (nbFields <= 32) (IntClass, 32) + else (LongClass, 64) + + // 0-based index of highest bit, divided by bits per bitmap + // note that this is only ever > 0 when bitmapClass == LongClass + val maxBitmapNumber = (nbFields - 1) / bitmapCapacity + + // transient fields get their own category + val isTransientCategory = fields.head hasAnnotation TransientAttr + + val bitmapSyms = + (0 to maxBitmapNumber).toArray map { bitmapNumber => + val bitmapSym = ( + clazz.newVariable(nme.newBitmapName(category, bitmapNumber).toTermName, defaultPos) + setInfo bitmapClass.tpe + setFlag PrivateLocal | NEEDS_TREES + ) + + bitmapSym addAnnotation VolatileAttr + + if (isTransientCategory) bitmapSym addAnnotation TransientAttr + + bitmapSym + } + + fields.zipWithIndex foreach { case (f, idx) => + val bitmapIdx = idx / bitmapCapacity + val offsetInBitmap = idx % bitmapCapacity + val mask = + if (bitmapClass == LongClass) Constant(1L << offsetInBitmap) + else Constant(1 << offsetInBitmap) + + _bitmapInfo(f) = BitmapInfo(bitmapSyms(bitmapIdx), Literal(mask)) + } + + bitmapSyms + } + + decls groupBy bitmapCategory flatMap { + case (category, fields) if category != nme.NO_NAME && fields.nonEmpty => doCategory(fields, category) + case _ => Nil + } toList + } + + def slowPathFor(lzyVal: Symbol): Symbol = _slowPathFor(lzyVal) + + def newSlowPathSymbol(lzyVal: Symbol): Symbol = { + val pos = if (lzyVal.pos != NoPosition) lzyVal.pos else defaultPos // TODO: is the else branch ever taken? + val sym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), pos, PRIVATE) setInfo MethodType(Nil, lzyVal.tpe.resultType) + _slowPathFor(lzyVal) = sym + sym + } + + } + + trait CheckInitAccessorSymbolSynth extends CheckedAccessorSymbolSynth { + /** Does this field require an initialized bit? + * Note: fields of classes inheriting DelayedInit are not checked. + * This is because they are neither initialized in the constructor + * nor do they have a setter (not if they are vals anyway). The usual + * logic for setting bitmaps does therefore not work for such fields. + * That's why they are excluded. + * Note: The `checkinit` option does not check if transient fields are initialized. + */ + protected def needsInitFlag(sym: Symbol): Boolean = + sym.isGetter && + !( sym.isInitializedToDefault + || isConstantType(sym.info.finalResultType) // SI-4742 + || sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) + || sym.accessed.hasFlag(PRESUPER) + || sym.isOuterAccessor + || (sym.owner isSubClass DelayedInitClass) + || (sym.accessed hasAnnotation TransientAttr)) + + /** Examines the symbol and returns a name indicating what brand of + * bitmap it requires. The possibilities are the BITMAP_* vals + * defined in StdNames. If it needs no bitmap, nme.NO_NAME. + * + * bitmaps for checkinit fields are not inherited + */ + override protected def bitmapCategory(sym: Symbol): Name = { + import nme._ + + super.bitmapCategory(sym) match { + case NO_NAME if needsInitFlag(sym) && !sym.isDeferred => + if (hasTransientAnnot(sym)) BITMAP_CHECKINIT_TRANSIENT else BITMAP_CHECKINIT + case category => category + } + } + + override def needsBitmap(sym: Symbol): Boolean = super.needsBitmap(sym) || !(isTrait || sym.isDeferred) && needsInitFlag(sym) + } + + + // synthesize trees based on info gathered during info transform + // (which are known to have been run because the tree transform runs afterOwnPhase) + // since we can't easily share all info via symbols and flags, we have two maps above + // (they are persisted even between phases because the -Xcheckinit logic runs during constructors) + // TODO: can we use attachments instead of _bitmapInfo and _slowPathFor? + trait CheckedAccessorTreeSynthesis extends AccessorTreeSynthesis { + + // note: we deal in getters here, not field symbols + trait SynthCheckedAccessorsTreesInClass extends CheckedAccessorSymbolSynth { + def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass + def thisRef = gen.mkAttributedThis(clazz) + + /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the + * precise comparison operator depending on the value of 'equalToZero'. + */ + def mkTest(field: Symbol, equalToZero: Boolean = true): Tree = { + val bitmap = bitmapFor(field) + val bitmapTree = thisRef DOT bitmap.symbol + + if (bitmap.storageClass == BooleanClass) { + if (equalToZero) NOT(bitmapTree) else bitmapTree + } else { + val lhs = bitmapTree GEN_&(bitmap.mask, bitmap.storageClass) + if (equalToZero) lhs GEN_==(ZERO, bitmap.storageClass) + else lhs GEN_!=(ZERO, bitmap.storageClass) + } + } + + /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ + def mkSetFlag(valSym: Symbol): Tree = { + val bitmap = bitmapFor(valSym) + def x = thisRef DOT bitmap.symbol + + Assign(x, + if (bitmap.storageClass == BooleanClass) TRUE + else { + val or = Apply(Select(x, getMember(bitmap.storageClass, nme.OR)), List(bitmap.mask)) + // NOTE: bitwise or (`|`) on two bytes yields and Int (TODO: why was this not a problem when this ran during mixins?) + // TODO: need this to make it type check -- is there another way?? + if (bitmap.storageClass != LongClass) Apply(Select(or, newTermName("to" + bitmap.storageClass.name)), Nil) + else or + } + ) + } + } + + class SynthLazyAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass { + /** + * The compute method (slow path) looks like: + * + * ``` + * def l$compute() = { + * synchronized(this) { + * if ((bitmap$n & MASK) == 0) { + * init // l$ = <rhs> + * bitmap$n = bimap$n | MASK + * } + * } + * ... + * this.f1 = null + * ... + * this.fn = null + * l$ + * } + * ``` + * + * `bitmap$n` is a byte, int or long value acting as a bitmap of initialized values. + * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it. + * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32), + * the MASK is (1 << (offset % 32)). + * + * If the class contains only a single lazy val then the bitmap is + * represented as a Boolean and the condition checking is a simple bool test. + * + * Private fields used only in this initializer are subsequently set to null. + * + * For performance reasons the double-checked locking is split into two parts, + * the first (fast) path checks the bitmap without synchronizing, and if that + * fails it initializes the lazy val within the synchronization block (slow path). + * + * This way the inliner should optimize the fast path because the method body is small enough. + */ + def expandLazyClassMember(lazyVar: global.Symbol, lazyAccessor: global.Symbol, transformedRhs: global.Tree): Tree = { + val slowPathSym = slowPathFor(lazyAccessor) + val rhsAtSlowDef = transformedRhs.changeOwner(lazyAccessor -> slowPathSym) + + val isUnit = isUnitGetter(lazyAccessor) + val selectVar = if (isUnit) UNIT else Select(thisRef, lazyVar) + val storeRes = if (isUnit) rhsAtSlowDef else Assign(selectVar, rhsAtSlowDef) + + def needsInit = mkTest(lazyAccessor) + val doInit = Block(List(storeRes), mkSetFlag(lazyAccessor)) + // the slow part of double-checked locking (TODO: is this the most efficient pattern? https://github.come/scala/scala-dev/issues/204) + val slowPathRhs = Block(gen.mkSynchronized(thisRef)(If(needsInit, doInit, EmptyTree)) :: Nil, selectVar) + + // The lazy accessor delegates to the compute method if needed, otherwise just accesses the var (it was initialized previously) + // `if ((bitmap&n & MASK) == 0) this.l$compute() else l$` + val accessorRhs = If(needsInit, Apply(Select(thisRef, slowPathSym), Nil), selectVar) + + afterOwnPhase { // so that we can assign to vals + Thicket(List((DefDef(slowPathSym, slowPathRhs)), DefDef(lazyAccessor, accessorRhs)) map typedPos(lazyAccessor.pos.focus)) + } + } + } + + class SynthInitCheckedAccessorsIn(protected val clazz: Symbol) extends SynthCheckedAccessorsTreesInClass with CheckInitAccessorSymbolSynth { + private object addInitBitsTransformer extends Transformer { + private def checkedGetter(lhs: Tree)(pos: Position) = { + val getter = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) + if (hasBitmap(getter) && needsInitFlag(getter)) { + debuglog("adding checked getter for: " + getter + " " + lhs.symbol.flagString) + List(typedPos(pos)(mkSetFlag(getter))) + } + else Nil + } + override def transformStats(stats: List[Tree], exprOwner: Symbol) = { + // !!! Ident(self) is never referenced, is it supposed to be confirming + // that self is anything in particular? + super.transformStats( + stats flatMap { + case stat@Assign(lhs@Select(This(_), _), rhs) => stat :: checkedGetter(lhs)(stat.pos.focus) + // remove initialization for default values -- TODO is this case ever hit? constructors does not generate Assigns with EmptyTree for the rhs AFAICT + case Apply(lhs@Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil + case stat => List(stat) + }, + exprOwner + ) + } + } + + /** Make getters check the initialized bit, and the class constructor & setters are changed to set the initialized bits. */ + def wrapRhsWithInitChecks(sym: Symbol)(rhs: Tree): Tree = { + // Add statements to the body of a constructor to set the 'init' bit for each field initialized in the constructor + if (sym.isConstructor) addInitBitsTransformer transform rhs + else if (isTrait || rhs == EmptyTree) rhs + else if (needsInitFlag(sym)) // getter + mkCheckedAccessorRhs(if (isUnitGetter(sym)) UNIT else rhs, rhs.pos, sym) + else if (sym.isSetter) { + val getter = sym.getterIn(clazz) + if (needsInitFlag(getter)) Block(List(rhs, typedPos(rhs.pos.focus)(mkSetFlag(getter))), UNIT) + else rhs + } + else rhs + } + + private def mkCheckedAccessorRhs(retVal: Tree, pos: Position, getter: Symbol): Tree = { + val msg = s"Uninitialized field: ${clazz.sourceFile}: ${pos.line}" + val result = + IF(mkTest(getter, equalToZero = false)). + THEN(retVal). + ELSE(Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) + + typedPos(pos)(BLOCK(result, retVal)) + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index 0fb6213d36..81df28bc87 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -456,6 +456,11 @@ abstract class CleanUp extends Statics with Transform with ast.TreeDSL { super.transform(treeCopy.ApplyDynamic(tree, atPos(fn.pos)(Ident(SymbolLiteral_dummy).setType(SymbolLiteral_dummy.info)), LIT(SymbolLiteral_bootstrap) :: arg :: Nil)) + // Drop the TypeApply, which was used in Erasure to make `synchronized { ... } ` erase like `...` + // (and to avoid boxing the argument to the polymorphic `synchronized` method). + case app@Apply(TypeApply(fun, _), args) if fun.symbol == Object_synchronized => + super.transform(treeCopy.Apply(app, fun, args)) + // Replaces `Array(Predef.wrapArray(ArrayValue(...).$asInstanceOf[...]), <tag>)` // with just `ArrayValue(...).$asInstanceOf[...]` // diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index 0a87e358b4..92823bafb2 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -450,7 +450,9 @@ abstract class Constructors extends Statics with Transform with TypingTransforme with DelayedInitHelper with OmittablesHelper with GuardianOfCtorStmts - { + with fields.CheckedAccessorTreeSynthesis + { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) val clazz = impl.symbol.owner // the transformed class @@ -525,7 +527,7 @@ abstract class Constructors extends Statics with Transform with TypingTransforme super.transform(tree) else if (canBeSupplanted(tree.symbol)) gen.mkAttributedIdent(parameter(tree.symbol)) setPos tree.pos - else if (tree.symbol.outerSource == clazz) + else if (tree.symbol.outerSource == clazz && !isDelayedInitSubclass) gen.mkAttributedIdent(parameterNamed(nme.OUTER)) setPos tree.pos else super.transform(tree) @@ -708,13 +710,19 @@ abstract class Constructors extends Statics with Transform with TypingTransforme // Initialize all parameters fields that must be kept. val paramInits = paramAccessors filterNot omittableSym map { acc => - // Check for conflicting symbol amongst parents: see bug #1960. - // It would be better to mangle the constructor parameter name since - // it can only be used internally, but I think we need more robust name - // mangling before we introduce more of it. - val conflict = clazz.info.nonPrivateMember(acc.name) filter (s => (s ne acc) && s.isGetter && !s.isOuterField && s.enclClass.isTrait) - if (conflict ne NoSymbol) - reporter.error(acc.pos, "parameter '%s' requires field but conflicts with %s".format(acc.name, conflict.fullLocationString)) + // Check for conflicting field mixed in for a val/var defined in a parent trait (neg/t1960.scala). + // Since the fields phase has already mixed in fields, we can just look for + // an existing decl with the local variant of our paramaccessor's name. + // + // TODO: mangle the constructor parameter name (it can only be used internally), though we probably first need more robust name mangling + + // sometimes acc is a field with a local name (when it's a val/var constructor param) --> exclude the `acc` itself when looking for conflicting decl + // sometimes it's not (just a constructor param) --> any conflicting decl is a problem + val conflict = clazz.info.decl(acc.name.localName).filter(sym => sym ne acc) + if (conflict ne NoSymbol) { + val orig = exitingTyper(clazz.info.nonPrivateMember(acc.name).filter(_ hasFlag ACCESSOR)) + reporter.error(acc.pos, s"parameter '${acc.name}' requires field but conflicts with ${(orig orElse conflict).fullLocationString}") + } val accSetter = if (clazz.isTrait) acc.setterIn(clazz, hasExpandedName = true) @@ -770,11 +778,20 @@ abstract class Constructors extends Statics with Transform with TypingTransforme // We never eliminate delayed hooks or the constructors, so, only filter `defs`. val prunedStats = (defs filterNot omittableStat) ::: delayedHookDefs ::: constructors + val statsWithInitChecks = + if (settings.checkInit) { + val addChecks = new SynthInitCheckedAccessorsIn(currentOwner) + prunedStats mapConserve { + case dd: DefDef => deriveDefDef(dd)(addChecks.wrapRhsWithInitChecks(dd.symbol)) + case stat => stat + } + } else prunedStats + // Add the static initializers - if (classInitStats.isEmpty) deriveTemplate(impl)(_ => prunedStats) + if (classInitStats.isEmpty) deriveTemplate(impl)(_ => statsWithInitChecks) else { - val staticCtor = staticConstructor(prunedStats, localTyper, impl.pos)(classInitStats) - deriveTemplate(impl)(_ => staticCtor :: prunedStats) + val staticCtor = staticConstructor(statsWithInitChecks, localTyper, impl.pos)(classInitStats) + deriveTemplate(impl)(_ => staticCtor :: statsWithInitChecks) } } } // TemplateTransformer diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index eecd52546c..25475515aa 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -258,7 +258,7 @@ abstract class Erasure extends InfoTransform // Anything which could conceivably be a module (i.e. isn't known to be // a type parameter or similar) must go through here or the signature is // likely to end up with Foo<T>.Empty where it needs Foo<T>.Empty$. - def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryName) + def fullNameInSig(sym: Symbol) = "L" + enteringJVM(sym.javaBinaryNameString) def jsig(tp0: Type, existentiallyBound: List[Symbol] = Nil, toplevel: Boolean = false, primitiveOK: Boolean = true): String = { val tp = tp0.dealias @@ -1093,7 +1093,7 @@ abstract class Erasure extends InfoTransform // See SI-5568. tree setSymbol Object_getClass } else { - devWarning(s"The symbol '${fn.symbol}' was interecepted but didn't match any cases, that means the intercepted methods set doesn't match the code") + devWarning(s"The symbol '${fn.symbol}' was intercepted but didn't match any cases, that means the intercepted methods set doesn't match the code") tree } } else qual match { @@ -1117,7 +1117,8 @@ abstract class Erasure extends InfoTransform case TypeApply(fun, args) if (fun.symbol.owner != AnyClass && fun.symbol != Object_asInstanceOf && - fun.symbol != Object_isInstanceOf) => + fun.symbol != Object_isInstanceOf && + fun.symbol != Object_synchronized) => // leave all other type tests/type casts, remove all other type applications preErase(fun) @@ -1194,7 +1195,7 @@ abstract class Erasure extends InfoTransform else { val tree1 = preErase(tree) tree1 match { - case TypeApply(fun, targs @ List(targ)) if fun.symbol == Any_asInstanceOf && targ.tpe == UnitTpe => + case TypeApply(fun, targs @ List(targ)) if (fun.symbol == Any_asInstanceOf || fun.symbol == Object_synchronized) && targ.tpe == UnitTpe => // SI-9066 prevent transforming `o.asInstanceOf[Unit]` to `o.asInstanceOf[BoxedUnit]`. // adaptMember will then replace the call by a reference to BoxedUnit.UNIT. treeCopy.TypeApply(tree1, transform(fun), targs).clearType() @@ -1208,7 +1209,7 @@ abstract class Erasure extends InfoTransform try super.transform(tree1).clearType() finally tpt setType specialErasure(tree1.symbol)(tree1.symbol.tpe).resultType - case ApplyDynamic(qual, Literal(Constant(boostrapMethodRef: Symbol)) :: _) => + case ApplyDynamic(qual, Literal(Constant(bootstrapMethodRef: Symbol)) :: _) => tree case _ => super.transform(tree1).clearType() diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index f3d5ceb0f0..7d50c12852 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -8,7 +8,7 @@ package tools.nsc package transform import symtab._ -import Flags.{ CASE => _, _ } +import Flags.{CASE => _, _} import scala.collection.mutable.ListBuffer /** This class ... diff --git a/src/compiler/scala/tools/nsc/transform/Fields.scala b/src/compiler/scala/tools/nsc/transform/Fields.scala index 26e517743a..0fe7a82b15 100644 --- a/src/compiler/scala/tools/nsc/transform/Fields.scala +++ b/src/compiler/scala/tools/nsc/transform/Fields.scala @@ -10,26 +10,38 @@ import scala.annotation.tailrec import symtab.Flags._ -/** Synthesize accessors and field for each (strict) val owned by a trait. +/** Synthesize accessors, fields (and bitmaps) for (lazy) vals and modules. * - * For traits: + * During Namers, a `ValDef` that is `lazy`, deferred and/or defined in a trait carries its getter's symbol. + * The underlying field symbol does not exist until this phase. * - * - Namers translates a definition `val x = rhs` into a getter `def x = rhs` -- no underlying field is created. - * - This phase synthesizes accessors and fields for any vals mixed into a non-trait class. - * - Constructors will move the rhs to an assignment in the template body. - * Those statements then move to the template into the constructor, - * which means it will initialize the fields defined in this template (and execute the corresponding side effects). - * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals. - * - A ModuleDef is desugared to a ClassDef, an accessor (which reuses the module's term symbol) - * and a module var (unless the module is static and does not implement a member of a supertype, or we're in a trait). - * For subclasses of traits that define modules, a module var is mixed in, as well as the required module accessors. + * For `val`s defined in classes, we still emit a field immediately. + * TODO: uniformly assign getter symbol to all `ValDef`s, stop using `accessed`. * - * Runs after uncurry to deal with classes that implement SAM traits with ValDefs. - * Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered. + * This phase synthesizes accessors, fields and bitmaps (for lazy or init-checked vals under -Xcheckinit) + * in the first (closest in the subclassing lattice) subclass (not a trait) of a trait. * - * We run after uncurry because it can introduce subclasses of traits with fields (SAMs with vals). - * Lambdalift also introduces new fields (paramaccessors for captured vals), but runs too late in the pipeline - * (mixins still synthesizes implementations for accessors that need to be mixed into subclasses of local traits that capture). + * For lazy vals and modules, we emit accessors that using double-checked locking (DCL) to balance thread safety + * and performance. For both lazy vals and modules, the a compute method contains the DCL's slow path. + * + * Local lazy vals do not receive bitmaps, but use a Lazy*Holder that has the volatile init bit and the computed value. + * See `mkLazyLocalDef`. + * + * Constructors will move the rhs to an assignment in the template body. + * Those statements then move to the template into the constructor, + * which means it will initialize the fields defined in this template (and execute the corresponding side effects). + * We need to maintain the connection between getter and rhs until after specialization so that it can duplicate vals. + * + * A ModuleDef is desugared to a ClassDef, an accessor (which reuses the module's term symbol) + * and a module var (unless the module is static and does not implement a member of a supertype, or we're in a trait). + * + * For subclasses of traits that define modules, a module var is mixed in, as well as the required module accessors. + * + * Phase ordering: + * - Runs after uncurry to deal with classes that implement SAM traits with ValDefs. + * - Runs before erasure (to get bridges), and thus before lambdalift/flatten, so that nested functions/definitions must be considered. + * - Lambdalift introduces new paramaccessors for captured vals, but runs too late in the pipeline, so + * mixins still synthesizes implementations for these accessors when a local trait that captures is subclassed. * * * In the future, would like to get closer to dotty, which lifts a val's RHS (a similar thing is done for template-level statements) @@ -54,10 +66,12 @@ import symtab.Flags._ * The only change due to overriding is that its value is never written to the field * (the overridden val's value is, of course, stored in the field in addition to its side-effect being performed). * - * TODO: check init support (or drop the -Xcheck-init flag??) + * TODO: Java 9 support for vals defined in traits. They are currently emitted as final, + * but the write (putfield) to the val does not occur syntactically within the <init> method + * (it's done by the trait setter, which is called from the trait's mixin constructor, + * which is called from the subclass's constructor...) */ -abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers { - +abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransformers with AccessorSynthesis { import global._ import definitions._ @@ -69,8 +83,8 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (sym.isJavaDefined || sym.isPackageClass || !sym.isClass) tp else synthFieldsAndAccessors(tp) - // we leave lazy vars/accessors and early-init vals alone for now - private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag LAZY | PRESUPER + // TODO: drop PRESUPER support when we implement trait parameters in 2.13 + private def excludedAccessorOrFieldByFlags(statSym: Symbol): Boolean = statSym hasFlag PRESUPER // used for internal communication between info and tree transform of this phase -- not pickled, not in initialflags // TODO: reuse MIXEDIN for NEEDS_TREES? @@ -139,13 +153,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor class FieldMemoization(accessorOrField: Symbol, site: Symbol) { val tp = fieldTypeOfAccessorIn(accessorOrField, site.thisType) - // not stored, no side-effect - val pureConstant = tp.isInstanceOf[ConstantType] - - // if !stored, may still have a side-effect - // (currently not distinguished -- used to think we could drop unit-typed vals, - // but the memory model cares about writes to unit-typed fields) - val stored = !pureConstant // || isUnitType(tp)) + // We can only omit strict vals of ConstantType. Lazy vals do not receive constant types (anymore). + // (See note at widenIfNecessary -- for example, the REPL breaks when we omit constant lazy vals) + // Note that a strict unit-typed val does receive a field, because we cannot omit the write to the field + // (well, we could emit it for non-@volatile ones, if I understand the memory model correctly, + // but that seems pretty edge-casey) + val constantTyped = tp.isInstanceOf[ConstantType] } private def fieldTypeForGetterIn(getter: Symbol, pre: Type): Type = getter.info.finalResultType.asSeenFrom(pre, getter.owner) @@ -174,22 +187,59 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // can't use the referenced field since it already tracks the module's moduleClass - private[this] val moduleVarOf = perRunCaches.newMap[Symbol, Symbol] - - private def newModuleVarSymbol(site: Symbol, module: Symbol, tp: Type, extraFlags: Long): TermSymbol = { -// println(s"new module var in $site for $module of type $tp") - val moduleVar = site.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | extraFlags) setInfo tp addAnnotation VolatileAttr - moduleVarOf(module) = moduleVar + private[this] val moduleOrLazyVarOf = perRunCaches.newMap[Symbol, Symbol] - moduleVar - } + // TODO: can we drop FINAL? In any case, since these variables are MUTABLE, they cannot and will + // not be emitted as ACC_FINAL. They are FINAL in the Scala sense, though: cannot be overridden. + private final val ModuleOrLazyFieldFlags = FINAL | PrivateLocal | SYNTHETIC | NEEDS_TREES - private def moduleInit(module: Symbol) = { + private def moduleInit(module: Symbol, moduleVar: Symbol) = { // println(s"moduleInit for $module in ${module.ownerChain} --> ${moduleVarOf.get(module)}") - val moduleVar = moduleVarOf(module) - gen.mkAssignAndReturn(moduleVar, gen.newModule(module, moduleVar.info)) + def moduleVarRef = gen.mkAttributedRef(moduleVar) + + // for local modules, we synchronize on the owner of the method that owns the module + val monitorHolder = This(moduleVar.owner.enclClass) + def needsInit = Apply(Select(moduleVarRef, Object_eq), List(CODE.NULL)) + val init = Assign(moduleVarRef, gen.newModule(module, moduleVar.info)) + + /** double-checked locking following https://shipilev.net/blog/2014/safe-public-construction/#_safe_publication + * + * public class SafeDCLFactory { + * private volatile Singleton instance; + * + * public Singleton get() { + * if (instance == null) { // check 1 + * synchronized(this) { + * if (instance == null) { // check 2 + * instance = new Singleton(); + * } + * } + * } + * return instance; + * } + * } + * + * TODO: optimize using local variable? + */ + val computeName = nme.newLazyValSlowComputeName(module.name) + val computeMethod = DefDef(NoMods, computeName, Nil, ListOfNil, TypeTree(UnitTpe), gen.mkSynchronized(monitorHolder)(If(needsInit, init, EmptyTree))) + Block(computeMethod :: If(needsInit, Apply(Ident(computeName), Nil), EmptyTree) :: Nil, + gen.mkCast(moduleVarRef, module.info.resultType)) } + // NoSymbol for lazy accessor sym with unit result type + def lazyVarOf(sym: Symbol) = moduleOrLazyVarOf.getOrElse(sym, NoSymbol) + + private def newLazyVarMember(clazz: Symbol, member: Symbol, tp: Type): TermSymbol = { + val flags = LAZY | (member.flags & FieldFlags) | ModuleOrLazyFieldFlags + val name = member.name.toTermName.append(reflect.NameTransformer.LOCAL_SUFFIX_STRING) + + // Set the MUTABLE flag because the field cannot be ACC_FINAL since we write to it outside of a constructor. + val sym = clazz.newVariable(name, member.pos.focus, flags) setInfo tp + + moduleOrLazyVarOf(member) = sym + sym + } private object synthFieldsAndAccessors extends TypeMap { private def newTraitSetter(getter: Symbol, clazz: Symbol) = { @@ -210,7 +260,7 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor private def newModuleAccessor(module: Symbol, site: Symbol, moduleVar: Symbol) = { val accessor = site.newMethod(module.name.toTermName, site.pos, STABLE | MODULE | NEEDS_TREES) - moduleVarOf(accessor) = moduleVar + moduleOrLazyVarOf(accessor) = moduleVar // we're in the same prefix as module, so no need for site.thisType.memberType(module) accessor setInfo MethodType(Nil, moduleVar.info) @@ -221,7 +271,6 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor accessor } - // needed for the following scenario (T could be trait or class) // trait T { def f: Object }; object O extends T { object f }. Need to generate method f in O. // marking it as an ACCESSOR so that it will get to `getterBody` when synthesizing trees below @@ -233,6 +282,19 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } + private def newSuperLazy(lazyCallingSuper: Symbol, site: Type, lazyVar: Symbol) = { + lazyCallingSuper.asTerm.referenced = lazyVar + + val tp = site.memberInfo(lazyCallingSuper) + + lazyVar setInfo tp.resultType + lazyCallingSuper setInfo tp + } + + private def classNeedsInfoTransform(cls: Symbol): Boolean = { + !(cls.isPackageClass || cls.isJavaDefined) && (currentRun.compiles(cls) || refChecks.isSeparatelyCompiledScalaSuperclass(cls)) + } + def apply(tp0: Type): Type = tp0 match { // TODO: make less destructive (name changes, decl additions, flag setting -- // none of this is actually undone when travelling back in time using atPhase) @@ -246,11 +308,16 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor if (member hasFlag ACCESSOR) { val fieldMemoization = fieldMemoizationIn(member, clazz) // check flags before calling makeNotPrivate - val accessorUnderConsideration = !(member hasFlag (DEFERRED | LAZY)) + val accessorUnderConsideration = !(member hasFlag DEFERRED) // destructively mangle accessor's name (which may cause rehashing of decls), also sets flags // this accessor has to be implemented in a subclass -- can't be private - if ((member hasFlag PRIVATE) && fieldMemoization.stored) member makeNotPrivate clazz + if ((member hasFlag PRIVATE) && !fieldMemoization.constantTyped) member makeNotPrivate clazz + // Since we need to refer to `member` using a super call in a subclass, we must ensure that access is allowed. + // If `member` has an access boundary, make sure the `PROTECTED` flag is set, + // to widen from `private[foo]` to `protected[foo]` + // (note that `member.hasAccessBoundary` implies `!member.hasFlag(PRIVATE)`, so we don't have to `resetFlag PRIVATE`) + else if (member.isLazy && member.hasAccessBoundary) member setFlag PROTECTED // This must remain in synch with publicizeTraitMethod in Mixins, so that the // synthesized member in a subclass and the trait member remain in synch regarding access. @@ -262,10 +329,10 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // (not sure why this only problem only arose when we started setting the notPROTECTED flag) // derive trait setter after calling makeNotPrivate (so that names are mangled consistently) - if (accessorUnderConsideration && fieldMemoization.stored) { + if (accessorUnderConsideration && !fieldMemoization.constantTyped) { synthesizeImplInSubclasses(member) - if (member hasFlag STABLE) // TODO: check isGetter? + if ((member hasFlag STABLE) && !(member hasFlag LAZY)) newDecls += newTraitSetter(member, clazz) } } else if (member hasFlag MODULE) { @@ -283,42 +350,61 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor ClassInfoType(parents, allDecls, clazz) } else tp + + case tp@ClassInfoType(parents, oldDecls, clazz) if !classNeedsInfoTransform(clazz) => tp + // mix in fields & accessors for all mixed in traits + case tp@ClassInfoType(parents, oldDecls, clazz) => - case tp@ClassInfoType(parents, oldDecls, clazz) if !clazz.isPackageClass => val site = clazz.thisType // setter conflicts cannot arise independently from a getter conflict, since a setter without a getter does not a val definition make - def accessorConflictsExistingVal(accessor: Symbol): Boolean = { - val existingGetter = oldDecls.lookup(accessor.name.getterName) -// println(s"$existingGetter from $accessor to ${accessor.name.getterName}") - val tp = fieldTypeOfAccessorIn(accessor, site) - (existingGetter ne NoSymbol) && (tp matches (site memberInfo existingGetter).resultType) // !existingGetter.isDeferred && -- see (3) + def getterConflictsExistingVal(getter: Symbol): Boolean = + getter.isGetter && { + val existingGetter = oldDecls.lookup(getter.name) + (existingGetter ne NoSymbol) && + ((site memberInfo existingGetter) matches (site memberInfo getter)) + } + + def newModuleVarMember(module: Symbol): TermSymbol = { + val moduleVar = + (clazz.newVariable(nme.moduleVarName(module.name.toTermName), module.pos.focus, MODULEVAR | ModuleOrLazyFieldFlags) + setInfo site.memberType(module).resultType + addAnnotation VolatileAttr) + + moduleOrLazyVarOf(module) = moduleVar + + moduleVar } - def newModuleVar(member: Symbol): TermSymbol = - newModuleVarSymbol(clazz, member, site.memberType(member).resultType, PrivateLocal | SYNTHETIC | NEEDS_TREES) + def newLazyVarMember(member: Symbol): TermSymbol = + Fields.this.newLazyVarMember(clazz, member, site.memberType(member).resultType) // a module does not need treatment here if it's static, unless it has a matching member in a superclass // a non-static method needs a module var - val modulesNeedingExpansion = - oldDecls.toList.filter(m => m.isModule && (!m.isStatic || m.isOverridingSymbol)) + val modulesAndLazyValsNeedingExpansion = + oldDecls.toList.filter(m => (m.isModule && (!m.isStatic || m.isOverridingSymbol)) || m.isLazy) + + val accessorSymbolSynth = checkedAccessorSymbolSynth(tp.typeSymbol) // expand module def in class/object (if they need it -- see modulesNeedingExpansion above) - val expandedModules = - modulesNeedingExpansion map { module => + val expandedModulesAndLazyVals = ( + modulesAndLazyValsNeedingExpansion flatMap { member => + if (member.isLazy) { + List(newLazyVarMember(member), accessorSymbolSynth.newSlowPathSymbol(member)) + } // expanding module def (top-level or nested in static module) - if (module.isStatic) { // implies m.isOverridingSymbol as per above filter + else List(if (member.isStatic) { // implies m.isOverridingSymbol as per above filter // Need a module accessor, to implement/override a matching member in a superclass. // Never a need for a module var if the module is static. - newMatchingModuleAccessor(clazz, module) + newMatchingModuleAccessor(clazz, member) } else { - nonStaticModuleToMethod(module) + nonStaticModuleToMethod(member) // must reuse symbol instead of creating an accessor - module setFlag NEEDS_TREES - newModuleVar(module) - } - } + member setFlag NEEDS_TREES + newModuleVarMember(member) + }) + }) // println(s"expanded modules for $clazz: $expandedModules") @@ -344,16 +430,12 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor clonedAccessor } - if (member hasFlag MODULE) { - val moduleVar = newModuleVar(member) - List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) - } // when considering whether to mix in the trait setter, forget about conflicts -- they are reported for the getter // a trait setter for an overridden val will receive a unit body in the tree transform - else if (nme.isTraitSetterName(member.name)) { + if (nme.isTraitSetterName(member.name)) { val getter = member.getterIn(member.owner) val clone = cloneAccessor() - + setClonedTraitSetterFlags(clazz, getter, clone) // println(s"mixed in trait setter ${clone.defString}") @@ -361,8 +443,18 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } // don't cause conflicts, skip overridden accessors contributed by supertraits (only act on the last overriding one) // see pos/trait_fields_dependent_conflict.scala and neg/t1960.scala - else if (accessorConflictsExistingVal(member) || isOverriddenAccessor(member, clazz)) Nil - else if (member.isGetter && fieldMemoizationIn(member, clazz).stored) { + else if (getterConflictsExistingVal(member) || isOverriddenAccessor(member, clazz)) Nil + else if (member hasFlag MODULE) { + val moduleVar = newModuleVarMember(member) + List(moduleVar, newModuleAccessor(member, clazz, moduleVar)) + } + else if (member hasFlag LAZY) { + val mixedinLazy = cloneAccessor() + val lazyVar = newLazyVarMember(mixedinLazy) + // println(s"mixing in lazy var: $lazyVar for $member") + List(lazyVar, accessorSymbolSynth.newSlowPathSymbol(mixedinLazy), newSuperLazy(mixedinLazy, site, lazyVar)) + } + else if (member.isGetter && !fieldMemoizationIn(member, clazz).constantTyped) { // add field if needed val field = clazz.newValue(member.localName, member.pos) setInfo fieldTypeForGetterIn(member, clazz.thisType) @@ -376,27 +468,34 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } else List(cloneAccessor()) // no field needed (constant-typed getter has constant as its RHS) } -// println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") + // println(s"mixedInAccessorAndFields for $clazz: $mixedInAccessorAndFields") // omit fields that are not memoized, retain all other members - def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && !fieldMemoizationIn(sym, clazz).stored + def omittableField(sym: Symbol) = sym.isValue && !sym.isMethod && fieldMemoizationIn(sym, clazz).constantTyped val newDecls = - if (expandedModules.isEmpty && mixedInAccessorAndFields.isEmpty) oldDecls.filterNot(omittableField) + // under -Xcheckinit we generate all kinds of bitmaps, even when there are no lazy vals + if (expandedModulesAndLazyVals.isEmpty && mixedInAccessorAndFields.isEmpty && !settings.checkInit) + oldDecls.filterNot(omittableField) else { // must not alter `decls` directly val newDecls = newScope val enter = newDecls enter (_: Symbol) val enterAll = (_: List[Symbol]) foreach enter + expandedModulesAndLazyVals foreach enter oldDecls foreach { d => if (!omittableField(d)) enter(d) } - expandedModules foreach enter mixedInAccessorAndFields foreach enterAll + // both oldDecls and mixedInAccessorAndFields (a list of lists) contribute + val bitmapSyms = accessorSymbolSynth.computeBitmapInfos(newDecls.toList) + + bitmapSyms foreach enter + newDecls } -// println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") + // println(s"new decls for $clazz: $expandedModules ++ $mixedInAccessorAndFields") if (newDecls eq oldDecls) tp else ClassInfoType(parents, newDecls, clazz) @@ -408,76 +507,176 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // done by uncurry's info transformer // instead of forcing every member's info to run said transformer, duplicate the flag update logic... - def nonStaticModuleToMethod(module: Symbol): Unit = { + def nonStaticModuleToMethod(module: Symbol): Unit = if (!module.isStatic) module setFlag METHOD | STABLE - } - - class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) { - def mkTypedUnit(pos: Position) = localTyper.typedPos(pos)(CODE.UNIT) - def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) - def mkAccessor(accessor: Symbol)(body: Tree) = localTyper.typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + class FieldsTransformer(unit: CompilationUnit) extends TypingTransformer(unit) with CheckedAccessorTreeSynthesis { + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) - def mkField(sym: Symbol) = localTyper.typedPos(sym.pos)(ValDef(sym)).asInstanceOf[ValDef] + def mkTypedUnit(pos: Position) = typedPos(pos)(CODE.UNIT) + // TODO: clean up. this method is not used + def deriveUnitDef(stat: Tree) = deriveDefDef(stat)(_ => mkTypedUnit(stat.pos)) + def mkAccessor(accessor: Symbol)(body: Tree) = typedPos(accessor.pos)(DefDef(accessor, body)).asInstanceOf[DefDef] + + // this makes trees for mixed in fields, as well as for bitmap fields (their RHS will be EmptyTree because they are initialized implicitly) + // if we decide to explicitly initialize, use this RHS: if (symbol.info.typeSymbol.asClass == BooleanClass) FALSE else ZERO) + // could detect it's a bitmap field with something like `sym.name.startsWith(nme.BITMAP_PREFIX)` (or perhaps something more robust...) + def mkTypedValDef(sym: Symbol, rhs: Tree = EmptyTree) = typedPos(sym.pos)(ValDef(sym, rhs)).asInstanceOf[ValDef] + + /** + * Desugar a local `lazy val x: Int = rhs` + * or a local `object x { ...}` (the rhs will be instantiating the module's class) into: + * + * ``` + * val x$lzy = new scala.runtime.LazyInt() + * def x$lzycompute(): Int = + * x$lzy.synchronized { + * if (x$lzy.initialized()) x$lzy.value() + * else x$lzy.initialize(rhs) // for a Unit-typed lazy val, this becomes `{ rhs ; x$lzy.initialize() }` to avoid passing around BoxedUnit + * } + * def x(): Int = if (x$lzy.initialized()) x$lzy.value() else x$lzycompute() + * ``` + * + * The expansion is the same for local lazy vals and local objects, + * except for the suffix of the underlying val's name ($lzy or $module) + */ + private def mkLazyLocalDef(lazySym: Symbol, rhs: Tree): Tree = { + import CODE._ + import scala.reflect.{NameTransformer => nx} + val owner = lazySym.owner + + val lazyValType = lazySym.tpe.resultType + val refClass = lazyHolders.getOrElse(lazyValType.typeSymbol, LazyRefClass) + val isUnit = refClass == LazyUnitClass + val refTpe = if (refClass != LazyRefClass) refClass.tpe else appliedType(refClass.typeConstructor, List(lazyValType)) + + val lazyName = lazySym.name.toTermName + val pos = lazySym.pos.focus + + val localLazyName = lazyName append (if (lazySym.isModule) nx.MODULE_VAR_SUFFIX_STRING else nx.LAZY_LOCAL_SUFFIX_STRING) + + // The lazy holder val need not be mutable, as we write to its field. + // In fact, it MUST not be mutable to avoid capturing it as an ObjectRef in lambdalift + // Must be marked LAZY to allow forward references, as in `def test2 { println(s.length) ; lazy val s = "abc" } + val holderSym = owner.newValue(localLazyName, pos, LAZY | ARTIFACT) setInfo refTpe + + val initializedSym = refTpe.member(nme.initialized) + val initializeSym = refTpe.member(nme.initialize) + + // LazyUnit does not have a `value` member + val valueSym = if (isUnit) NoSymbol else refTpe.member(nme.value) + + def initialized = Select(Ident(holderSym), initializedSym) + def initialize = Select(Ident(holderSym), initializeSym) + def getValue = if (isUnit) UNIT else Apply(Select(Ident(holderSym), valueSym), Nil) + + val computerSym = + owner.newMethod(lazyName append nme.LAZY_SLOW_SUFFIX, pos, ARTIFACT | PRIVATE) setInfo MethodType(Nil, lazyValType) + + val rhsAtComputer = rhs.changeOwner(lazySym -> computerSym) + + val computer = mkAccessor(computerSym)(gen.mkSynchronized(Ident(holderSym))( + If(initialized, getValue, + if (isUnit) Block(rhsAtComputer :: Nil, Apply(initialize, Nil)) + else Apply(initialize, rhsAtComputer :: Nil)))) + + val accessor = mkAccessor(lazySym)( + If(initialized, getValue, + Apply(Ident(computerSym), Nil))) + + // do last! + // remove STABLE: prevent replacing accessor call of type Unit by BoxedUnit.UNIT in erasure + // remove ACCESSOR: prevent constructors from eliminating the method body if the lazy val is + // lifted into a trait (TODO: not sure about the details here) + lazySym.resetFlag(STABLE | ACCESSOR) + + Thicket(mkTypedValDef(holderSym, New(refTpe)) :: computer :: accessor :: Nil) + } // synth trees for accessors/fields and trait setters when they are mixed into a class - def fieldsAndAccessors(clazz: Symbol): List[ValOrDefDef] = { - def fieldAccess(accessor: Symbol): Option[Tree] = { - val fieldName = accessor.localName - val field = clazz.info.decl(fieldName) - // The `None` result denotes an error, but it's refchecks' job to report it (this fallback is for robustness). - // This is the result of overriding a val with a def, so that no field is found in the subclass. - if (field.exists) Some(Select(This(clazz), field)) - else None - } - - def getterBody(getter: Symbol): Option[Tree] = { + def fieldsAndAccessors(clazz: Symbol): List[Tree] = { + // scala/scala-dev#219 + // Cast to avoid spurious mismatch in paths containing trait vals that have + // not been rebound to accessors in the subclass we're in now. + // For example, for a lazy val mixed into a class, the lazy var's info + // will not refer to symbols created during our info transformer, + // so if its type depends on a val that is now implemented after the info transformer, + // we'll get a mismatch when assigning `rhs` to `lazyVarOf(getter)`. + // TODO: could we rebind more aggressively? consider overriding in type equality? + def cast(tree: Tree, pt: Type) = gen.mkAsInstanceOf(tree, pt) + + // Could be NoSymbol, which denotes an error, but it's refchecks' job to report it (this fallback is for robustness). + // This is the result of overriding a val with a def, so that no field is found in the subclass. + def fieldAccess(accessor: Symbol): Symbol = + afterOwnPhase { clazz.info.decl(accessor.localName) } + + def getterBody(getter: Symbol): Tree = // accessor created by newMatchingModuleAccessor for a static module that does need an accessor // (because there's a matching member in a super class) - if (getter.asTerm.referenced.isModule) { - Some(gen.mkAttributedRef(clazz.thisType, getter.asTerm.referenced)) - } else { + if (getter.asTerm.referenced.isModule) + mkAccessor(getter)(cast(Select(This(clazz), getter.asTerm.referenced), getter.info.resultType)) + else { val fieldMemoization = fieldMemoizationIn(getter, clazz) - if (fieldMemoization.pureConstant) Some(gen.mkAttributedQualifier(fieldMemoization.tp)) // TODO: drop when we no longer care about producing identical bytecode - else fieldAccess(getter) + // TODO: drop getter for constant? (when we no longer care about producing identical bytecode?) + if (fieldMemoization.constantTyped) mkAccessor(getter)(gen.mkAttributedQualifier(fieldMemoization.tp)) + else fieldAccess(getter) match { + case NoSymbol => EmptyTree + case fieldSel => mkAccessor(getter)(cast(Select(This(clazz), fieldSel), getter.info.resultType)) + } } - } // println(s"accessorsAndFieldsNeedingTrees for $templateSym: $accessorsAndFieldsNeedingTrees") - def setterBody(setter: Symbol): Option[Tree] = { + def setterBody(setter: Symbol): Tree = // trait setter in trait - if (clazz.isTrait) Some(EmptyTree) + if (clazz.isTrait) mkAccessor(setter)(EmptyTree) // trait setter for overridden val in class - else if (checkAndClearOverriddenTraitSetter(setter)) Some(mkTypedUnit(setter.pos)) + else if (checkAndClearOverriddenTraitSetter(setter)) mkAccessor(setter)(mkTypedUnit(setter.pos)) // trait val/var setter mixed into class - else fieldAccess(setter) map (fieldSel => Assign(fieldSel, Ident(setter.firstParam))) - } + else fieldAccess(setter) match { + case NoSymbol => EmptyTree + case fieldSel => afterOwnPhase { // the assign only type checks after our phase (assignment to val) + mkAccessor(setter)(Assign(Select(This(clazz), fieldSel), cast(Ident(setter.firstParam), fieldSel.info))) + } + } - def moduleAccessorBody(module: Symbol): Some[Tree] = Some( + def moduleAccessorBody(module: Symbol): Tree = // added during synthFieldsAndAccessors using newModuleAccessor // a module defined in a trait by definition can't be static (it's a member of the trait and thus gets a new instance for every outer instance) - if (clazz.isTrait) EmptyTree + if (clazz.isTrait) mkAccessor(module)(EmptyTree) // symbol created by newModuleAccessor for a (non-trait) class - else moduleInit(module) - ) + else { + mkAccessor(module)(moduleInit(module, moduleOrLazyVarOf(module))) + } - clazz.info.decls.toList.filter(checkAndClearNeedsTrees) flatMap { - case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module) map mkAccessor(module) - case setter if setter.isSetter => setterBody(setter) map mkAccessor(setter) - case getter if getter.hasFlag(ACCESSOR) => getterBody(getter) map mkAccessor(getter) - case field if !(field hasFlag METHOD) => Some(mkField(field)) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) - case _ => None + val synthAccessorInClass = new SynthLazyAccessorsIn(clazz) + def superLazy(getter: Symbol): Tree = { + assert(!clazz.isTrait) + // this contortion was the only way I can get the super select to be type checked correctly.. + // TODO: why does SelectSuper not work? + val selectSuper = Select(Super(This(clazz), tpnme.EMPTY), getter.name) + + val lazyVar = lazyVarOf(getter) + val rhs = cast(Apply(selectSuper, Nil), lazyVar.info) + + synthAccessorInClass.expandLazyClassMember(lazyVar, getter, rhs) } + + (afterOwnPhase { clazz.info.decls } toList) filter checkAndClearNeedsTrees map { + case module if module hasAllFlags (MODULE | METHOD) => moduleAccessorBody(module) + case getter if getter hasAllFlags (LAZY | METHOD) => superLazy(getter) + case setter if setter.isSetter => setterBody(setter) + case getter if getter.hasFlag(ACCESSOR) => getterBody(getter) + case field if !(field hasFlag METHOD) => mkTypedValDef(field) // vals/vars and module vars (cannot have flags PACKAGE | JAVA since those never receive NEEDS_TREES) + case _ => EmptyTree + } filterNot (_ == EmptyTree) // there will likely be many EmptyTrees, but perhaps no thicket blocks that need expanding } def rhsAtOwner(stat: ValOrDefDef, newOwner: Symbol): Tree = atOwner(newOwner)(super.transform(stat.rhs.changeOwner(stat.symbol -> newOwner))) - - private def Thicket(trees: List[Tree]) = Block(trees, EmptyTree) override def transform(stat: Tree): Tree = { - val clazz = currentOwner + val currOwner = currentOwner // often a class, but not necessarily val statSym = stat.symbol /* @@ -500,22 +699,38 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor // also remove ACCESSOR flag since there won't be an underlying field to access? case DefDef(_, _, _, _, _, rhs) if (statSym hasFlag ACCESSOR) && (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) - && !clazz.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo - && fieldMemoizationIn(statSym, clazz).pureConstant => - deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) // TODO: recurse? + && !currOwner.isTrait // we've already done this for traits.. the asymmetry will be solved by the above todo + && fieldMemoizationIn(statSym, currOwner).constantTyped => + deriveDefDef(stat)(_ => gen.mkAttributedQualifier(rhs.tpe)) + + // deferred val, trait val, lazy val (local or in class) + case vd@ValDef(mods, name, tpt, rhs) if vd.symbol.hasFlag(ACCESSOR) && treeInfo.noFieldFor(vd, currOwner) => + val transformedRhs = atOwner(statSym)(transform(rhs)) + + if (rhs == EmptyTree) mkAccessor(statSym)(EmptyTree) + else if (currOwner.isTrait) mkAccessor(statSym)(transformedRhs) + else if (!currOwner.isClass) mkLazyLocalDef(vd.symbol, transformedRhs) + else { + // TODO: make `synthAccessorInClass` a field and update it in atOwner? + // note that `LazyAccessorTreeSynth` is pretty lightweight + // (it's just a bunch of methods that all take a `clazz` parameter, which is thus stored as a field) + val synthAccessorInClass = new SynthLazyAccessorsIn(currOwner) + synthAccessorInClass.expandLazyClassMember(lazyVarOf(statSym), statSym, transformedRhs) + } // drop the val for (a) constant (pure & not-stored) and (b) not-stored (but still effectful) fields case ValDef(mods, _, _, rhs) if (rhs ne EmptyTree) && !excludedAccessorOrFieldByFlags(statSym) - && fieldMemoizationIn(statSym, clazz).pureConstant => - EmptyTree + && currOwner.isClass && fieldMemoizationIn(statSym, currOwner).constantTyped => + EmptyThicket case ModuleDef(_, _, impl) => // ??? The typer doesn't take kindly to seeing this ClassDef; we have to set NoType so it will be ignored. val cd = super.transform(ClassDef(statSym.moduleClass, impl) setType NoType) - if (clazz.isClass) cd + if (currOwner.isClass) cd else { // local module -- symbols cannot be generated by info transformer, so do it all here - val moduleVar = newModuleVarSymbol(currentOwner, statSym, statSym.info.resultType, 0) - Thicket(cd :: mkField(moduleVar) :: mkAccessor(statSym)(moduleInit(statSym)) :: Nil) + val Block(stats, _) = mkLazyLocalDef(statSym, gen.newModule(statSym, statSym.info.resultType)) + + Thicket(cd :: stats) } case tree => @@ -524,26 +739,28 @@ abstract class Fields extends InfoTransform with ast.TreeDSL with TypingTransfor } } + def transformTermsAtExprOwner(exprOwner: Symbol)(stat: Tree) = if (stat.isTerm) atOwner(exprOwner)(transform(stat)) else transform(stat) override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { val addedStats = - if (!currentOwner.isClass) Nil - else afterOwnPhase { fieldsAndAccessors(currentOwner) } + if (!currentOwner.isClass || currentOwner.isPackageClass) Nil + else { + val thickets = fieldsAndAccessors(currentOwner) + if (thickets exists mustExplodeThicket) + thickets flatMap explodeThicket + else thickets + } val newStats = stats mapConserve (if (exprOwner != currentOwner) transformTermsAtExprOwner(exprOwner) else transform) addedStats ::: (if (newStats eq stats) stats else { // check whether we need to flatten thickets and drop empty ones - if (newStats exists { case EmptyTree => true case Block(_, EmptyTree) => true case _ => false }) - newStats flatMap { - case EmptyTree => Nil - case Block(thicket, EmptyTree) => thicket - case stat => stat :: Nil - } + if (newStats exists mustExplodeThicket) + newStats flatMap explodeThicket else newStats }) } diff --git a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala index 74e6c58388..798cfcd072 100644 --- a/src/compiler/scala/tools/nsc/transform/LambdaLift.scala +++ b/src/compiler/scala/tools/nsc/transform/LambdaLift.scala @@ -104,8 +104,31 @@ abstract class LambdaLift extends InfoTransform { /** Buffers for lifted out classes and methods */ private val liftedDefs = new LinkedHashMap[Symbol, List[Tree]] + val delayedInitDummies = new mutable.HashMap[Symbol, Symbol] + + /** + * For classes capturing locals, LambdaLift uses `local.logicallyEnclosingMember` to decide + * whether an access to the local is re-written to the field or constructor parameter. If the + * access is in a constructor statement, the constructor parameter is used. + * + * For DelayedInit subclasses, constructor statements end up in the synthetic init method + * instead of the constructor itself, so the access should go to the field. This method changes + * `logicallyEnclosingMember` in this case to return a temprorary symbol corresponding to that + * method. + */ + private def logicallyEnclosingMember(sym: Symbol): Symbol = { + if (sym.isLocalDummy) { + val enclClass = sym.enclClass + if (enclClass.isSubClass(DelayedInitClass)) + delayedInitDummies.getOrElseUpdate(enclClass, enclClass.newMethod(nme.delayedInit)) + else + enclClass.primaryConstructor + } else if (sym.isMethod || sym.isClass || sym == NoSymbol) sym + else logicallyEnclosingMember(sym.owner) + } + private def isSameOwnerEnclosure(sym: Symbol) = - sym.owner.logicallyEnclosingMember == currentOwner.logicallyEnclosingMember + logicallyEnclosingMember(sym.owner) == logicallyEnclosingMember(currentOwner) /** Mark symbol `sym` as being free in `enclosure`, unless `sym` * is defined in `enclosure` or there is a class between `enclosure`s owner @@ -139,9 +162,9 @@ abstract class LambdaLift extends InfoTransform { */ private def markFree(sym: Symbol, enclosure: Symbol): Boolean = { // println(s"mark free: ${sym.fullLocationString} marked free in $enclosure") - (enclosure == sym.owner.logicallyEnclosingMember) || { - debuglog("%s != %s".format(enclosure, sym.owner.logicallyEnclosingMember)) - if (enclosure.isPackageClass || !markFree(sym, enclosure.skipConstructor.owner.logicallyEnclosingMember)) false + (enclosure == logicallyEnclosingMember(sym.owner)) || { + debuglog("%s != %s".format(enclosure, logicallyEnclosingMember(sym.owner))) + if (enclosure.isPackageClass || !markFree(sym, logicallyEnclosingMember(enclosure.skipConstructor.owner))) false else { val ss = symSet(free, enclosure) if (!ss(sym)) { @@ -184,14 +207,14 @@ abstract class LambdaLift extends InfoTransform { if (sym == NoSymbol) { assert(name == nme.WILDCARD) } else if (sym.isLocalToBlock) { - val owner = currentOwner.logicallyEnclosingMember + val owner = logicallyEnclosingMember(currentOwner) if (sym.isTerm && !sym.isMethod) markFree(sym, owner) else if (sym.isMethod) markCalled(sym, owner) //symSet(called, owner) += sym } case Select(_, _) => if (sym.isConstructor && sym.owner.isLocalToBlock) - markCalled(sym, currentOwner.logicallyEnclosingMember) + markCalled(sym, logicallyEnclosingMember(currentOwner)) case _ => } super.traverse(tree) @@ -283,17 +306,18 @@ abstract class LambdaLift extends InfoTransform { private def proxy(sym: Symbol) = { def searchIn(enclosure: Symbol): Symbol = { - if (enclosure eq NoSymbol) throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )") - debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + enclosure.logicallyEnclosingMember) + if (enclosure eq NoSymbol) + throw new IllegalArgumentException("Could not find proxy for "+ sym.defString +" in "+ sym.ownerChain +" (currentOwner= "+ currentOwner +" )") + debuglog("searching for " + sym + "(" + sym.owner + ") in " + enclosure + " " + logicallyEnclosingMember(enclosure)) val proxyName = proxyNames.getOrElse(sym, sym.name) - val ps = (proxies get enclosure.logicallyEnclosingMember).toList.flatten find (_.name == proxyName) + val ps = (proxies get logicallyEnclosingMember(enclosure)).toList.flatten find (_.name == proxyName) ps getOrElse searchIn(enclosure.skipConstructor.owner) } debuglog("proxy %s from %s has logical enclosure %s".format( sym.debugLocationString, currentOwner.debugLocationString, - sym.owner.logicallyEnclosingMember.debugLocationString) + logicallyEnclosingMember(sym.owner).debugLocationString) ) if (isSameOwnerEnclosure(sym)) sym @@ -319,7 +343,14 @@ abstract class LambdaLift extends InfoTransform { else if (clazz.isStaticOwner) gen.mkAttributedQualifier(clazz.thisType) else outerValue match { case EmptyTree => prematureSelfReference() - case o => outerPath(o, currentClass.outerClass, clazz) + case o => + val path = outerPath(o, currentClass.outerClass, clazz) + if (path.tpe <:< clazz.tpeHK) path + else { + // SI-9920 The outer accessor might have an erased type of the self type of a trait, + // rather than the trait itself. Add a cast if necessary. + gen.mkAttributedCast(path, clazz.tpeHK) + } } } diff --git a/src/compiler/scala/tools/nsc/transform/LazyVals.scala b/src/compiler/scala/tools/nsc/transform/LazyVals.scala deleted file mode 100644 index fc7999bf3b..0000000000 --- a/src/compiler/scala/tools/nsc/transform/LazyVals.scala +++ /dev/null @@ -1,318 +0,0 @@ -package scala.tools.nsc -package transform - -import scala.collection.mutable - -abstract class LazyVals extends Transform with TypingTransformers with ast.TreeDSL { - // inherits abstract value `global` and class `Phase` from Transform - - import global._ // the global environment - import definitions._ // standard classes and methods - import typer.typed // methods to type trees - import CODE._ - - val phaseName: String = "lazyvals" - private val FLAGS_PER_BYTE: Int = 8 // Byte - private def bitmapKind = ByteClass - - def newTransformer(unit: CompilationUnit): Transformer = - new LazyValues(unit) - - private def lazyUnit(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass - - object LocalLazyValFinder extends Traverser { - var result: Boolean = _ - - def find(t: Tree) = {result = false; traverse(t); result} - def find(ts: List[Tree]) = {result = false; traverseTrees(ts); result} - - override def traverse(t: Tree) { - if (!result) - t match { - case v@ValDef(_, _, _, _) if v.symbol.isLazy => - result = true - - case d@DefDef(_, _, _, _, _, _) if d.symbol.isLazy && lazyUnit(d.symbol) => - d.symbol.resetFlag(symtab.Flags.LAZY) - result = true - - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) => - - // Avoid adding bitmaps when they are fully overshadowed by those that are added inside loops - case LabelDef(name, _, _) if nme.isLoopHeaderLabel(name) => - - case _ => - super.traverse(t) - } - } - } - - /** - * Transform local lazy accessors to check for the initialized bit. - */ - class LazyValues(unit: CompilationUnit) extends TypingTransformer(unit) { - /** map from method symbols to the number of lazy values it defines. */ - private val lazyVals = perRunCaches.newMap[Symbol, Int]() withDefaultValue 0 - - import symtab.Flags._ - private def flattenThickets(stats: List[Tree]): List[Tree] = stats.flatMap(_ match { - case b @ Block(List(d1@DefDef(_, n1, _, _, _, _)), d2@DefDef(_, n2, _, _, _, _)) if b.tpe == null && n1.endsWith(nme.LAZY_SLOW_SUFFIX) => - List(d1, d2) - case stat => - List(stat) - }) - - /** Perform the following transformations: - * - for a lazy accessor inside a method, make it check the initialization bitmap - * - implement double checked locking of member modules for non-trait owners (trait just have the abstract accessor) - * ``` - * // typer - * class C { object x } - * // fields - * class C { var x$module; def x() = { x$module = new x; x$module } - * // lazyvals - * class C { - * var x$module // module var - * def x() = { if (x$module == null) x$lzycompute() else x$module // fast path - * def x$lzycompute() = { synchronized { if (x$module == null) x$module = new x }; x$module } // slow path - * } - * ``` - * - for all methods, add enough int vars to allow one flag per lazy local value - * - blocks in template bodies behave almost like methods. A single bitmaps section is - * added in the first block, for all lazy values defined in such blocks. - * - remove ACCESSOR flags: accessors in traits are not statically implemented, - * but moved to the host class. local lazy values should be statically implemented. - */ - override def transform(tree: Tree): Tree = { - val sym = tree.symbol - curTree = tree - - tree match { - - case Block(_, _) => - val block1 = super.transform(tree) - val Block(stats, expr) = block1 - treeCopy.Block(block1, flattenThickets(stats), expr) - - case DefDef(_, _, _, _, _, rhs) => atOwner(tree.symbol) { - val (res, slowPathDef) = if (!sym.owner.isClass && sym.isLazy) { - val enclosingClassOrDummyOrMethod = { - val enclMethod = sym.enclMethod - - if (enclMethod != NoSymbol) { - val enclClass = sym.enclClass - if (enclClass != NoSymbol && enclMethod == enclClass.enclMethod) - enclClass - else - enclMethod - } else - sym.owner - } - debuglog(s"determined enclosing class/dummy/method for lazy val as $enclosingClassOrDummyOrMethod given symbol $sym") - val idx = lazyVals(enclosingClassOrDummyOrMethod) - lazyVals(enclosingClassOrDummyOrMethod) = idx + 1 - val (rhs1, sDef) = mkLazyDef(enclosingClassOrDummyOrMethod, transform(rhs), idx, sym) - sym.resetFlag((if (lazyUnit(sym)) 0 else LAZY) | ACCESSOR) - (rhs1, sDef) - } else if (sym.hasAllFlags(MODULE | METHOD) && !sym.owner.isTrait) { - rhs match { - case b @ Block((assign @ Assign(moduleRef, _)) :: Nil, expr) => - def cond = Apply(Select(moduleRef, Object_eq), List(Literal(Constant(null)))) - val (fastPath, slowPath) = mkFastPathBody(sym.owner.enclClass, moduleRef.symbol, cond, transform(assign) :: Nil, Nil, transform(expr)) - (localTyper.typedPos(tree.pos)(fastPath), localTyper.typedPos(tree.pos)(slowPath)) - case rhs => - global.reporter.error(tree.pos, "Unexpected tree on the RHS of a module accessor: " + rhs) - (rhs, EmptyTree) - } - } else { - (transform(rhs), EmptyTree) - } - - val ddef1 = deriveDefDef(tree)(_ => if (LocalLazyValFinder.find(res)) typed(addBitmapDefs(sym, res)) else res) - if (slowPathDef != EmptyTree) { - // The contents of this block are flattened into the enclosing statement sequence, see flattenThickets - // This is a poor man's version of dotty's Thicket: https://github.com/lampepfl/dotty/blob/d5280358d1/src/dotty/tools/dotc/ast/Trees.scala#L707 - Block(slowPathDef, ddef1) - } else ddef1 - } - - case Template(_, _, body) => atOwner(currentOwner) { - // TODO: shady business... can this logic be encapsulated in LocalLazyValFinder? - var added = false - val stats = super.transformTrees(body) mapConserve { - case stat: ValDef => typed(deriveValDef(stat)(addBitmapDefs(stat.symbol, _))) - case stat: TermTree if !added && (LocalLazyValFinder find stat) => - added = true - typed(addBitmapDefs(sym, stat)) - case stat => stat - } - - val innerClassBitmaps = if (!added && currentOwner.isClass && bitmaps.contains(currentOwner)) { - // add bitmap to inner class if necessary - val toAdd0 = bitmaps(currentOwner).map(s => typed(ValDef(s, ZERO))) - toAdd0.foreach(t => { - if (currentOwner.info.decl(t.symbol.name) == NoSymbol) { - t.symbol.setFlag(PROTECTED) - currentOwner.info.decls.enter(t.symbol) - } - }) - toAdd0 - } else List() - deriveTemplate(tree)(_ => innerClassBitmaps ++ flattenThickets(stats)) - } - - case ValDef(_, _, _, _) if !sym.owner.isModule && !sym.owner.isClass => - deriveValDef(tree) { rhs0 => - val rhs = transform(rhs0) - if (LocalLazyValFinder.find(rhs)) typed(addBitmapDefs(sym, rhs)) else rhs - } - - case l@LabelDef(name0, params0, ifp0@If(_, _, _)) if name0.startsWith(nme.WHILE_PREFIX) => - val ifp1 = super.transform(ifp0) - val If(cond0, thenp0, elsep0) = ifp1 - - if (LocalLazyValFinder.find(thenp0)) - deriveLabelDef(l)(_ => treeCopy.If(ifp1, cond0, typed(addBitmapDefs(sym.owner, thenp0)), elsep0)) - else - l - - case l@LabelDef(name0, params0, block@Block(stats0, expr)) - if name0.startsWith(nme.WHILE_PREFIX) || name0.startsWith(nme.DO_WHILE_PREFIX) => - val stats1 = super.transformTrees(stats0) - if (LocalLazyValFinder.find(stats1)) - deriveLabelDef(l)(_ => treeCopy.Block(block, typed(addBitmapDefs(sym.owner, stats1.head))::stats1.tail, expr)) - else - l - - case _ => super.transform(tree) - } - } - - /** Add the bitmap definitions to the rhs of a method definition. - * If the rhs has been tail-call transformed, insert the bitmap - * definitions inside the top-level label definition, so that each - * iteration has the lazy values uninitialized. Otherwise add them - * at the very beginning of the method. - */ - private def addBitmapDefs(methSym: Symbol, rhs: Tree): Tree = { - def prependStats(stats: List[Tree], tree: Tree): Block = tree match { - case Block(stats1, res) => Block(stats ::: stats1, res) - case _ => Block(stats, tree) - } - - val bmps = bitmaps(methSym) map (ValDef(_, ZERO)) - - def isMatch(params: List[Ident]) = (params.tail corresponds methSym.tpe.params)(_.tpe == _.tpe) - - if (bmps.isEmpty) rhs else rhs match { - case Block(assign, l @ LabelDef(name, params, _)) - if (name string_== "_" + methSym.name) && isMatch(params) => - Block(assign, deriveLabelDef(l)(rhs => typed(prependStats(bmps, rhs)))) - - case _ => prependStats(bmps, rhs) - } - } - - def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): Tree = { - val owner = lzyVal.owner - val defSym = owner.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, STABLE | PRIVATE) - defSym setInfo MethodType(List(), lzyVal.tpe.resultType) - if (owner.isClass) owner.info.decls.enter(defSym) - debuglog(s"crete slow compute path $defSym with owner ${defSym.owner} for lazy val $lzyVal") - // this is a hack i don't understand for lazy vals nested in a lazy val, introduced in 3769f4d, - // tested in pos/t3670 (add9be64). class A { val n = { lazy val b = { lazy val dd = 3; dd }; b } } - // bitmaps has an entry bMethodSym -> List(bitmap$0), where bitmap$0.owner == bMethodSym. - // now we set bitmap$0.owner = b$lzycomputeMethodSym. - for (bitmap <- bitmaps(lzyVal)) bitmap.owner = defSym - val rhs: Tree = gen.mkSynchronizedCheck(clazz, cond, syncBody, stats).changeOwner(currentOwner -> defSym) - - DefDef(defSym, addBitmapDefs(lzyVal, BLOCK(rhs, retVal))) - } - - - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): (Tree, Tree) = { - val slowPathDef: Tree = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal) - (If(cond, Apply(Ident(slowPathDef.symbol), Nil), retVal), slowPathDef) - } - - /** return a 'lazified' version of rhs. Rhs should conform to the - * following schema: - * { - * l$ = <rhs> - * l$ - * } or - * <rhs> when the lazy value has type Unit (for which there is no field - * to cache its value. - * - * Similarly as for normal lazy val members (see Mixin), the result will be a tree of the form - * { if ((bitmap&n & MASK) == 0) this.l$compute() - * else l$ - * - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * l$ = <rhs> - * bitmap$n = bimap$n | MASK - * }} - * l$ - * } - * } - * where bitmap$n is a byte value acting as a bitmap of initialized values. It is - * the 'n' is (offset / 8), the MASK is (1 << (offset % 8)). If the value has type - * unit, no field is used to cache the value, so the l$compute will now look as following: - * { - * def l$compute() = { synchronized(enclosing_class_or_dummy) { - * if ((bitmap$n & MASK) == 0) { - * <rhs>; - * bitmap$n = bimap$n | MASK - * }} - * () - * } - * } - */ - private def mkLazyDef(methOrClass: Symbol, tree: Tree, offset: Int, lazyVal: Symbol): (Tree, Tree) = { - val bitmapSym = getBitmapFor(methOrClass, offset) - val mask = LIT(1 << (offset % FLAGS_PER_BYTE)) - val bitmapRef = if (methOrClass.isClass) Select(This(methOrClass), bitmapSym) else Ident(bitmapSym) - - def mkBlock(stmt: Tree) = BLOCK(stmt, mkSetFlag(bitmapSym, mask, bitmapRef), UNIT) - - debuglog(s"create complete lazy def in $methOrClass for $lazyVal") - val (block, res) = tree match { - case Block(List(assignment), res) if !lazyUnit(lazyVal) => - (mkBlock(assignment), res) - case rhs => - (mkBlock(rhs), UNIT) - } - - def cond = (bitmapRef GEN_& (mask, bitmapKind)) GEN_== (ZERO, bitmapKind) - val lazyDefs = mkFastPathBody(methOrClass.enclClass, lazyVal, cond, List(block), Nil, res) - (atPos(tree.pos)(localTyper.typed {lazyDefs._1 }), atPos(tree.pos)(localTyper.typed {lazyDefs._2 })) - } - - private def mkSetFlag(bmp: Symbol, mask: Tree, bmpRef: Tree): Tree = - bmpRef === (bmpRef GEN_| (mask, bitmapKind)) - - val bitmaps = mutable.Map[Symbol, List[Symbol]]() withDefaultValue Nil - - /** Return the symbol corresponding of the right bitmap int inside meth, - * given offset. - */ - private def getBitmapFor(meth: Symbol, offset: Int): Symbol = { - val n = offset / FLAGS_PER_BYTE - val bmps = bitmaps(meth) - if (bmps.length > n) - bmps(n) - else { - val sym = meth.newVariable(nme.newBitmapName(nme.BITMAP_NORMAL, n), meth.pos).setInfo(ByteTpe) - enteringTyper { - sym addAnnotation VolatileAttr - } - - bitmaps(meth) = (sym :: bmps).reverse - sym - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index f781426f1a..56d11d85a6 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -1,5 +1,6 @@ /* NSC -- new Scala compiler - * Copyright 2005-2013 LAMP/EPFL + * Copyright 2005-2016 LAMP/EPFL and Lightbend, Inc + * * @author Martin Odersky */ @@ -11,11 +12,13 @@ import Flags._ import scala.annotation.tailrec import scala.collection.mutable -abstract class Mixin extends InfoTransform with ast.TreeDSL { + +abstract class Mixin extends InfoTransform with ast.TreeDSL with AccessorSynthesis { import global._ import definitions._ import CODE._ + /** The name of the phase: */ val phaseName: String = "mixin" @@ -57,8 +60,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { */ private val treatedClassInfos = perRunCaches.newMap[Symbol, Type]() withDefaultValue NoType - /** Map a lazy, mixedin field accessor to its trait member accessor */ - private val initializer = perRunCaches.newMap[Symbol, Symbol]() // --------- helper functions ----------------------------------------------- @@ -71,47 +72,22 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { * (private modules, on the other hand, are implemented statically, but their * module variable is not. all such private modules are lifted, because * non-lifted private modules have been eliminated in ExplicitOuter) - * - field accessors and superaccessors, except for lazy value accessors which become initializer - * methods in the impl class (because they can have arbitrary initializers) + * - field accessors and superaccessors */ private def isImplementedStatically(sym: Symbol) = ( (sym.isMethod || ((sym hasFlag MODULE) && !sym.isStatic)) + // TODO: ^^^ non-static modules should have been turned into methods by fields by now, no? maybe the info transformer hasn't run??? && notDeferred(sym) && sym.owner.isTrait && (!sym.isModule || sym.hasFlag(PRIVATE | LIFTED)) - && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || sym.isLazy) + && (!(sym hasFlag (ACCESSOR | SUPERACCESSOR)) || (sym hasFlag LAZY)) && !sym.isPrivate && !sym.hasAllFlags(LIFTED | MODULE | METHOD) && !sym.isConstructor && (!sym.hasFlag(notPRIVATE | LIFTED) || sym.hasFlag(ACCESSOR | SUPERACCESSOR | MODULE)) ) - private def isFieldWithBitmap(field: Symbol) = { - field.info // ensure that nested objects are transformed - // For checkinit consider normal value getters - // but for lazy values only take into account lazy getters - field.isLazy && field.isMethod && !field.isDeferred - } - /** Does this field require an initialized bit? - * Note: fields of classes inheriting DelayedInit are not checked. - * This is because they are neither initialized in the constructor - * nor do they have a setter (not if they are vals anyway). The usual - * logic for setting bitmaps does therefore not work for such fields. - * That's why they are excluded. - * Note: The `checkinit` option does not check if transient fields are initialized. - */ - private def needsInitFlag(sym: Symbol) = ( - settings.checkInit - && sym.isGetter - && !sym.isInitializedToDefault - && !isConstantType(sym.info.finalResultType) // SI-4742 - && !sym.hasFlag(PARAMACCESSOR | SPECIALIZED | LAZY) - && !sym.accessed.hasFlag(PRESUPER) - && !sym.isOuterAccessor - && !(sym.owner isSubClass DelayedInitClass) - && !(sym.accessed hasAnnotation TransientAttr) - ) /** Returns the symbol that is accessed by a super-accessor in a mixin composition. * @@ -156,7 +132,6 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { ) } - private def isUnitGetter(sym: Symbol) = sym.tpe.resultType.typeSymbol == UnitClass /** Add given member to given class, and mark member as mixed-in. */ @@ -207,9 +182,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else { assert(member.isTerm && !member.isDeferred, member) // disable assert to support compiling against code compiled by an older compiler (until we re-starr) - // assert(member hasFlag LAZY | PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}") - // lazy vals still leave field symbols lying around in traits -- TODO: never emit them to begin with - // ditto for early init vals + // assert(member hasFlag PRESUPER, s"unexpected $member in $clazz ${member.debugFlagString}") clazz.info.decls.unlink(member) } @@ -243,51 +216,60 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { case NoSymbol => val isMemberOfClazz = clazz.info.findMember(member.name, 0, 0L, stableOnly = false).alternatives.contains(member) if (isMemberOfClazz) { - def genForwarder(): Unit = { - cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + def genForwarder(required: Boolean): Unit = { + val owner = member.owner + if (owner.isJavaDefined && owner.isInterface && !clazz.parentSymbols.contains(owner)) { + if (required) { + val text = s"Unable to implement a mixin forwarder for $member in $clazz unless interface ${owner.name} is directly extended by $clazz." + reporter.error(clazz.pos, text) + } + } else + cloneAndAddMixinMember(mixinClass, member).asInstanceOf[TermSymbol] setAlias member + } + + // `member` is a concrete method defined in `mixinClass`, which is a base class of + // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: + // + // - A non-trait base class of `clazz` defines a matching method. Example: + // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T + // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would + // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. + // + // - There exists another concrete, matching method in a parent interface `p` of + // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the + // forwarder is needed to disambiguate. Example: + // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 + // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves + // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". + // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 + // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM + // level. + + @tailrec + def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { + case baseClass :: rest => + if (baseClass ne mixinClass) { + val m = member.overriddenSymbol(baseClass) + val isCompeting = m.exists && { + !m.owner.isTraitOrInterface || + (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) + } + isCompeting || existsCompetingMethod(rest) + } else existsCompetingMethod(rest) + + case _ => false } - if (settings.XgenMixinForwarders) genForwarder() - else { - - // `member` is a concrete method defined in `mixinClass`, which is a base class of - // `clazz`, and the method is not overridden in `clazz`. A forwarder is needed if: - // - // - A non-trait base class of `clazz` defines a matching method. Example: - // class C {def f: Int}; trait T extends C {def f = 1}; class D extends T - // Even if C.f is abstract, the forwarder in D is needed, otherwise the JVM would - // resolve `D.f` to `C.f`, see jvms-6.5.invokevirtual. - // - // - There exists another concrete, matching method in a parent interface `p` of - // `clazz`, and the `mixinClass` does not itself extend `p`. In this case the - // forwarder is needed to disambiguate. Example: - // trait T1 {def f = 1}; trait T2 extends T1 {override def f = 2}; class C extends T2 - // In C we don't need a forwarder for f because T2 extends T1, so the JVM resolves - // C.f to T2.f non-ambiguously. See jvms-5.4.3.3, "maximally-specific method". - // trait U1 {def f = 1}; trait U2 {self:U1 => override def f = 2}; class D extends U2 - // In D the forwarder is needed, the interfaces U1 and U2 are unrelated at the JVM - // level. - - @tailrec - def existsCompetingMethod(baseClasses: List[Symbol]): Boolean = baseClasses match { - case baseClass :: rest => - if (baseClass ne mixinClass) { - val m = member.overriddenSymbol(baseClass) - val isCompeting = m.exists && { - !m.owner.isTraitOrInterface || - (!m.isDeferred && !mixinClass.isNonBottomSubClass(m.owner)) - } - isCompeting || existsCompetingMethod(rest) - } else existsCompetingMethod(rest) - - case _ => false - } - - if (existsCompetingMethod(clazz.baseClasses)) - genForwarder() - else if (!settings.nowarnDefaultJunitMethods && JUnitTestClass.exists && member.hasAnnotation(JUnitTestClass)) - warning(member.pos, "JUnit tests in traits that are compiled as default methods are not executed by JUnit 4. JUnit 5 will fix this issue.") + def generateJUnitForwarder: Boolean = { + settings.mixinForwarderChoices.isAtLeastJunit && + member.annotations.nonEmpty && + JUnitAnnotations.exists(annot => annot.exists && member.hasAnnotation(annot)) } + + if (existsCompetingMethod(clazz.baseClasses) || generateJUnitForwarder) + genForwarder(required = true) + else if (settings.mixinForwarderChoices.isTruthy) + genForwarder(required = false) } case _ => @@ -295,9 +277,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - /* Mix in members of trait mixinClass into class clazz. Also, - * for each lazy field in mixinClass, add a link from its mixed in member to its - * initializer method inside the implclass. + /* Mix in members of trait mixinClass into class clazz. */ def mixinTraitMembers(mixinClass: Symbol) { // For all members of a trait's interface do: @@ -319,28 +299,15 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } else if (mixinMember.hasFlag(ACCESSOR) && notDeferred(mixinMember) - && (mixinMember hasFlag (LAZY | PARAMACCESSOR)) + && (mixinMember hasFlag PARAMACCESSOR) && !isOverriddenAccessor(mixinMember, clazz.info.baseClasses)) { - // pick up where `fields` left off -- it already mixed in fields and accessors for regular vals. - // but has ignored lazy vals and constructor parameter accessors - // TODO: captures added by lambdalift for local traits? - // - // mixin accessor for lazy val or constructor parameter + // mixin accessor for constructor parameter // (note that a paramaccessor cannot have a constant type as it must have a user-defined type) - val mixedInAccessor = cloneAndAddMixinMember(mixinClass, mixinMember) + cloneAndAddMixinMember(mixinClass, mixinMember) + val name = mixinMember.name - if (mixinMember.isLazy) - initializer(mixedInAccessor) = - (mixinClass.info.decl(name) orElse abort(s"Could not find initializer for lazy val $name!")) - - // Add field while we're mixing in the getter (unless it's a Unit-typed lazy val) - // - // lazy val of type Unit doesn't need a field -- the bitmap is enough. - // TODO: constant-typed lazy vals... it's an extreme corner case, but we could also suppress the field in: - // `trait T { final lazy val a = "a" }; class C extends T`, but who writes code like that!? :) - // we'd also have to change the lazyvals logic if we do this - if (!nme.isSetterName(name) && !(mixinMember.isLazy && isUnitGetter(mixinMember))) { + if (!nme.isSetterName(name)) { // enteringPhase: the private field is moved to the implementation class by erasure, // so it can no longer be found in the mixinMember's owner (the trait) val accessed = enteringPickler(mixinMember.accessed) @@ -354,7 +321,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { val newFlags = ( (PrivateLocal) - | (mixinMember getFlag MUTABLE | LAZY) + | (mixinMember getFlag MUTABLE) | (if (mixinMember.hasStableFlag) 0 else MUTABLE) ) @@ -384,87 +351,27 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { override def transformInfo(sym: Symbol, tp: Type): Type = tp - /** Return a map of single-use fields to the lazy value that uses them during initialization. - * Each field has to be private and defined in the enclosing class, and there must - * be exactly one lazy value using it. - * - * Such fields will be nulled after the initializer has memoized the lazy value. - */ - def singleUseFields(templ: Template): scala.collection.Map[Symbol, List[Symbol]] = { - val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil - - object SingleUseTraverser extends Traverser { - override def traverse(tree: Tree) { - tree match { - case Assign(lhs, rhs) => traverse(rhs) // assignments don't count - case _ => - if (tree.hasSymbolField && tree.symbol != NoSymbol) { - val sym = tree.symbol - if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) - && sym.isPrivate - && !(currentOwner.isGetter && currentOwner.accessed == sym) // getter - && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) - && sym.owner == templ.symbol.owner - && !sym.isLazy - && !tree.isDef) { - debuglog("added use in: " + currentOwner + " -- " + tree) - usedIn(sym) ::= currentOwner - - } - } - super.traverse(tree) - } - } - } - SingleUseTraverser(templ) - debuglog("usedIn: " + usedIn) - usedIn filter { - case (_, member :: Nil) => member.isValue && member.isLazy - case _ => false - } - } - // --------- term transformation ----------------------------------------------- protected def newTransformer(unit: CompilationUnit): Transformer = new MixinTransformer(unit) - class MixinTransformer(unit : CompilationUnit) extends Transformer { + class MixinTransformer(unit : CompilationUnit) extends Transformer with AccessorTreeSynthesis { + /** The typer */ + private var localTyper: erasure.Typer = _ + protected def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) /** The rootContext used for typing */ private val rootContext = erasure.NoContext.make(EmptyTree, rootMirror.RootClass, newScope) - /** The typer */ - private var localTyper: erasure.Typer = _ - private def typedPos(pos: Position)(tree: Tree): Tree = localTyper.typedPos(pos)(tree) - - /** Map lazy values to the fields they should null after initialization. */ - private var lazyValNullables: Map[Symbol, Set[Symbol]] = _ - - /** Map a field symbol to a unique integer denoting its position in the class layout. - * For each class, fields defined by the class come after inherited fields. Mixed-in - * fields count as fields defined by the class itself. - */ - private val fieldOffset = perRunCaches.newMap[Symbol, Int]() - - private val bitmapKindForCategory = perRunCaches.newMap[Name, ClassSymbol]() - - // ByteClass, IntClass, LongClass - private def bitmapKind(field: Symbol): ClassSymbol = bitmapKindForCategory(bitmapCategory(field)) - - private def flagsPerBitmap(field: Symbol): Int = bitmapKind(field) match { - case BooleanClass => 1 - case ByteClass => 8 - case IntClass => 32 - case LongClass => 64 - } - + private val nullables = mutable.AnyRefMap[Symbol, Map[Symbol, List[Symbol]]]() /** The first transform; called in a pre-order traversal at phase mixin * (that is, every node is processed before its children). * What transform does: * - For every non-trait class, add all mixed in members to the class info. + * - For every non-trait class, assign null to singly used private fields after use in lazy initialization. */ private def preTransform(tree: Tree): Tree = { val sym = tree.symbol @@ -478,422 +385,104 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else if (currentOwner.isTrait) publicizeTraitMethods(currentOwner) + if (!currentOwner.isTrait) + nullables(currentOwner) = lazyValNullables(currentOwner, body) + tree + case dd: DefDef if dd.symbol.name.endsWith(nme.LAZY_SLOW_SUFFIX) => + val fieldsToNull = nullables.getOrElse(sym.enclClass, Map()).getOrElse(sym, Nil) + if (fieldsToNull.isEmpty) dd + else { + deriveDefDef(dd) { + case blk@Block(stats, expr) => + assert(dd.symbol.originalOwner.isClass, dd.symbol) + def nullify(sym: Symbol) = + Select(gen.mkAttributedThis(sym.enclClass), sym.accessedOrSelf) === NULL + val stats1 = stats ::: fieldsToNull.map(nullify) + treeCopy.Block(blk, stats1, expr) + case tree => + devWarning("Unexpected tree shape in lazy slow path") + tree + } + } case _ => tree } } - def needsInitAndHasOffset(sym: Symbol) = - needsInitFlag(sym) && (fieldOffset contains sym) - - /** Examines the symbol and returns a name indicating what brand of - * bitmap it requires. The possibilities are the BITMAP_* vals - * defined in StdNames. If it needs no bitmap, nme.NO_NAME. - */ - def bitmapCategory(field: Symbol): Name = { - import nme._ - val isNormal = ( - if (isFieldWithBitmap(field)) true - // bitmaps for checkinit fields are not inherited - else if (needsInitFlag(field) && !field.isDeferred) false - else return NO_NAME - ) - if (field.accessed hasAnnotation TransientAttr) { - if (isNormal) BITMAP_TRANSIENT - else BITMAP_CHECKINIT_TRANSIENT - } else { - if (isNormal) BITMAP_NORMAL - else BITMAP_CHECKINIT - } - } - - /** Add all new definitions to a non-trait class - * These fall into the following categories: - * - for a trait interface: - * - abstract accessors for all fields in the implementation class - * - for a non-trait class: - * - A field for every in a mixin class - * - Setters and getters for such fields - * - getters for mixed in lazy fields are completed - * - module variables and module creators for every module in a mixin class - * (except if module is lifted -- in this case the module variable - * is local to some function, and the creator method is static.) - * - A super accessor for every super accessor in a mixin class - * - Forwarders for all methods that are implemented statically - * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) - * - * @param clazz The class to which definitions are added - */ - private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { - val newDefs = mutable.ListBuffer[Tree]() - - /* Attribute given tree and anchor at given position */ - def attributedDef(pos: Position, tree: Tree): Tree = { - debuglog("add new def to " + clazz + ": " + tree) - typedPos(pos)(tree) - } - - /* The position of given symbol, or, if this is undefined, - * the position of the current class. - */ - def position(sym: Symbol) = - if (sym.pos == NoPosition) clazz.pos else sym.pos - - /* Add tree at given position as new definition */ - def addDef(pos: Position, tree: Tree) { - newDefs += attributedDef(pos, tree) - } - - /* Add new method definition. - * - * @param sym The method symbol. - * @param rhs The method body. - */ - def addDefDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), DefDef(sym, rhs)) - def addValDef(sym: Symbol, rhs: Tree = EmptyTree) = addDef(position(sym), ValDef(sym, rhs)) - - /* Add `newdefs` to `stats`, removing any abstract method definitions - * in `stats` that are matched by some symbol defined in - * `newDefs`. - */ - def add(stats: List[Tree], newDefs: List[Tree]) = { - val newSyms = newDefs map (_.symbol) - def isNotDuplicate(tree: Tree) = tree match { - case DefDef(_, _, _, _, _, _) => - val sym = tree.symbol - !(sym.isDeferred && - (newSyms exists (nsym => nsym.name == sym.name && (nsym.tpe matches sym.tpe)))) - case _ => - true - } - if (newDefs.isEmpty) stats - else newDefs ::: (stats filter isNotDuplicate) - } - - /* If `stat` is a superaccessor, complete it by adding a right-hand side. - * Note: superaccessors are always abstract until this point. - * The method to call in a superaccessor is stored in the accessor symbol's alias field. - * The rhs is: - * super.A(xs) where A is the super accessor's alias and xs are its formal parameters. - * This rhs is typed and then mixin transformed. - */ - def completeSuperAccessor(stat: Tree) = stat match { - case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor => - val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol)))) - val pt = stat.symbol.tpe.resultType - - copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) - case _ => - stat - } - - /* - * Return the bitmap field for 'offset'. Depending on the hierarchy it is possible to reuse - * the bitmap of its parents. If that does not exist yet we create one. - */ - def bitmapFor(clazz0: Symbol, offset: Int, field: Symbol): Symbol = { - val category = bitmapCategory(field) - val bitmapName = nme.newBitmapName(category, offset / flagsPerBitmap(field)).toTermName - val sym = clazz0.info.decl(bitmapName) - - assert(!sym.isOverloaded, sym) - - def createBitmap: Symbol = { - val bitmapKind = bitmapKindForCategory(category) - val sym = clazz0.newVariable(bitmapName, clazz0.pos) setInfo bitmapKind.tpe - enteringTyper(sym addAnnotation VolatileAttr) - - category match { - case nme.BITMAP_TRANSIENT | nme.BITMAP_CHECKINIT_TRANSIENT => sym addAnnotation TransientAttr - case _ => - } - val init = bitmapKind match { - case BooleanClass => ValDef(sym, FALSE) - case _ => ValDef(sym, ZERO) - } - - sym setFlag PrivateLocal - clazz0.info.decls.enter(sym) - addDef(clazz0.pos, init) - sym - } - - sym orElse createBitmap - } - - def maskForOffset(offset: Int, sym: Symbol, kind: ClassSymbol): Tree = { - def realOffset = offset % flagsPerBitmap(sym) - if (kind == LongClass ) LIT(1L << realOffset) else LIT(1 << realOffset) - } - - /* Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */ - def mkSetFlag(clazz: Symbol, offset: Int, valSym: Symbol, kind: ClassSymbol): Tree = { - val bmp = bitmapFor(clazz, offset, valSym) - def mask = maskForOffset(offset, valSym, kind) - def x = This(clazz) DOT bmp - def newValue = if (kind == BooleanClass) TRUE else (x GEN_| (mask, kind)) - - x === newValue - } - - /* Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the - * precise comparison operator depending on the value of 'equalToZero'. - */ - def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean, kind: ClassSymbol): Tree = { - val bitmapTree = (This(clazz) DOT bitmapSym) - def lhs = bitmapTree GEN_& (mask, kind) - kind match { - case BooleanClass => - if (equalToZero) NOT(bitmapTree) - else bitmapTree - case _ => - if (equalToZero) lhs GEN_== (ZERO, kind) - else lhs GEN_!= (ZERO, kind) - } - } - - def mkSlowPathDef(clazz: Symbol, lzyVal: Symbol, cond: Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Symbol = { - val defSym = clazz.newMethod(nme.newLazyValSlowComputeName(lzyVal.name.toTermName), lzyVal.pos, PRIVATE) - val params = defSym newSyntheticValueParams args.map(_.symbol.tpe) - defSym setInfoAndEnter MethodType(params, lzyVal.tpe.resultType) - val rhs: Tree = gen.mkSynchronizedCheck(attrThis, cond, syncBody, stats).changeOwner(currentOwner -> defSym) - val strictSubst = new TreeSymSubstituterWithCopying(args.map(_.symbol), params) - addDef(position(defSym), DefDef(defSym, strictSubst(BLOCK(rhs, retVal)))) - defSym - } - - def mkFastPathLazyBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree): Tree = { - mkFastPathBody(clazz, lzyVal, cond, syncBody, stats, retVal, gen.mkAttributedThis(clazz), List()) - } - - def mkFastPathBody(clazz: Symbol, lzyVal: Symbol, cond: => Tree, syncBody: List[Tree], - stats: List[Tree], retVal: Tree, attrThis: Tree, args: List[Tree]): Tree = { - val slowPathSym: Symbol = mkSlowPathDef(clazz, lzyVal, cond, syncBody, stats, retVal, attrThis, args) - If(cond, fn (This(clazz), slowPathSym, args.map(arg => Ident(arg.symbol)): _*), retVal) - } - - - /* Always copy the tree if we are going to perform sym substitution, - * otherwise we will side-effect on the tree that is used in the fast path - */ - class TreeSymSubstituterWithCopying(from: List[Symbol], to: List[Symbol]) extends TreeSymSubstituter(from, to) { - override def transform(tree: Tree): Tree = - if (tree.hasSymbolField && from.contains(tree.symbol)) - super.transform(tree.duplicate) - else super.transform(tree.duplicate) - - override def apply[T <: Tree](tree: T): T = if (from.isEmpty) tree else super.apply(tree) - } - - /* return a 'lazified' version of rhs. It uses double-checked locking to ensure - * initialization is performed at most once. For performance reasons the double-checked - * locking is split into two parts, the first (fast) path checks the bitmap without - * synchronizing, and if that fails it initializes the lazy val within the - * synchronization block (slow path). This way the inliner should optimize - * the fast path because the method body is small enough. - * Private fields used only in this initializer are subsequently set to null. - * - * @param clazz The class symbol - * @param lzyVal The symbol of this lazy field - * @param init The tree which initializes the field ( f = <rhs> ) - * @param offset The offset of this field in the flags bitmap - * - * The result will be a tree of the form - * { if ((bitmap&n & MASK) == 0) this.l$compute() - * else l$ - * - * ... - * def l$compute() = { synchronized(this) { - * if ((bitmap$n & MASK) == 0) { - * init // l$ = <rhs> - * bitmap$n = bimap$n | MASK - * }} - * l$ - * } - * - * ... - * this.f1 = null - * ... this.fn = null - * } - * where bitmap$n is a byte, int or long value acting as a bitmap of initialized values. - * The kind of the bitmap determines how many bit indicators for lazy vals are stored in it. - * For Int bitmap it is 32 and then 'n' in the above code is: (offset / 32), - * the MASK is (1 << (offset % 32)). - * If the class contains only a single lazy val then the bitmap is represented - * as a Boolean and the condition checking is a simple bool test. - */ - def mkLazyDef(clazz: Symbol, lzyVal: Symbol, init: List[Tree], retVal: Tree, offset: Int): Tree = { - def nullify(sym: Symbol) = Select(This(clazz), sym.accessedOrSelf) === LIT(null) - - val bitmapSym = bitmapFor(clazz, offset, lzyVal) - val kind = bitmapKind(lzyVal) - val mask = maskForOffset(offset, lzyVal, kind) - def cond = mkTest(clazz, mask, bitmapSym, equalToZero = true, kind) - val nulls = lazyValNullables(lzyVal).toList sortBy (_.id) map nullify - def syncBody = init ::: List(mkSetFlag(clazz, offset, lzyVal, kind), UNIT) - - if (nulls.nonEmpty) - log("nulling fields inside " + lzyVal + ": " + nulls) - - typedPos(init.head.pos)(mkFastPathLazyBody(clazz, lzyVal, cond, syncBody, nulls, retVal)) - } - - def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position, fieldSym: Symbol): Tree = { - val sym = fieldSym.getterIn(fieldSym.owner) - val bitmapSym = bitmapFor(clazz, offset, sym) - val kind = bitmapKind(sym) - val mask = maskForOffset(offset, sym, kind) - val msg = s"Uninitialized field: ${unit.source}: ${pos.line}" - val result = - IF (mkTest(clazz, mask, bitmapSym, equalToZero = false, kind)) . - THEN (retVal) . - ELSE (Throw(NewFromConstructor(UninitializedFieldConstructor, LIT(msg)))) - - typedPos(pos)(BLOCK(result, retVal)) - } - - /* Complete lazy field accessors. Applies only to classes, - * for its own (non inherited) lazy fields. If 'checkinit' - * is enabled, getters that check for the initialized bit are - * generated, and the class constructor is changed to set the - * initialized bits. - */ - def addCheckedGetters(clazz: Symbol, stats: List[Tree]): List[Tree] = { - def dd(stat: DefDef) = { - val sym = stat.symbol - def isEmpty = stat.rhs == EmptyTree - - if (!clazz.isTrait && sym.isLazy && !isEmpty) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat) { - case t if isUnitGetter(sym) => mkLazyDef(clazz, sym, List(t), UNIT, fieldOffset(sym)) - - case Block(stats, res) => - mkLazyDef(clazz, sym, stats, Select(This(clazz), res.symbol), fieldOffset(sym)) - - case t => t // pass specialized lazy vals through + /** Map lazy values to the fields they should null after initialization. */ + def lazyValNullables(clazz: Symbol, templStats: List[Tree]): Map[Symbol, List[Symbol]] = { + // if there are no lazy fields, take the fast path and save a traversal of the whole AST + if (!clazz.info.decls.exists(_.isLazy)) Map() + else { + // A map of single-use fields to the lazy value that uses them during initialization. + // Each field has to be private and defined in the enclosing class, and there must + // be exactly one lazy value using it. + // + // Such fields will be nulled after the initializer has memoized the lazy value. + val singleUseFields: Map[Symbol, List[Symbol]] = { + val usedIn = mutable.HashMap[Symbol, List[Symbol]]() withDefaultValue Nil + + object SingleUseTraverser extends Traverser { + override def traverse(tree: Tree) { + tree match { + // assignment targets don't count as a dereference -- only check the rhs + case Assign(_, rhs) => traverse(rhs) + case tree: RefTree if tree.symbol != NoSymbol => + val sym = tree.symbol + // println(s"$sym in ${sym.owner} from $currentOwner ($tree)") + if ((sym.hasAccessorFlag || (sym.isTerm && !sym.isMethod)) && sym.isPrivate && !sym.isLazy && !sym.isModule // non-lazy private field or its accessor + && !definitions.isPrimitiveValueClass(sym.tpe.resultType.typeSymbol) // primitives don't hang on to significant amounts of heap + && sym.owner == currentOwner.enclClass && !(currentOwner.isGetter && currentOwner.accessed == sym)) { + + // println("added use in: " + currentOwner + " -- " + tree) + usedIn(sym) ::= currentOwner + } + super.traverse(tree) + case _ => super.traverse(tree) + } } } - else if (needsInitFlag(sym) && !isEmpty && !clazz.hasFlag(TRAIT)) { - assert(fieldOffset contains sym, sym) - deriveDefDef(stat)(rhs => - (mkCheckedAccessor(clazz, _: Tree, fieldOffset(sym), stat.pos, sym))( - if (isUnitGetter(sym)) UNIT else rhs - ) - ) - } - else if (sym.isConstructor) { - deriveDefDef(stat)(addInitBits(clazz, _)) - } - else if (settings.checkInit && !clazz.isTrait && sym.isSetter) { - val getter = sym.getterIn(clazz) - if (needsInitFlag(getter) && fieldOffset.isDefinedAt(getter)) - deriveDefDef(stat)(rhs => Block(List(rhs, localTyper.typed(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter)))), UNIT)) - else stat - } - else stat - } - stats map { - case defn: DefDef => dd(defn) - case stat => stat + templStats foreach SingleUseTraverser.apply + // println("usedIn: " + usedIn) + + // only consider usages from non-transient lazy vals (SI-9365) + val singlyUsedIn = usedIn.filter { + case (_, member :: Nil) if member.name.endsWith(nme.LAZY_SLOW_SUFFIX) => + val lazyAccessor = member.owner.info.decl(member.name.stripSuffix(nme.LAZY_SLOW_SUFFIX)) + !lazyAccessor.accessedOrSelf.hasAnnotation(TransientAttr) + case _ => false + }.toMap + + // println("singlyUsedIn: " + singlyUsedIn) + singlyUsedIn } - } - - class AddInitBitsTransformer(clazz: Symbol) extends Transformer { - private def checkedGetter(lhs: Tree) = { - val sym = clazz.info decl lhs.symbol.getterName suchThat (_.isGetter) - if (needsInitAndHasOffset(sym)) { - debuglog("adding checked getter for: " + sym + " " + lhs.symbol.flagString) - List(localTyper typed mkSetFlag(clazz, fieldOffset(sym), sym, bitmapKind(sym))) - } - else Nil - } - override def transformStats(stats: List[Tree], exprOwner: Symbol) = { - // !!! Ident(self) is never referenced, is it supposed to be confirming - // that self is anything in particular? - super.transformStats( - stats flatMap { - case stat @ Assign(lhs @ Select(This(_), _), rhs) => stat :: checkedGetter(lhs) - // remove initialization for default values - case Apply(lhs @ Select(Ident(self), _), EmptyTree.asList) if lhs.symbol.isSetter => Nil - case stat => List(stat) - }, - exprOwner - ) - } - } - - /* Adds statements to set the 'init' bit for each field initialized - * in the body of a constructor. - */ - def addInitBits(clazz: Symbol, rhs: Tree): Tree = - new AddInitBitsTransformer(clazz) transform rhs - - // begin addNewDefs - - /* Fill the map from fields to offset numbers. - * Instead of field symbols, the map keeps their getter symbols. This makes - * code generation easier later. - */ - def buildBitmapOffsets() { - def fold(fields: List[Symbol], category: Name) = { - var idx = 0 - fields foreach { f => - fieldOffset(f) = idx - idx += 1 - } - - if (idx == 0) () - else if (idx == 1) bitmapKindForCategory(category) = BooleanClass - else if (idx < 9) bitmapKindForCategory(category) = ByteClass - else if (idx < 33) bitmapKindForCategory(category) = IntClass - else bitmapKindForCategory(category) = LongClass - } - clazz.info.decls.toList groupBy bitmapCategory foreach { - case (nme.NO_NAME, _) => () - case (category, fields) => fold(fields, category) - } - } - buildBitmapOffsets() - var stats1 = addCheckedGetters(clazz, stats) - - def getterBody(getter: Symbol) = { - assert(getter.isGetter) - val readValue = - if (getter.isLazy) { - getter.tpe.resultType match { - case ConstantType(c) => Literal(c) - case _ => - val initCall = Apply(SuperSelect(clazz, initializer(getter)), Nil) - val offset = fieldOffset(getter) - if (isUnitGetter(getter)) mkLazyDef(clazz, getter, List(initCall), UNIT, offset) - else mkLazyDef(clazz, getter, List(atPos(getter.pos)(Assign(fieldAccess(getter), initCall))), fieldAccess(getter), offset) - } - } else { - assert(getter.hasFlag(PARAMACCESSOR)) - fieldAccess(getter) - } - - if (!needsInitFlag(getter)) readValue - else mkCheckedAccessor(clazz, readValue, fieldOffset(getter), getter.pos, getter) - } - def setterBody(setter: Symbol) = { - val getter = setter.getterIn(clazz) - assert(getter.hasFlag(PARAMACCESSOR), s"missing implementation for non-paramaccessor $setter in $clazz") - - val setInitFlag = - if (!needsInitFlag(getter)) Nil - else List(mkSetFlag(clazz, fieldOffset(getter), getter, bitmapKind(getter))) + val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() + // invert the map to see which fields can be nulled for each non-transient lazy val + for ((field, users) <- singleUseFields; lazyFld <- users) map(lazyFld) += field - Block(Assign(fieldAccess(setter), Ident(setter.firstParam)) :: setInitFlag : _*) + map.mapValues(_.toList sortBy (_.id)).toMap } + } - def fieldAccess(accessor: Symbol) = Select(This(clazz), accessor.accessed) - + /** Add all new definitions to a non-trait class + * + * These fall into the following categories: + * - for a trait interface: + * - abstract accessors for all paramaccessor or early initialized fields + * - for a non-trait class: + * - field and accessor implementations for each inherited paramaccessor or early initialized field + * - A super accessor for every super accessor in a mixin class + * - Forwarders for all methods that are implemented statically + * + * All superaccessors are completed with right-hand sides (@see completeSuperAccessor) + * + * @param clazz The class to which definitions are added + */ + private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = { + val accessorSynth = new UncheckedAccessorSynth(clazz) + import accessorSynth._ // for all symbols `sym` in the class definition, which are mixed in by mixinTraitMembers for (sym <- clazz.info.decls ; if sym hasFlag MIXEDIN) { @@ -902,10 +491,10 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { if (clazz.isTrait || sym.isSuperAccessor) addDefDef(sym) // implement methods mixed in from a supertrait (the symbols were created by mixinTraitMembers) else if (sym.hasFlag(ACCESSOR) && !sym.hasFlag(DEFERRED)) { - assert(sym hasFlag (LAZY | PARAMACCESSOR), s"mixed in $sym from $clazz is not lazy/param?!?") + assert(sym hasFlag (PARAMACCESSOR), s"mixed in $sym from $clazz is not param?!?") // add accessor definitions - addDefDef(sym, if (sym.isSetter) setterBody(sym) else getterBody(sym)) + addDefDef(sym, accessorBody(sym)) } else if (!sym.isMethod) addValDef(sym) // field else if (!sym.isMacro) { // forwarder @@ -915,32 +504,33 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { } } - stats1 = add(stats1, newDefs.toList) + val implementedAccessors = implementWithNewDefs(stats) - if (clazz.isTrait) stats1 = stats1.filter { - case vd: ValDef => - assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR | LAZY), s"unexpected valdef $vd in trait $clazz") - false + if (clazz.isTrait) + implementedAccessors filter { + case vd: ValDef => assert(vd.symbol.hasFlag(PRESUPER | PARAMACCESSOR), s"unexpected valdef $vd in trait $clazz"); false case _ => true } + else { + /* If `stat` is a superaccessor, complete it by adding a right-hand side. + * Note: superaccessors are always abstract until this point. + * The method to call in a superaccessor is stored in the accessor symbol's alias field. + * The rhs is: + * super.A(xs) where A is the super accessor's alias and xs are its formal parameters. + * This rhs is typed and then mixin transformed. + */ + def completeSuperAccessor(stat: Tree) = stat match { + case DefDef(_, _, _, vparams :: Nil, _, EmptyTree) if stat.symbol.isSuperAccessor => + val body = atPos(stat.pos)(Apply(SuperSelect(clazz, stat.symbol.alias), vparams map (v => Ident(v.symbol)))) + val pt = stat.symbol.tpe.resultType + + copyDefDef(stat)(rhs = enteringMixin(transform(localTyper.typed(body, pt)))) + case _ => + stat + } - if (!clazz.isTrait) stats1 = stats1 map completeSuperAccessor - - stats1 - } - - private def nullableFields(templ: Template): Map[Symbol, Set[Symbol]] = { - val scope = templ.symbol.owner.info.decls - // if there are no lazy fields, take the fast path and save a traversal of the whole AST - if (scope exists (_.isLazy)) { - val map = mutable.Map[Symbol, Set[Symbol]]() withDefaultValue Set() - // check what fields can be nulled for - for ((field, users) <- singleUseFields(templ); lazyFld <- users if !lazyFld.accessed.hasAnnotation(TransientAttr)) - map(lazyFld) += field - - map.toMap + implementedAccessors map completeSuperAccessor } - else Map() } /** The transform that gets applied to a tree after it has been completely @@ -961,8 +551,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { case templ @ Template(parents, self, body) => // change parents of templates to conform to parents in the symbol info val parents1 = currentOwner.info.parents map (t => TypeTree(t) setPos tree.pos) - // mark fields which can be nulled afterward - lazyValNullables = nullableFields(templ) withDefaultValue Set() + // add all new definitions to current class or interface val statsWithNewDefs = addNewDefs(currentOwner, body) statsWithNewDefs foreach { diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala index 87c14eb3a1..c171050bbd 100644 --- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala +++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala @@ -723,7 +723,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { } else if (!sClass.isTrait && m.isMethod && m.hasFlag(LAZY)) { forwardToOverload(m) - } else if (m.isValue && !m.isMethod && !m.hasFlag(LAZY)) { // concrete value definition + } else if (m.isValue && !m.isMethod) { // concrete value definition def mkAccessor(field: Symbol, name: Name) = { val newFlags = (SPECIALIZED | m.getterIn(clazz).flags) & ~(LOCAL | CASEACCESSOR | PARAMACCESSOR) // we rely on the super class to initialize param accessors @@ -744,7 +744,14 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers { enterMember(specVal) // create accessors - if (nme.isLocalName(m.name)) { + if (m.isLazy) { + // no getters needed (we'll specialize the compute method and accessor separately), can stay private + // m.setFlag(PRIVATE) -- TODO: figure out how to leave the non-specialized lazy var private + // (the implementation needs it to be visible while duplicating and retypechecking, + // but it really could be private in bytecode) + specVal.setFlag(PRIVATE) + } + else if (nme.isLocalName(m.name)) { val specGetter = mkAccessor(specVal, specVal.getterName) setInfo MethodType(Nil, specVal.info) val origGetter = overrideIn(sClass, m.getterIn(clazz)) info(origGetter) = Forward(specGetter) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index fa7c503213..744b9c8a8e 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -274,10 +274,8 @@ abstract class TailCalls extends Transform { import runDefinitions.{Boolean_or, Boolean_and} tree match { - case ValDef(_, _, _, _) => - if (tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass)) - reporter.error(tree.pos, "lazy vals are not tailcall transformed") - + case dd: DefDef if tree.symbol.isLazy && tree.symbol.hasAnnotation(TailrecClass) => + reporter.error(tree.pos, "lazy vals are not tailcall transformed") super.transform(tree) case dd @ DefDef(_, name, _, vparamss0, _, rhs0) if isEligible(dd) => diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index 6ade45c41c..f6c667353f 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -439,9 +439,9 @@ abstract class UnCurry extends InfoTransform super.transform(treeCopy.DefDef(dd, mods, name, tparams, vparamssNoRhs, tpt, rhs)) } } - case ValDef(_, _, _, rhs) => + case ValDef(mods, _, _, rhs) => if (sym eq NoSymbol) throw new IllegalStateException("Encountered Valdef without symbol: "+ tree + " in "+ unit) - if (!sym.owner.isSourceMethod) + if (!sym.owner.isSourceMethod || mods.isLazy) withNeedLift(needLift = true) { super.transform(tree) } else super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala index 2cd4785fbf..8b62409076 100644 --- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala +++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala @@ -103,13 +103,13 @@ abstract class ConstantFolder { case nme.XOR => Constant(x.longValue ^ y.longValue) case nme.AND => Constant(x.longValue & y.longValue) case nme.LSL if x.tag <= IntTag - => Constant(x.intValue << y.longValue.toInt) // TODO: remove .toInt once starr includes the fix for SI-9516 (2.12.0-M5) + => Constant(x.intValue << y.longValue) case nme.LSL => Constant(x.longValue << y.longValue) case nme.LSR if x.tag <= IntTag - => Constant(x.intValue >>> y.longValue.toInt) // TODO: remove .toInt once starr includes the fix for SI-9516 (2.12.0-M5) + => Constant(x.intValue >>> y.longValue) case nme.LSR => Constant(x.longValue >>> y.longValue) case nme.ASR if x.tag <= IntTag - => Constant(x.intValue >> y.longValue.toInt) // TODO: remove .toInt once starr includes the fix for SI-9516 (2.12.0-M5) + => Constant(x.intValue >> y.longValue) case nme.ASR => Constant(x.longValue >> y.longValue) case nme.EQ => Constant(x.longValue == y.longValue) case nme.NE => Constant(x.longValue != y.longValue) diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 78e72cf771..df014b5161 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -151,8 +151,12 @@ abstract class Duplicators extends Analyzer { ldef.symbol = newsym debuglog("newsym: " + newsym + " info: " + newsym.info) - case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) => - debuglog("ValDef " + name + " sym.info: " + vdef.symbol.info) + // don't retypecheck val members or local lazy vals -- you'll end up with duplicate symbols because + // entering a valdef results in synthesizing getters etc + // TODO: why retype check any valdefs?? I checked and the rhs is specialized just fine this way + // (and there are no args/type params/... to warrant full type checking?) + case vdef @ ValDef(mods, name, _, rhs) if mods.hasFlag(Flags.LAZY) || owner.isClass => + debuglog(s"ValDef $name in $owner sym.info: ${vdef.symbol.info}") invalidSyms(vdef.symbol) = vdef val newowner = owner orElse context.owner val newsym = vdef.symbol.cloneSymbol(newowner) diff --git a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala index e0b64a7600..d11417192d 100644 --- a/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala +++ b/src/compiler/scala/tools/nsc/typechecker/MethodSynthesis.scala @@ -5,6 +5,7 @@ package scala.tools.nsc package typechecker +import scala.reflect.NameTransformer import symtab.Flags._ import scala.reflect.internal.util.StringOps.ojoin import scala.reflect.internal.util.ListOfNil @@ -116,38 +117,53 @@ trait MethodSynthesis { import NamerErrorGen._ - def enterImplicitWrapper(tree: ClassDef): Unit = { - enterSyntheticSym(ImplicitClassWrapper(tree).derivedTree) - } - // trees are later created by addDerivedTrees (common logic is encapsulated in field/standardAccessors/beanAccessors) + import treeInfo.noFieldFor + + // populate synthetics for this unit with trees that will later be added by the typer + // we get here when entering the symbol for the valdef, so its rhs has not yet been type checked def enterGetterSetter(tree: ValDef): Unit = { + val fieldSym = + if (noFieldFor(tree, owner)) NoSymbol + else owner.newValue(tree.name append NameTransformer.LOCAL_SUFFIX_STRING, tree.pos, tree.mods.flags & FieldFlags | PrivateLocal) + val getter = Getter(tree) val getterSym = getter.createSym - val setterSym = if (getter.needsSetter) Setter(tree).createSym else NoSymbol - - // a lazy field is linked to its lazy accessor (TODO: can we do the same for field -> getter -> setter) - val fieldSym = if (Field.noFieldFor(tree)) NoSymbol else Field(tree).createSym(getterSym) // only one symbol can have `tree.pos`, the others must focus their position // normally the field gets the range position, but if there is none, give it to the getter tree.symbol = fieldSym orElse (getterSym setPos tree.pos) + val namer = namerOf(tree.symbol) + + // the valdef gets the accessor symbol for a lazy val (too much going on in its RHS) + // the fields phase creates the field symbol + if (!tree.mods.isLazy) { + // if there's a field symbol, the getter is considered a synthetic that must be added later + // if there's no field symbol, the ValDef tree receives the getter symbol and thus is not a synthetic + if (fieldSym != NoSymbol) { + context.unit.synthetics(getterSym) = getter.derivedTree(getterSym) + getterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = false) + } else getterSym setInfo namer.valTypeCompleter(tree) + + enterInScope(getterSym) + + if (getter.needsSetter) { + val setter = Setter(tree) + val setterSym = setter.createSym + context.unit.synthetics(setterSym) = setter.derivedTree(setterSym) + setterSym setInfo namer.accessorTypeCompleter(tree, tree.tpt.isEmpty, isBean = false, isSetter = true) + enterInScope(setterSym) + } - val namer = if (fieldSym != NoSymbol) namerOf(fieldSym) else namerOf(getterSym) - - // There's no reliable way to detect all kinds of setters from flags or name!!! - // A BeanSetter's name does not end in `_=` -- it does begin with "set", but so could the getter - // for a regular Scala field... TODO: can we add a flag to distinguish getter/setter accessors? - val getterCompleter = namer.accessorTypeCompleter(tree, isSetter = false) - val setterCompleter = namer.accessorTypeCompleter(tree, isSetter = true) - - getterSym setInfo getterCompleter - setterSym andAlso (_ setInfo setterCompleter) - fieldSym andAlso (_ setInfo namer.valTypeCompleter(tree)) - - enterInScope(getterSym) - setterSym andAlso (enterInScope(_)) - fieldSym andAlso (enterInScope(_)) + // TODO: delay emitting the field to the fields phase (except for private[this] vals, which only get a field and no accessors) + if (fieldSym != NoSymbol) { + fieldSym setInfo namer.valTypeCompleter(tree) + enterInScope(fieldSym) + } + } else { + getterSym setInfo namer.valTypeCompleter(tree) + enterInScope(getterSym) + } deriveBeanAccessors(tree, namer) } @@ -188,242 +204,82 @@ trait MethodSynthesis { sym } - val getterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = false) + val getterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = false) enterInScope(deriveBeanAccessor(if (hasBeanProperty) "get" else "is") setInfo getterCompleter) if (tree.mods.isMutable) { - val setterCompleter = namer.beanAccessorTypeCompleter(tree, missingTpt, isSetter = true) + val setterCompleter = namer.accessorTypeCompleter(tree, missingTpt, isBean = true, isSetter = true) enterInScope(deriveBeanAccessor("set") setInfo setterCompleter) } } } - import AnnotationInfo.{mkFilter => annotationFilter} - def addDerivedTrees(typer: Typer, stat: Tree): List[Tree] = stat match { - case vd @ ValDef(mods, name, tpt, rhs) if deriveAccessors(vd) && !vd.symbol.isModuleVar && !vd.symbol.isJava => - stat.symbol.initialize // needed! - - val getter = Getter(vd) - getter.validate() - val accessors = getter :: (if (getter.needsSetter) Setter(vd) :: Nil else Nil) - (Field(vd) :: accessors).map(_.derivedTree).filter(_ ne EmptyTree) - - case cd @ ClassDef(mods, _, _, _) if mods.isImplicit => - val annotations = stat.symbol.initialize.annotations - // TODO: need to shuffle annotations between wrapper and class. - val wrapper = ImplicitClassWrapper(cd) - val meth = wrapper.derivedSym - context.unit.synthetics get meth match { - case Some(mdef) => - context.unit.synthetics -= meth - meth setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) - cd.symbol setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) - List(cd, mdef) - case _ => - // Shouldn't happen, but let's give ourselves a reasonable error when it does - context.error(cd.pos, s"Internal error: Symbol for synthetic factory method not found among ${context.unit.synthetics.keys.mkString(", ")}") - // Soldier on for the sake of the presentation compiler - List(cd) - } - case _ => - stat :: Nil - } - - - sealed trait Derived { - /** The derived symbol. It is assumed that this symbol already exists and has been - * entered in the parent scope when derivedSym is called - */ - def derivedSym: Symbol - - /** The definition tree of the derived symbol. */ - def derivedTree: Tree + def enterImplicitWrapper(classDef: ClassDef): Unit = { + val methDef = factoryMeth(classDef.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC, classDef.name.toTermName, classDef) + val methSym = assignAndEnterSymbol(methDef) + context.unit.synthetics(methSym) = methDef + methSym setInfo implicitFactoryMethodCompleter(methDef, classDef.symbol, completerOf(methDef).asInstanceOf[LockingTypeCompleter]) } - /** A synthetic method which performs the implicit conversion implied by - * the declaration of an implicit class. - */ - case class ImplicitClassWrapper(tree: ClassDef) extends Derived { - def derivedSym = { - val enclClass = tree.symbol.owner.enclClass - // Only methods will do! Don't want to pick up any stray - // companion objects of the same name. - val result = enclClass.info decl derivedName filter (x => x.isMethod && x.isSynthetic) - if (result == NoSymbol || result.isOverloaded) - context.error(tree.pos, s"Internal error: Unable to find the synthetic factory method corresponding to implicit class $derivedName in $enclClass / ${enclClass.info.decls}") - result - } - - def derivedTree = factoryMeth(derivedMods, derivedName, tree) - - def derivedName = tree.name.toTermName - def derivedMods = tree.mods & AccessFlags | METHOD | IMPLICIT | SYNTHETIC - } - - trait DerivedAccessor extends Derived { + trait DerivedAccessor { def tree: ValDef def derivedName: TermName def derivedFlags: Long + def derivedTree(sym: Symbol): Tree def derivedPos = tree.pos.focus def createSym = createMethod(tree, derivedName, derivedPos, derivedFlags) } case class Getter(tree: ValDef) extends DerivedAccessor { - def derivedName = tree.name - - def derivedSym = - if (tree.mods.isLazy) tree.symbol.lazyAccessor - else if (Field.noFieldFor(tree)) tree.symbol - else tree.symbol.getterIn(tree.symbol.enclClass) - + def derivedName = tree.name def derivedFlags = tree.mods.flags & GetterFlags | ACCESSOR.toLong | ( if (needsSetter) 0 else STABLE ) + def needsSetter = tree.mods.isMutable // implies !lazy - def needsSetter = tree.mods.isMutable // implies !lazy - - override def derivedTree = - if (tree.mods.isLazy) deriveLazyAccessor - else newDefDef(derivedSym, if (Field.noFieldFor(tree)) tree.rhs else Select(This(tree.symbol.enclClass), tree.symbol))(tpt = derivedTpt) - - /** Implements lazy value accessors: - * - for lazy values of type Unit and all lazy fields inside traits, - * the rhs is the initializer itself, because we'll just "compute" the result on every access - * ("computing" unit / constant type is free -- the side-effect is still only run once, using the init bitmap) - * - for all other lazy values z the accessor is a block of this form: - * { z = <rhs>; z } where z can be an identifier or a field. - */ - private def deriveLazyAccessor: DefDef = { - val ValDef(_, _, tpt0, rhs0) = tree - val rhs1 = context.unit.transformed.getOrElse(rhs0, rhs0) - val body = - if (tree.symbol.owner.isTrait || Field.noFieldFor(tree)) rhs1 // TODO move tree.symbol.owner.isTrait into noFieldFor - else gen.mkAssignAndReturn(tree.symbol, rhs1) - - derivedSym setPos tree.pos // TODO: can we propagate `tree.pos` to `derivedSym` when the symbol is created? - val ddefRes = DefDef(derivedSym, new ChangeOwnerTraverser(tree.symbol, derivedSym)(body)) - // ValDef will have its position focused whereas DefDef will have original correct rangepos - // ideally positions would be correct at the creation time but lazy vals are really a special case - // here so for the sake of keeping api clean we fix positions manually in LazyValGetter - ddefRes.tpt.setPos(tpt0.pos) - tpt0.setPos(tpt0.pos.focus) - ddefRes - } + override def derivedTree(derivedSym: Symbol) = { + val missingTpt = tree.tpt.isEmpty + val tpt = if (missingTpt) TypeTree() else tree.tpt.duplicate - // TODO: more principled approach -- this is a bit bizarre - private def derivedTpt = { - // For existentials, don't specify a type for the getter, even one derived - // from the symbol! This leads to incompatible existentials for the field and - // the getter. Let the typer do all the work. You might think "why only for - // existentials, why not always," and you would be right, except: a single test - // fails, but it looked like some work to deal with it. Test neg/t0606.scala - // starts compiling (instead of failing like it's supposed to) because the typer - // expects to be able to identify escaping locals in typedDefDef, and fails to - // spot that brand of them. In other words it's an artifact of the implementation. - // - // JZ: ... or we could go back to uniformly using explicit result types in all cases - // if we fix `dropExistential`. More details https://github.com/scala/scala-dev/issues/165 - val getterTp = derivedSym.tpe_*.finalResultType - // Range position errors ensue if we don't duplicate this in some - // circumstances (at least: concrete vals with existential types.) - def inferredTpt = TypeTree() setOriginal (tree.tpt.duplicate setPos tree.tpt.pos.focus) - val tpt = getterTp match { - case _: ExistentialType => inferredTpt - case _ => getterTp.widen match { - case _: ExistentialType => inferredTpt - case _ if tree.mods.isDeferred => TypeTree() setOriginal tree.tpt // keep type tree of original abstract field - case _ => TypeTree(getterTp) - } - } - tpt setPos tree.tpt.pos.focus - } + val rhs = + if (noFieldFor(tree, owner)) tree.rhs // context.unit.transformed.getOrElse(tree.rhs, tree.rhs) + else Select(This(tree.symbol.enclClass), tree.symbol) - def validate() = { - assert(derivedSym != NoSymbol, tree) - if (derivedSym.isOverloaded) - GetterDefinedTwiceError(derivedSym) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = Nil, tpt = tpt) } +// derivedSym setPos tree.pos +// // ValDef will have its position focused whereas DefDef will have original correct rangepos +// // ideally positions would be correct at the creation time but lazy vals are really a special case +// // here so for the sake of keeping api clean we fix positions manually in LazyValGetter +// tpt.setPos(tree.tpt.pos) +// tree.tpt.setPos(tree.tpt.pos.focus) + } case class Setter(tree: ValDef) extends DerivedAccessor { def derivedName = tree.setterName - def derivedSym = tree.symbol.setterIn(tree.symbol.enclClass) def derivedFlags = tree.mods.flags & SetterFlags | ACCESSOR - def derivedTree = - derivedSym.paramss match { - case (setterParam :: Nil) :: _ => - // assert(!derivedSym.isOverloaded, s"Unexpected overloaded setter $derivedSym for ${tree.symbol} in ${tree.symbol.enclClass}") - val rhs = - if (Field.noFieldFor(tree) || derivedSym.isOverloaded) EmptyTree - else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - - DefDef(derivedSym, rhs) - case _ => EmptyTree - } - } - - object Field { - // No field for these vals (either never emitted or eliminated later on): - // - abstract vals have no value we could store (until they become concrete, potentially) - // - lazy vals of type Unit - // - concrete vals in traits don't yield a field here either (their getter's RHS has the initial value) - // Constructors will move the assignment to the constructor, abstracting over the field using the field setter, - // and Fields will add a field to the class that mixes in the trait, implementing the accessors in terms of it - // - [Emitted, later removed during Constructors] a concrete val with a statically known value (ConstantType) - // performs its side effect according to lazy/strict semantics, but doesn't need to store its value - // each access will "evaluate" the RHS (a literal) again - // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. - // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. - // NOTE: do not look at `vd.symbol` when called from `enterGetterSetter` (luckily, that call-site implies `!mods.isLazy`), - // similarly, the `def field` call-site breaks when you add `|| vd.symbol.owner.isTrait` (detected in test suite) - // as the symbol info is in the process of being created then. - // TODO: harmonize tree & symbol creation - // the middle `&& !owner.isTrait` is needed after `isLazy` because non-unit-typed lazy vals in traits still get a field -- see neg/t5455.scala - def noFieldFor(vd: ValDef) = (vd.mods.isDeferred - || (vd.mods.isLazy && !owner.isTrait && isUnitType(vd.symbol.info)) - || (owner.isTrait && !traitFieldFor(vd))) - - // TODO: never emit any fields in traits -- only use getter for lazy/presuper ones as well - private def traitFieldFor(vd: ValDef): Boolean = vd.mods.hasFlag(PRESUPER | LAZY) - } + def derivedTree(derivedSym: Symbol) = { + val setterParam = nme.syntheticParamName(1) - case class Field(tree: ValDef) extends Derived { - private val isLazy = tree.mods.isLazy - - // If the owner is not a class, this is a lazy val from a method, - // with no associated field. It has an accessor with $lzy appended to its name and - // its flags are set differently. The implicit flag is reset because otherwise - // a local implicit "lazy val x" will create an ambiguity with itself - // via "x$lzy" as can be seen in test #3927. - private val localLazyVal = isLazy && !owner.isClass - private val nameSuffix = - if (!localLazyVal) reflect.NameTransformer.LOCAL_SUFFIX_STRING - else reflect.NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - def derivedName = tree.name.append(nameSuffix) - - def createSym(getter: MethodSymbol) = { - val sym = owner.newValue(derivedName, tree.pos, derivedMods.flags) - if (isLazy) sym setLazyAccessor getter - sym - } + // note: tree.tpt may be EmptyTree, which will be a problem when use as the tpt of a parameter + // the completer will patch this up (we can't do this now without completing the field) + val missingTpt = tree.tpt.isEmpty + val tptToPatch = if (missingTpt) TypeTree() else tree.tpt.duplicate - def derivedSym = tree.symbol + val vparams = List(ValDef(Modifiers(PARAM | SYNTHETIC), setterParam, tptToPatch, EmptyTree)) - def derivedMods = - if (!localLazyVal) tree.mods & FieldFlags | PrivateLocal | (if (isLazy) MUTABLE else 0) - else (tree.mods | ARTIFACT | MUTABLE) & ~IMPLICIT + val tpt = TypeTree(UnitTpe) - // TODO: why is this different from the symbol!? - private def derivedModsForTree = tree.mods | PrivateLocal + val rhs = + if (noFieldFor(tree, owner)) EmptyTree + else Assign(Select(This(tree.symbol.enclClass), tree.symbol), Ident(setterParam)) - def derivedTree = - if (Field.noFieldFor(tree)) EmptyTree - else if (isLazy) copyValDef(tree)(mods = derivedModsForTree, name = derivedName, rhs = EmptyTree).setPos(tree.pos.focus) - else copyValDef(tree)(mods = derivedModsForTree, name = derivedName) + newDefDef(derivedSym, rhs)(tparams = Nil, vparamss = List(vparams), tpt = tpt) + } } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 6b11dec967..0cd547c1eb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -129,6 +129,7 @@ trait Namers extends MethodSynthesis { !(vd.name startsWith nme.OUTER) && // outer accessors are added later, in explicitouter !isEnumConstant(vd) // enums can only occur in classes, so only check here + /** Determines whether this field holds an enum constant. * To qualify, the following conditions must be met: * - The field's class has the ENUM flag set @@ -685,6 +686,8 @@ trait Namers extends MethodSynthesis { if (name == nme.copy && sym.isSynthetic) enterCopyMethod(tree) + else if (name == nme.apply && sym.hasAllFlags(SYNTHETIC | CASE)) + sym setInfo caseApplyMethodCompleter(tree, completerOf(tree).asInstanceOf[LockingTypeCompleter]) else sym setInfo completerOf(tree) } @@ -803,86 +806,98 @@ trait Namers extends MethodSynthesis { import AnnotationInfo.{mkFilter => annotationFilter} - def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { sym => - val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter annotationFilter(FieldTargetClass, !tree.mods.isParamAccessor) + def implicitFactoryMethodCompleter(tree: DefDef, classSym: Symbol, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - sym setInfo typeSig(tree, annots) + val annotations = classSym.initialize.annotations - validate(sym) + methSym setAnnotations (annotations filter annotationFilter(MethodTargetClass, defaultRetention = false)) + classSym setAnnotations (annotations filter annotationFilter(ClassTargetClass, defaultRetention = true)) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def accessorTypeCompleter(tree: ValDef, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - // println(s"triaging for ${sym.debugFlagString} $sym from $valAnnots to $annots") - - // typeSig calls valDefSig (because tree: ValDef) - // sym is an accessor, while tree is the field (which may have the same symbol as the getter, or maybe it's the field) - // TODO: can we make this work? typeSig is called on same tree (valdef) to complete info for field and all its accessors - // reuse work done in valTypeCompleter if we already computed the type signature of the val - // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) -// val valSig = -// if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info -// else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + def caseApplyMethodCompleter(tree: DefDef, sigCompleter: LockingTypeCompleter) = mkTypeCompleter(tree) { methSym => + sigCompleter.completeImpl(methSym) - val valSig = typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + // don't propagate e.g. @volatile annot to apply's argument + def retainOnlyParamAnnots(param: Symbol) = + param setAnnotations (param.annotations filter AnnotationInfo.mkFilter(ParamTargetClass, defaultRetention = false)) - val sig = accessorSigFromFieldTp(sym, isSetter, valSig) + methSym.info.paramss.foreach(_.foreach(retainOnlyParamAnnots)) + } + // complete the type of a value definition (may have a method symbol, for those valdefs that never receive a field, + // as specified by Field.noFieldFor) + def valTypeCompleter(tree: ValDef) = mkTypeCompleter(tree) { fieldOrGetterSym => val mods = tree.mods - if (mods.annotations.nonEmpty) { - val annotSigs = annotSig(mods.annotations) - - // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis - // (TODO: can we look at symbols earlier?) - if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) - && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) - BeanPropertyAnnotationLimitationError(tree) + val isGetter = fieldOrGetterSym.isMethod + val annots = + if (mods.annotations.isEmpty) Nil + else { + val annotSigs = annotSig(mods.annotations) + if (isGetter) filterAccessorAnnots(annotSigs, tree) // if this is really a getter, retain annots targeting either field/getter + else annotSigs filter annotationFilter(FieldTargetClass, !mods.isParamAccessor) + } - sym setAnnotations (annotSigs filter filterAccessorAnnotations(isSetter)) - } + // must use typeSig, not memberSig (TODO: when do we need to switch namers?) + val sig = typeSig(tree, annots) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + fieldOrGetterSym setInfo (if (isGetter) NullaryMethodType(sig) else sig) - validate(sym) + validate(fieldOrGetterSym) } - /* Explicit isSetter required for bean setters (beanSetterSym.isSetter is false) */ - def beanAccessorTypeCompleter(tree: ValDef, missingTpt: Boolean, isSetter: Boolean) = mkTypeCompleter(tree) { sym => - context.unit.synthetics get sym match { + // knowing `isBean`, we could derive `isSetter` from `valDef.name` + def accessorTypeCompleter(valDef: ValDef, missingTpt: Boolean, isBean: Boolean, isSetter: Boolean) = mkTypeCompleter(valDef) { accessorSym => + context.unit.synthetics get accessorSym match { case Some(ddef: DefDef) => - // sym is an accessor, while tree is the field (for traits it's actually the getter, and we're completing the setter) + // `accessorSym` is the accessor for which we're completing the info (tree == ddef), + // while `valDef` is the field definition that spawned the accessor + // NOTE: `valTypeCompleter` handles abstract vals, trait vals and lazy vals, where the ValDef carries the getter's symbol + // reuse work done in valTypeCompleter if we already computed the type signature of the val // (assuming the field and accessor symbols are distinct -- i.e., we're not in a trait) val valSig = - if ((sym ne tree.symbol) && tree.symbol.isInitialized) tree.symbol.info - else typeSig(tree, Nil) // don't set annotations for the valdef -- we just want to compute the type sig + if ((accessorSym ne valDef.symbol) && valDef.symbol.isInitialized) valDef.symbol.info + else typeSig(valDef, Nil) // don't set annotations for the valdef -- we just want to compute the type sig (TODO: dig deeper and see if we can use memberSig) // patch up the accessor's tree if the valdef's tpt was not known back when the tree was synthesized - if (missingTpt) { // can't look at tree.tpt here because it may have been completed by now + // can't look at `valDef.tpt` here because it may have been completed by now (this is why we pass in `missingTpt`) + // HACK: a param accessor `ddef.tpt.tpe` somehow gets out of whack with `accessorSym.info`, so always patch it back... + // (the tpt is typed in the wrong namer, using the class as owner instead of the outer context, which is where param accessors should be typed) + if (missingTpt || accessorSym.isParamAccessor) { if (!isSetter) ddef.tpt setType valSig else if (ddef.vparamss.nonEmpty && ddef.vparamss.head.nonEmpty) ddef.vparamss.head.head.tpt setType valSig - else throw new TypeError(tree.pos, s"Internal error: could not complete parameter/return type for $ddef from $sym") + else throw new TypeError(valDef.pos, s"Internal error: could not complete parameter/return type for $ddef from $accessorSym") } + val mods = valDef.mods val annots = - if (tree.mods.annotations.isEmpty) Nil - else annotSig(tree.mods.annotations) filter filterBeanAccessorAnnotations(isSetter) + if (mods.annotations.isEmpty) Nil + else filterAccessorAnnots(annotSig(mods.annotations), valDef, isSetter, isBean) + + // for a setter, call memberSig to attribute the parameter (for a bean, we always use the regular method sig completer since they receive method types) + // for a regular getter, make sure it gets a NullaryMethodType (also, no need to recompute it: we already have the valSig) + val sig = + if (isSetter || isBean) typeSig(ddef, annots) + else { + if (annots.nonEmpty) annotate(accessorSym, annots) + + NullaryMethodType(valSig) + } - val sig = typeSig(ddef, annots) + accessorSym setInfo pluginsTypeSigAccessor(sig, typer, valDef, accessorSym) - sym setInfo pluginsTypeSigAccessor(sig, typer, tree, sym) + if (!isBean && accessorSym.isOverloaded) + if (isSetter) ddef.rhs.setType(ErrorType) + else GetterDefinedTwiceError(accessorSym) - validate(sym) + validate(accessorSym) case _ => - throw new TypeError(tree.pos, s"Internal error: no synthetic tree found for bean accessor $sym") + throw new TypeError(valDef.pos, s"Internal error: no synthetic tree found for bean accessor $accessorSym") } - } - // see scala.annotation.meta's package class for more info // Annotations on ValDefs can be targeted towards the following: field, getter, setter, beanGetter, beanSetter, param. // The defaults are: @@ -893,24 +908,33 @@ trait Namers extends MethodSynthesis { // // TODO: these defaults can be surprising for annotations not meant for accessors/fields -- should we revisit? // (In order to have `@foo val X` result in the X getter being annotated with `@foo`, foo needs to be meta-annotated with @getter) - private def filterAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) + private def filterAccessorAnnots(annotSigs: List[global.AnnotationInfo], tree: global.ValDef, isSetter: Boolean = false, isBean: Boolean = false): List[AnnotationInfo] = { + val mods = tree.mods + if (!isBean) { + // neg/t3403: check that we didn't get a sneaky type alias/renamed import that we couldn't detect because we only look at names during synthesis + // (TODO: can we look at symbols earlier?) + if (!((mods hasAnnotationNamed tpnme.BeanPropertyAnnot) || (mods hasAnnotationNamed tpnme.BooleanBeanPropertyAnnot)) + && annotSigs.exists(ann => (ann.matches(BeanPropertyAttr)) || ann.matches(BooleanBeanPropertyAttr))) + BeanPropertyAnnotationLimitationError(tree) + } + + def filterAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) SetterTargetClass else GetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(GetterTargetClass, defaultRetention = true)(ann)) - private def filterBeanAccessorAnnotations(isSetter: Boolean): AnnotationInfo => Boolean = - if (isSetter || !owner.isTrait) - annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) - else (ann => - annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || - annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + def filterBeanAccessorAnnotations: AnnotationInfo => Boolean = + if (isSetter || !owner.isTrait) + annotationFilter(if (isSetter) BeanSetterTargetClass else BeanGetterTargetClass, defaultRetention = false) + else (ann => + annotationFilter(FieldTargetClass, defaultRetention = true)(ann) || + annotationFilter(BeanGetterTargetClass, defaultRetention = true)(ann)) + annotSigs filter (if (isBean) filterBeanAccessorAnnotations else filterAccessorAnnotations) + } - private def accessorSigFromFieldTp(sym: Symbol, isSetter: Boolean, tp: Type): Type = - if (isSetter) MethodType(List(sym.newSyntheticValueParam(tp)), UnitTpe) - else NullaryMethodType(tp) def selfTypeCompleter(tree: Tree) = mkTypeCompleter(tree) { sym => val selftpe = typer.typedType(tree).tpe @@ -959,7 +983,7 @@ trait Namers extends MethodSynthesis { ) dropIllegalStarTypes( if (shouldWiden) tpe.widen - else if (sym.isFinal) tpe // "final val" allowed to retain constant type + else if (sym.isFinal && !sym.isLazy) tpe // "final val" allowed to retain constant type else tpe.deconst ) } diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 8449260fe6..106b076eef 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -95,6 +95,15 @@ abstract class RefChecks extends Transform { ) } + private val separatelyCompiledScalaSuperclass = perRunCaches.newAnyRefMap[Symbol, Unit]() + final def isSeparatelyCompiledScalaSuperclass(sym: Symbol) = if (globalPhase.refChecked){ + separatelyCompiledScalaSuperclass.contains(sym) + } else { + // conservative approximation in case someone in pre-refchecks phase asks for `exitingFields(someClass.info)` + // and we haven't run the refchecks tree transform which populates `separatelyCompiledScalaSuperclass` + false + } + class RefCheckTransformer(unit: CompilationUnit) extends Transformer { var localTyper: analyzer.Typer = typer @@ -450,9 +459,9 @@ abstract class RefChecks extends Transform { } else if (other.isStable && !member.isStable) { // (1.4) overrideError("needs to be a stable, immutable value") } else if (member.isValue && member.isLazy && - other.isValue && !other.isSourceMethod && !other.isDeferred && !other.isLazy) { + other.isValue && other.hasFlag(STABLE) && !(other.isDeferred || other.isLazy)) { overrideError("cannot override a concrete non-lazy value") - } else if (other.isValue && other.isLazy && !other.isSourceMethod && !other.isDeferred && // !(other.hasFlag(MODULE) && other.hasFlag(PACKAGE | JAVA)) && other.hasFlag(LAZY) && (!other.isMethod || other.hasFlag(STABLE)) && !other.hasFlag(DEFERRED) + } else if (other.isValue && other.isLazy && member.isValue && !member.isLazy) { overrideError("must be declared lazy to override a concrete lazy value") } else if (other.isDeferred && member.isTermMacro && member.extendedOverriddenSymbols.forall(_.isDeferred)) { // (1.9) @@ -609,7 +618,7 @@ abstract class RefChecks extends Transform { val (missing, rest) = memberList partition (m => m.isDeferred && !ignoreDeferred(m)) // Group missing members by the name of the underlying symbol, // to consolidate getters and setters. - val grouped = missing groupBy (sym => analyzer.underlyingSymbol(sym).name) + val grouped = missing groupBy (_.name.getterName) val missingMethods = grouped.toList flatMap { case (name, syms) => if (syms exists (_.isSetter)) syms filterNot (_.isGetter) @@ -651,15 +660,16 @@ abstract class RefChecks extends Transform { // Give a specific error message for abstract vars based on why it fails: // It could be unimplemented, have only one accessor, or be uninitialized. - if (underlying.isVariable) { - val isMultiple = grouped.getOrElse(underlying.name, Nil).size > 1 + val groupedAccessors = grouped.getOrElse(member.name.getterName, Nil) + val isMultiple = groupedAccessors.size > 1 + if (groupedAccessors.exists(_.isSetter) || (member.isGetter && !isMultiple && member.setterIn(member.owner).exists)) { // If both getter and setter are missing, squelch the setter error. if (member.isSetter && isMultiple) () else undefined( if (member.isSetter) "\n(Note that an abstract var requires a setter in addition to the getter)" else if (member.isGetter && !isMultiple) "\n(Note that an abstract var requires a getter in addition to the setter)" - else analyzer.abstractVarMessage(member) + else "\n(Note that variables need to be initialized to be defined)" ) } else if (underlying.isMethod) { @@ -853,6 +863,8 @@ abstract class RefChecks extends Transform { // println("validate base type "+tp) val baseClass = tp.typeSymbol if (baseClass.isClass) { + if (!baseClass.isTrait && !baseClass.isJavaDefined && !currentRun.compiles(baseClass) && !separatelyCompiledScalaSuperclass.contains(baseClass)) + separatelyCompiledScalaSuperclass.update(baseClass, ()) val index = clazz.info.baseTypeIndex(baseClass) if (index >= 0) { if (seenTypes(index) forall (tp1 => !(tp1 <:< tp))) @@ -919,17 +931,11 @@ abstract class RefChecks extends Transform { var index = -1 for (stat <- stats) { index = index + 1 - def enterSym(sym: Symbol) = if (sym.isLocalToBlock) { - currentLevel.scope.enter(sym) - symIndex(sym) = index - } stat match { - case DefDef(_, _, _, _, _, _) if stat.symbol.isLazy => - enterSym(stat.symbol) - case ClassDef(_, _, _, _) | DefDef(_, _, _, _, _, _) | ModuleDef(_, _, _) | ValDef(_, _, _, _) => - //assert(stat.symbol != NoSymbol, stat);//debug - enterSym(stat.symbol.lazyAccessorOrSelf) + case _ : MemberDef if stat.symbol.isLocalToBlock => + currentLevel.scope.enter(stat.symbol) + symIndex(stat.symbol) = index case _ => } } @@ -1180,10 +1186,10 @@ abstract class RefChecks extends Transform { val tree1 = transform(tree) // important to do before forward reference check if (tree1.symbol.isLazy) tree1 :: Nil else { - val lazySym = tree.symbol.lazyAccessorOrSelf - if (lazySym.isLocalToBlock && index <= currentLevel.maxindex) { + val sym = tree.symbol + if (sym.isLocalToBlock && index <= currentLevel.maxindex) { debuglog("refsym = " + currentLevel.refsym) - reporter.error(currentLevel.refpos, "forward reference extends over definition of " + lazySym) + reporter.error(currentLevel.refpos, "forward reference extends over definition of " + sym) } tree1 :: Nil } @@ -1451,9 +1457,9 @@ abstract class RefChecks extends Transform { ) } - sym.isSourceMethod && + sym.name == nme.apply && + !(sym hasFlag STABLE) && // ??? sym.isCase && - sym.name == nme.apply && isClassTypeAccessible(tree) && !tree.tpe.finalResultType.typeSymbol.primaryConstructor.isLessAccessibleThan(tree.symbol) } diff --git a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala index 49d892e04f..8b1b2f35c5 100644 --- a/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala +++ b/src/compiler/scala/tools/nsc/typechecker/SuperAccessors.scala @@ -146,7 +146,28 @@ abstract class SuperAccessors extends transform.Transform with transform.TypingT val intermediateClasses = clazz.info.baseClasses.tail.takeWhile(_ != sym.owner) intermediateClasses.map(sym.overridingSymbol).find(s => s.isDeferred && !s.isAbstractOverride && !s.owner.isTrait).foreach { absSym => - reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from ${clazz} because ${absSym.owner} redeclares it as abstract") + reporter.error(sel.pos, s"${sym.fullLocationString} cannot be directly accessed from $clazz because ${absSym.owner} redeclares it as abstract") + } + } else { + // SD-143: a call super[T].m that resolves to A.m cannot be translated to correct bytecode if + // - A is a class (not a trait / interface), but not the direct superclass. Invokespecial + // would select an overriding method in the direct superclass, rather than A.m. + // We allow this if there are statically no intervening overrides. + // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial + // - A is a java-defined interface and not listed as direct parent of the class. In this + // case, `invokespecial A.m` would be invalid. + def hasClassOverride(member: Symbol, subclass: Symbol): Boolean = { + if (subclass == ObjectClass || subclass == member.owner) false + else if (member.overridingSymbol(subclass) != NoSymbol) true + else hasClassOverride(member, subclass.superClass) + } + val owner = sym.owner + if (mix != tpnme.EMPTY && !owner.isTrait && owner != clazz.superClass && hasClassOverride(sym, clazz.superClass)) { + reporter.error(sel.pos, + s"cannot emit super call: the selected $sym is declared in $owner, which is not the direct superclass of $clazz.\n" + + s"An unqualified super call (super.${sym.name}) would be allowed.") + } else if (owner.isInterface && owner.isJavaDefined && !clazz.parentSymbols.contains(owner)) { + reporter.error(sel.pos, s"unable to emit super call unless interface ${owner.name} (which declares $sym) is directly extended by $clazz.") } } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index bee327c760..b66dbf21c0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -97,7 +97,7 @@ trait TypeDiagnostics { /** An explanatory note to be added to error messages * when there's a problem with abstract var defs */ def abstractVarMessage(sym: Symbol): String = - if (underlyingSymbol(sym).isVariable) + if (sym.isSetter || sym.isGetter && sym.setterIn(sym.owner).exists) "\n(Note that variables need to be initialized to be defined)" else "" @@ -140,7 +140,7 @@ trait TypeDiagnostics { * TODO: is it wise to create new symbols simply to generate error message? is this safe in interactive/resident mode? */ def underlyingSymbol(member: Symbol): Symbol = - if (!member.hasAccessorFlag || member.owner.isTrait) member + if (!member.hasAccessorFlag || member.accessed == NoSymbol) member else if (!member.isDeferred) member.accessed else { val getter = if (member.isSetter) member.getterIn(member.owner) else member diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a95ecd360c..7d48c548a1 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -128,6 +128,15 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def canTranslateEmptyListToNil = true def missingSelectErrorTree(tree: Tree, qual: Tree, name: Name): Tree = tree + // used to exempt synthetic accessors (i.e. those that are synthesized by the compiler to access a field) + // from skolemization because there's a weird bug that causes spurious type mismatches + // (it seems to have something to do with existential abstraction over values + // https://github.com/scala/scala-dev/issues/165 + // when we're past typer, lazy accessors are synthetic, but before they are user-defined + // to make this hack less hacky, we could rework our flag assignment to allow for + // requiring both the ACCESSOR and the SYNTHETIC bits to trigger the exemption + private def isSyntheticAccessor(sym: Symbol) = sym.isAccessor && (!sym.isLazy || isPastTyper) + // when type checking during erasure, generate erased types in spots that aren't transformed by erasure // (it erases in TypeTrees, but not in, e.g., the type a Function node) def phasedAppliedType(sym: Symbol, args: List[Type]) = { @@ -1051,8 +1060,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper @inline def tpdPos(transformed: Tree) = typedPos(tree.pos, mode, pt)(transformed) @inline def tpd(transformed: Tree) = typed(transformed, mode, pt) - @inline def warnValueDiscard(): Unit = - if (!isPastTyper && settings.warnValueDiscard) context.warning(tree.pos, "discarded non-Unit value") + @inline def warnValueDiscard(): Unit = if (!isPastTyper && settings.warnValueDiscard) { + def isThisTypeResult = (tree, tree.tpe) match { + case (Apply(Select(receiver, _), _), SingleType(_, sym)) => sym == receiver.symbol + case _ => false + } + if (!isThisTypeResult) context.warning(tree.pos, "discarded non-Unit value") + } @inline def warnNumericWiden(): Unit = if (!isPastTyper && settings.warnNumericWiden) context.warning(tree.pos, "implicit numeric widening") @@ -1159,7 +1173,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper adapt(tree setType restpe, mode, pt, original) case TypeRef(_, ByNameParamClass, arg :: Nil) if mode.inExprMode => // (2) adapt(tree setType arg, mode, pt, original) - case tp if mode.typingExprNotLhs && isExistentialType(tp) => + case tp if mode.typingExprNotLhs && isExistentialType(tp) && !isSyntheticAccessor(context.owner) => adapt(tree setType tp.dealias.skolemizeExistential(context.owner, tree), mode, pt, original) case PolyType(tparams, restpe) if mode.inNone(TAPPmode | PATTERNmode) && !context.inTypeConstructorAllowed => // (3) // assert((mode & HKmode) == 0) //@M a PolyType in HKmode represents an anonymous type function, @@ -1373,13 +1387,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper notAllowed(s"redefinition of $name method. See SIP-15, criterion 4.") else if (stat.symbol != null && stat.symbol.isParamAccessor) notAllowed("additional parameter") - // concrete accessor (getter) in trait corresponds to a field definition (neg/anytrait.scala) - // TODO: only reject accessors that actually give rise to field (e.g., a constant-type val is fine) - else if (!isValueClass && stat.symbol.isAccessor && !stat.symbol.isDeferred) - notAllowed("field definition") checkEphemeralDeep.traverse(rhs) - // for value class or "exotic" vals in traits - // (traits don't receive ValDefs for regular vals until fields phase -- well, except for early initialized/lazy vals) case _: ValDef => notAllowed("field definition") case _: ModuleDef => @@ -1956,11 +1964,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper if (!phase.erasedTypes && !clazz.info.resultType.isError) // @S: prevent crash for duplicated type members checkFinitary(clazz.info.resultType.asInstanceOf[ClassInfoType]) - val body2 = { - val body2 = - if (isPastTyper || reporter.hasErrors) body1 - else body1 flatMap rewrappingWrapperTrees(namer.addDerivedTrees(Typer.this, _)) - val primaryCtor = treeInfo.firstConstructor(body2) + val bodyWithPrimaryCtor = { + val primaryCtor = treeInfo.firstConstructor(body1) val primaryCtor1 = primaryCtor match { case DefDef(_, _, _, _, _, Block(earlyVals :+ global.pendingSuperCall, unit)) => val argss = superArgs(parents1.head) getOrElse Nil @@ -1969,10 +1974,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper deriveDefDef(primaryCtor)(block => Block(earlyVals :+ superCall, unit) setPos pos) setPos pos case _ => primaryCtor } - body2 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } + body1 mapConserve { case `primaryCtor` => primaryCtor1; case stat => stat } } - val body3 = typedStats(body2, templ.symbol) + val body3 = typedStats(bodyWithPrimaryCtor, templ.symbol) if (clazz.info.firstParent.typeSymbol == AnyValClass) validateDerivedValueClass(clazz, body3) @@ -2040,7 +2045,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper override def matches(sym: Symbol, sym1: Symbol) = if (sym.isSkolem) matches(sym.deSkolemize, sym1) else if (sym1.isSkolem) matches(sym, sym1.deSkolemize) - else super[SubstTypeMap].matches(sym, sym1) + else super.matches(sym, sym1) } // allow defaults on by-name parameters if (sym hasFlag BYNAMEPARAM) @@ -2436,13 +2441,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case _ => } } - val stats1 = if (isPastTyper) block.stats else - block.stats.flatMap { - case vd@ValDef(_, _, _, _) if vd.symbol.isLazy => - namer.addDerivedTrees(Typer.this, vd) - case stat => stat::Nil - } - val stats2 = typedStats(stats1, context.owner, warnPure = false) + val statsTyped = typedStats(block.stats, context.owner, warnPure = false) val expr1 = typed(block.expr, mode &~ (FUNmode | QUALmode), pt) // sanity check block for unintended expr placement @@ -2456,18 +2455,18 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def checkPure(t: Tree, supple: Boolean): Unit = if (treeInfo.isPureExprForWarningPurposes(t)) { val msg = "a pure expression does nothing in statement position" - val parens = if (stats2.length + count > 1) "multiline expressions might require enclosing parentheses" else "" + val parens = if (statsTyped.length + count > 1) "multiline expressions might require enclosing parentheses" else "" val discard = if (adapted) "; a value can be silently discarded when Unit is expected" else "" val text = if (supple) s"${parens}${discard}" else if (!parens.isEmpty) s"${msg}; ${parens}" else msg context.warning(t.pos, text) } - stats2.foreach(checkPure(_, supple = false)) + statsTyped.foreach(checkPure(_, supple = false)) if (result0.nonEmpty) checkPure(result0, supple = true) } - treeCopy.Block(block, stats2, expr1) + treeCopy.Block(block, statsTyped, expr1) .setType(if (treeInfo.isExprSafeToInline(block)) expr1.tpe else expr1.tpe.deconst) } finally { // enable escaping privates checking from the outside and recycle @@ -3171,6 +3170,10 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper case (ClassDef(cmods, cname, _, _), DefDef(dmods, dname, _, _, _, _)) => cmods.isImplicit && dmods.isImplicit && cname.toTermName == dname + // ValDef and Accessor + case (ValDef(_, cname, _, _), DefDef(_, dname, _, _, _, _)) => + cname.getterName == dname.getterName + case _ => false } @@ -4455,8 +4458,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper def narrowRhs(tp: Type) = { val sym = context.tree.symbol context.tree match { case ValDef(mods, _, _, Apply(Select(`tree`, _), _)) if !mods.isMutable && sym != null && sym != NoSymbol => - val sym1 = if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) - else sym.lazyAccessorOrSelf + val sym1 = + if (sym.owner.isClass && sym.getterIn(sym.owner) != NoSymbol) sym.getterIn(sym.owner) + else sym val pre = if (sym1.owner.isClass) sym1.owner.thisType else NoPrefix intersectionType(List(tp, singleType(pre, sym1))) case _ => tp diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index 22fb0728e6..f2e9b260b0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -128,6 +128,7 @@ trait Unapplies extends ast.TreeDSL { */ def factoryMeth(mods: Modifiers, name: TermName, cdef: ClassDef): DefDef = { val tparams = constrTparamsInvariant(cdef) + val cparamss = constrParamss(cdef) def classtpe = classType(cdef, tparams) atPos(cdef.pos.focus)( diff --git a/src/compiler/scala/tools/nsc/util/StackTracing.scala b/src/compiler/scala/tools/nsc/util/StackTracing.scala index 0765bb923f..c6749a13f3 100644 --- a/src/compiler/scala/tools/nsc/util/StackTracing.scala +++ b/src/compiler/scala/tools/nsc/util/StackTracing.scala @@ -8,7 +8,7 @@ private[util] trait StackTracing extends Any { /** Format a stack trace, returning the prefix consisting of frames that satisfy * a given predicate. - * The format is similar to the typical case described in the JavaDoc + * The format is similar to the typical case described in the Javadoc * for [[java.lang.Throwable#printStackTrace]]. * If a stack trace is truncated, it will be followed by a line of the form * `... 3 elided`, by analogy to the lines `... 3 more` which indicate @@ -19,25 +19,18 @@ private[util] trait StackTracing extends Any { def stackTracePrefixString(e: Throwable)(p: StackTraceElement => Boolean): String = { import collection.mutable.{ ArrayBuffer, ListBuffer } import compat.Platform.EOL - import scala.util.Properties.isJavaAtLeast - - val sb = ListBuffer.empty[String] type TraceRelation = String val Self = new TraceRelation("") val CausedBy = new TraceRelation("Caused by: ") val Suppressed = new TraceRelation("Suppressed: ") - val suppressable = isJavaAtLeast("1.7") - - def clazz(e: Throwable) = e.getClass.getName + def clazz(e: Throwable): String = e.getClass.getName def because(e: Throwable): String = e.getCause match { case null => null ; case c => header(c) } def msg(e: Throwable): String = e.getMessage match { case null => because(e) ; case s => s } def txt(e: Throwable): String = msg(e) match { case null => "" ; case s => s": $s" } def header(e: Throwable): String = s"${clazz(e)}${txt(e)}" - val indent = "\u0020\u0020" - val seen = new ArrayBuffer[Throwable](16) def unseen(t: Throwable) = { def inSeen = seen exists (_ eq t) @@ -46,28 +39,25 @@ private[util] trait StackTracing extends Any { interesting } + val sb = ListBuffer.empty[String] + + // format the stack trace, skipping the shared trace def print(e: Throwable, r: TraceRelation, share: Array[StackTraceElement], indents: Int): Unit = if (unseen(e)) { val trace = e.getStackTrace - val frames = ( - if (share.nonEmpty) { - val spare = share.reverseIterator - val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _) - trimmed.reverse - } else trace - ) - val prefix = frames takeWhile p - val margin = indent * indents - val indented = margin + indent + val frames = if (share.isEmpty) trace else { + val spare = share.reverseIterator + val trimmed = trace.reverse dropWhile (spare.hasNext && spare.next == _) + trimmed.reverse + } + val prefix = frames takeWhile p + val margin = " " * indents + val indent = margin + " " sb append s"${margin}${r}${header(e)}" - prefix foreach (f => sb append s"${indented}at $f") - if (frames.size < trace.size) sb append s"$indented... ${trace.size - frames.size} more" - if (r == Self && prefix.size < frames.size) sb append s"$indented... ${frames.size - prefix.size} elided" + prefix foreach (f => sb append s"${margin} at $f") + if (frames.size < trace.size) sb append s"${margin} ... ${trace.size - frames.size} more" + if (r == Self && prefix.size < frames.size) sb append s"${margin} ... ${frames.size - prefix.size} elided" print(e.getCause, CausedBy, trace, indents) - if (suppressable) { - import scala.language.reflectiveCalls - type Suppressing = { def getSuppressed(): Array[Throwable] } - for (s <- e.asInstanceOf[Suppressing].getSuppressed) print(s, Suppressed, frames, indents + 1) - } + e.getSuppressed foreach (t => print(t, Suppressed, frames, indents + 1)) } print(e, Self, share = Array.empty, indents = 0) diff --git a/src/eclipse/README.md b/src/eclipse/README.md index f67fa26e5e..c7a4827341 100644 --- a/src/eclipse/README.md +++ b/src/eclipse/README.md @@ -57,10 +57,10 @@ If it doesn’t compile ===================== The likely reason is that the build path of the imported projects isn’t correct. This can happen for instance -when the [version.properties](https://github.com/scala/scala/blob/master/versions.properties) file is updated, +when the [versions.properties](https://github.com/scala/scala/blob/master/versions.properties) file is updated, and Eclipse .classpath of the different projects isn’t updated accordingly. The fix is simple, manually inspect the build path of each project and make sure the version of the declared dependencies is in sync with the version -declared in the `version.properties` file. If it isn’t, update it manually and, when done, don’t forget to share +declared in the `versions.properties` file. If it isn’t, update it manually and, when done, don’t forget to share your changes via a pull request. (We are aware this is cumbersome. If you feel like scripting the process, pull requests are of course welcome.) diff --git a/src/ensime/.ensime.SAMPLE b/src/ensime/.ensime.SAMPLE deleted file mode 100644 index 10801816b7..0000000000 --- a/src/ensime/.ensime.SAMPLE +++ /dev/null @@ -1,17 +0,0 @@ -( - :disable-source-load-on-startup t - :disable-scala-jars-on-classpath t - :root-dir "c:/Projects/Kepler" - :sources ( - "c:/Projects/Kepler/src/library" - "c:/Projects/Kepler/src/reflect" - "c:/Projects/Kepler/src/compiler" - ) - :compile-deps ( - "c:/Projects/Kepler/build/asm/classes" - "c:/Projects/Kepler/build/locker/classes/library" - "c:/Projects/Kepler/build/locker/classes/reflect" - "c:/Projects/Kepler/build/locker/classes/compiler" - ) - :target "c:/Projects/Kepler/build/classes" -)
\ No newline at end of file diff --git a/src/ensime/README.md b/src/ensime/README.md deleted file mode 100644 index 302d47b8a7..0000000000 --- a/src/ensime/README.md +++ /dev/null @@ -1,11 +0,0 @@ -Ensime project files -===================== - -Rename .ensime.SAMPLE to .ensime and replace sample paths with real paths to your sources and build results. -After that you're good to go with one of the ENSIME-enabled text editors. - -Editors that know how to talk to ENSIME servers: -1) Emacs via https://github.com/aemoncannon/ensime -2) jEdit via https://github.com/djspiewak/ensime-sidekick -3) TextMate via https://github.com/mads379/ensime.tmbundle -4) Sublime Text 2 via https://github.com/sublimescala/sublime-ensime diff --git a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala index cb12cebc49..462f4432cd 100644 --- a/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/interactive/scala/tools/nsc/interactive/CompilerControl.scala @@ -101,11 +101,11 @@ trait CompilerControl { self: Global => * the given sources at the head of the list of to-be-compiled sources. */ def askReload(sources: List[SourceFile], response: Response[Unit]) = { - val superseeded = scheduler.dequeueAll { + val superseded = scheduler.dequeueAll { case ri: ReloadItem if ri.sources == sources => Some(ri) case _ => None } - superseeded.foreach(_.response.set(())) + superseded.foreach(_.response.set(())) postWorkItem(new ReloadItem(sources, response)) } diff --git a/src/interactive/scala/tools/nsc/interactive/Global.scala b/src/interactive/scala/tools/nsc/interactive/Global.scala index 64535a749f..715ba0d4f3 100644 --- a/src/interactive/scala/tools/nsc/interactive/Global.scala +++ b/src/interactive/scala/tools/nsc/interactive/Global.scala @@ -72,8 +72,6 @@ trait InteractiveAnalyzer extends Analyzer { override def enterExistingSym(sym: Symbol, tree: Tree): Context = { if (sym != null && sym.owner.isTerm) { enterIfNotThere(sym) - if (sym.isLazy) - sym.lazyAccessor andAlso enterIfNotThere for (defAtt <- sym.attachments.get[DefaultsOfLocalMethodAttachment]) defAtt.defaultGetters foreach enterIfNotThere diff --git a/src/library/scala/Array.scala b/src/library/scala/Array.scala index 7f3200b90a..6d829a9e5d 100644 --- a/src/library/scala/Array.scala +++ b/src/library/scala/Array.scala @@ -496,16 +496,6 @@ object Array extends FallbackArrayBuilding { * @hideImplicitConversion scala.Predef.refArrayOps * @hideImplicitConversion scala.Predef.shortArrayOps * @hideImplicitConversion scala.Predef.unitArrayOps - * @hideImplicitConversion scala.Predef._booleanArrayOps - * @hideImplicitConversion scala.Predef._byteArrayOps - * @hideImplicitConversion scala.Predef._charArrayOps - * @hideImplicitConversion scala.Predef._doubleArrayOps - * @hideImplicitConversion scala.Predef._floatArrayOps - * @hideImplicitConversion scala.Predef._intArrayOps - * @hideImplicitConversion scala.Predef._longArrayOps - * @hideImplicitConversion scala.Predef._refArrayOps - * @hideImplicitConversion scala.Predef._shortArrayOps - * @hideImplicitConversion scala.Predef._unitArrayOps * @hideImplicitConversion scala.LowPriorityImplicits.wrapRefArray * @hideImplicitConversion scala.LowPriorityImplicits.wrapIntArray * @hideImplicitConversion scala.LowPriorityImplicits.wrapDoubleArray diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 5e82062b44..b79fa9d732 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -428,28 +428,16 @@ object Predef extends LowPriorityImplicits with DeprecatedPredef { case null => null }).asInstanceOf[ArrayOps[T]] - // TODO: when we remove, these should we drop the underscores from the new generation below? (For source compatibility in case someone was shadowing these.) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def booleanArrayOps(xs: Array[Boolean]): ArrayOps[Boolean] = new ArrayOps.ofBoolean(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def byteArrayOps(xs: Array[Byte]): ArrayOps[Byte] = new ArrayOps.ofByte(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def charArrayOps(xs: Array[Char]): ArrayOps[Char] = new ArrayOps.ofChar(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def doubleArrayOps(xs: Array[Double]): ArrayOps[Double] = new ArrayOps.ofDouble(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def floatArrayOps(xs: Array[Float]): ArrayOps[Float] = new ArrayOps.ofFloat(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def intArrayOps(xs: Array[Int]): ArrayOps[Int] = new ArrayOps.ofInt(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def longArrayOps(xs: Array[Long]): ArrayOps[Long] = new ArrayOps.ofLong(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T] = new ArrayOps.ofRef[T](xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def shortArrayOps(xs: Array[Short]): ArrayOps[Short] = new ArrayOps.ofShort(xs) - @deprecated("For binary compatibility only. Release new partest and remove in M3.", "2.12.0-M2") def unitArrayOps(xs: Array[Unit]): ArrayOps[Unit] = new ArrayOps.ofUnit(xs) - - implicit def _booleanArrayOps(xs: Array[Boolean]): ArrayOps.ofBoolean = new ArrayOps.ofBoolean(xs) - implicit def _byteArrayOps(xs: Array[Byte]): ArrayOps.ofByte = new ArrayOps.ofByte(xs) - implicit def _charArrayOps(xs: Array[Char]): ArrayOps.ofChar = new ArrayOps.ofChar(xs) - implicit def _doubleArrayOps(xs: Array[Double]): ArrayOps.ofDouble = new ArrayOps.ofDouble(xs) - implicit def _floatArrayOps(xs: Array[Float]): ArrayOps.ofFloat = new ArrayOps.ofFloat(xs) - implicit def _intArrayOps(xs: Array[Int]): ArrayOps.ofInt = new ArrayOps.ofInt(xs) - implicit def _longArrayOps(xs: Array[Long]): ArrayOps.ofLong = new ArrayOps.ofLong(xs) - implicit def _refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps.ofRef[T] = new ArrayOps.ofRef[T](xs) - implicit def _shortArrayOps(xs: Array[Short]): ArrayOps.ofShort = new ArrayOps.ofShort(xs) - implicit def _unitArrayOps(xs: Array[Unit]): ArrayOps.ofUnit = new ArrayOps.ofUnit(xs) + implicit def booleanArrayOps(xs: Array[Boolean]): ArrayOps.ofBoolean = new ArrayOps.ofBoolean(xs) + implicit def byteArrayOps(xs: Array[Byte]): ArrayOps.ofByte = new ArrayOps.ofByte(xs) + implicit def charArrayOps(xs: Array[Char]): ArrayOps.ofChar = new ArrayOps.ofChar(xs) + implicit def doubleArrayOps(xs: Array[Double]): ArrayOps.ofDouble = new ArrayOps.ofDouble(xs) + implicit def floatArrayOps(xs: Array[Float]): ArrayOps.ofFloat = new ArrayOps.ofFloat(xs) + implicit def intArrayOps(xs: Array[Int]): ArrayOps.ofInt = new ArrayOps.ofInt(xs) + implicit def longArrayOps(xs: Array[Long]): ArrayOps.ofLong = new ArrayOps.ofLong(xs) + implicit def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps.ofRef[T] = new ArrayOps.ofRef[T](xs) + implicit def shortArrayOps(xs: Array[Short]): ArrayOps.ofShort = new ArrayOps.ofShort(xs) + implicit def unitArrayOps(xs: Array[Unit]): ArrayOps.ofUnit = new ArrayOps.ofUnit(xs) // "Autoboxing" and "Autounboxing" --------------------------------------------------- diff --git a/src/library/scala/collection/GenTraversableLike.scala b/src/library/scala/collection/GenTraversableLike.scala index d730996be2..1cd126f94f 100644 --- a/src/library/scala/collection/GenTraversableLike.scala +++ b/src/library/scala/collection/GenTraversableLike.scala @@ -24,7 +24,7 @@ import scala.annotation.migration * is found. * @define bfinfo an implicit value of class `CanBuildFrom` which determines * the result class `That` from the current representation type `Repr` and - * and the new element type `B`. + * the new element type `B`. * @define orderDependent * * Note: might return different results for different runs, unless the diff --git a/src/library/scala/collection/GenTraversableOnce.scala b/src/library/scala/collection/GenTraversableOnce.scala index d3096a872c..f87f7654bc 100644 --- a/src/library/scala/collection/GenTraversableOnce.scala +++ b/src/library/scala/collection/GenTraversableOnce.scala @@ -96,7 +96,7 @@ trait GenTraversableOnce[+A] extends Any { */ def size: Int - /** The size of this $coll if it is can be cheaply computed + /** The size of this $coll, if it can be cheaply computed * * @return the number of elements in this $coll, or -1 if the size cannot be determined cheaply */ diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index a26765027c..2d662257e5 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -113,7 +113,7 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ } def indexWhere(p: A => Boolean, from: Int): Int = { - var i = from + var i = from max 0 val it = iterator.drop(from) while (it.hasNext) { if (p(it.next())) return i diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index be2f427ea4..c9482fe0a2 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -605,22 +605,69 @@ trait TraversableLike[+A, +Repr] extends Any * applied to this $coll. By default the string prefix is the * simple name of the collection class $coll. */ - def stringPrefix : String = { + def stringPrefix: String = { + /* This method is written in a style that avoids calling `String.split()` + * as well as methods of java.lang.Character that require the Unicode + * database information. This is mostly important for Scala.js, so that + * using the collection library does automatically bring java.util.regex.* + * and the Unicode database in the generated code. + * + * This algorithm has the additional benefit that it won't allocate + * anything except the result String in the common case, where the class + * is not an inner class (i.e., when the result contains no '.'). + */ val fqn = repr.getClass.getName - val cls = { - val idx1 = fqn.lastIndexOf('.' : Int) - if (idx1 != -1) fqn.substring(idx1 + 1) else fqn + var pos: Int = fqn.length - 1 + + // Skip trailing $'s + while (pos != -1 && fqn.charAt(pos) == '$') { + pos -= 1 + } + if (pos == -1 || fqn.charAt(pos) == '.') { + return "" } - val parts = cls.split('$') - val last = parts.length - 1 - parts.zipWithIndex.foldLeft("") { case (z, (s, i)) => - if (s.isEmpty) z - else if (i != last && s.forall(java.lang.Character.isDigit)) "" // drop prefix in method-local classes - else if (i == 0 || java.lang.Character.isUpperCase(s.charAt(0))) { - if (z.isEmpty) s else z + '.' + s + + var result: String = "" + while (true) { + // Invariant: if we enter the loop, there is a non-empty part + + // Look for the beginning of the part, remembering where was the last non-digit + val partEnd = pos + 1 + while (pos != -1 && fqn.charAt(pos) <= '9' && fqn.charAt(pos) >= '0') { + pos -= 1 + } + val lastNonDigit = pos + while (pos != -1 && fqn.charAt(pos) != '$' && fqn.charAt(pos) != '.') { + pos -= 1 + } + val partStart = pos + 1 + + // A non-last part which contains only digits marks a method-local part -> drop the prefix + if (pos == lastNonDigit && partEnd != fqn.length) { + return result + } + + // Skip to the next part, and determine whether we are the end + while (pos != -1 && fqn.charAt(pos) == '$') { + pos -= 1 + } + val atEnd = pos == -1 || fqn.charAt(pos) == '.' + + // Handle the actual content of the part (we ignore parts that are likely synthetic) + def isPartLikelySynthetic = { + val firstChar = fqn.charAt(partStart) + (firstChar > 'Z' && firstChar < 0x7f) || (firstChar < 'A') + } + if (atEnd || !isPartLikelySynthetic) { + val part = fqn.substring(partStart, partEnd) + result = if (result.isEmpty) part else part + '.' + result + if (atEnd) + return result } - else z } + + // dead code + result } /** Creates a non-strict view of this $coll. diff --git a/src/library/scala/collection/immutable/Set.scala b/src/library/scala/collection/immutable/Set.scala index 3a8ee8b0be..047ea736bd 100644 --- a/src/library/scala/collection/immutable/Set.scala +++ b/src/library/scala/collection/immutable/Set.scala @@ -65,6 +65,7 @@ object Set extends ImmutableSetFactory[Set] { implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Set[A]] = setCanBuildFrom[A] /** An optimized representation for immutable empty sets */ + @SerialVersionUID(-2443710944435909512L) private object EmptySet extends AbstractSet[Any] with Set[Any] with Serializable { override def size: Int = 0 def contains(elem: Any): Boolean = false diff --git a/src/library/scala/collection/immutable/StringLike.scala b/src/library/scala/collection/immutable/StringLike.scala index 155d25d933..af8703293f 100644 --- a/src/library/scala/collection/immutable/StringLike.scala +++ b/src/library/scala/collection/immutable/StringLike.scala @@ -139,6 +139,7 @@ self => /** Returns this string with first character converted to upper case. * If the first character of the string is capitalized, it is returned unchanged. + * This method does not convert characters outside the Basic Multilingual Plane (BMP). */ def capitalize: String = if (toString == null) null diff --git a/src/library/scala/collection/mutable/ListBuffer.scala b/src/library/scala/collection/mutable/ListBuffer.scala index 02fcced3ac..3bb7004184 100644 --- a/src/library/scala/collection/mutable/ListBuffer.scala +++ b/src/library/scala/collection/mutable/ListBuffer.scala @@ -386,6 +386,25 @@ final class ListBuffer[A] this } + /** Selects the last element. + * + * Runs in constant time. + * + * @return the last element of this buffer. + * @throws NoSuchElementException if this buffer is empty. + */ + override def last: A = + if (last0 eq null) throw new NoSuchElementException("last of empty ListBuffer") + else last0.head + + /** Optionally selects the last element. + * + * Runs in constant time. + * + * @return `Some` of the last element of this buffer if the buffer is nonempty, `None` if it is empty. + */ + override def lastOption: Option[A] = if (last0 eq null) None else Some(last0.head) + /** Returns an iterator over this `ListBuffer`. The iterator will reflect * changes made to the underlying `ListBuffer` beyond the next element; * the next element's value is cached so that `hasNext` and `next` are diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index c0398605a6..6c1c9a0c80 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -116,7 +116,7 @@ trait Future[+T] extends Awaitable[T] { @deprecated("use `foreach` or `onComplete` instead (keep in mind that they take total rather than partial functions)", "2.12.0") def onSuccess[U](pf: PartialFunction[T, U])(implicit executor: ExecutionContext): Unit = onComplete { case Success(v) => - pf.applyOrElse[T, Any](v, Predef.conforms[T]) // Exploiting the cached function to avoid MatchError + pf.applyOrElse[T, Any](v, Predef.identity[T]) // Exploiting the cached function to avoid MatchError case _ => } @@ -141,7 +141,7 @@ trait Future[+T] extends Awaitable[T] { @deprecated("use `onComplete` or `failed.foreach` instead (keep in mind that they take total rather than partial functions)", "2.12.0") def onFailure[U](@deprecatedName('callback) pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Unit = onComplete { case Failure(t) => - pf.applyOrElse[Throwable, Any](t, Predef.conforms[Throwable]) // Exploiting the cached function to avoid MatchError + pf.applyOrElse[Throwable, Any](t, Predef.identity[Throwable]) // Exploiting the cached function to avoid MatchError case _ => } @@ -528,7 +528,7 @@ trait Future[+T] extends Awaitable[T] { def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = transform { result => - try pf.applyOrElse[Try[T], Any](result, Predef.conforms[Try[T]]) + try pf.applyOrElse[Try[T], Any](result, Predef.identity[Try[T]]) catch { case NonFatal(t) => executor reportFailure t } result diff --git a/src/library/scala/inline.scala b/src/library/scala/inline.scala index dc55af301c..f6d7c7569e 100644 --- a/src/library/scala/inline.scala +++ b/src/library/scala/inline.scala @@ -29,7 +29,7 @@ package scala * } * }}} * - * Note: parentheses are required when annotating a callsite withing a larger expression. + * Note: parentheses are required when annotating a callsite within a larger expression. * * {{{ * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline diff --git a/src/library/scala/noinline.scala b/src/library/scala/noinline.scala index a427e170f4..0cd5ef9f64 100644 --- a/src/library/scala/noinline.scala +++ b/src/library/scala/noinline.scala @@ -29,7 +29,7 @@ package scala * } * }}} * - * Note: parentheses are required when annotating a callsite withing a larger expression. + * Note: parentheses are required when annotating a callsite within a larger expression. * * {{{ * def t1 = f1(1) + f1(1): @noinline // equivalent to (f1(1) + f1(1)): @noinline diff --git a/src/library/scala/reflect/ClassManifestDeprecatedApis.scala b/src/library/scala/reflect/ClassManifestDeprecatedApis.scala index 30a99340cc..cd46f0ff76 100644 --- a/src/library/scala/reflect/ClassManifestDeprecatedApis.scala +++ b/src/library/scala/reflect/ClassManifestDeprecatedApis.scala @@ -205,15 +205,18 @@ object ClassManifestFactory { case m: ClassManifest[_] => m.asInstanceOf[ClassManifest[T]].arrayManifest } + @SerialVersionUID(1L) + private class AbstractTypeClassManifest[T](prefix: OptManifest[_], name: String, clazz: jClass[_], args: OptManifest[_]*) extends ClassManifest[T] { + override def runtimeClass = clazz + override val typeArguments = args.toList + override def toString = prefix.toString+"#"+name+argString + } + /** ClassManifest for the abstract type `prefix # name`. `upperBound` is not * strictly necessary as it could be obtained by reflection. It was * added so that erasure can be calculated without reflection. */ def abstractType[T](prefix: OptManifest[_], name: String, clazz: jClass[_], args: OptManifest[_]*): ClassManifest[T] = - new ClassManifest[T] { - override def runtimeClass = clazz - override val typeArguments = args.toList - override def toString = prefix.toString+"#"+name+argString - } + new AbstractTypeClassManifest(prefix, name, clazz) /** ClassManifest for the abstract type `prefix # name`. `upperBound` is not * strictly necessary as it could be obtained by reflection. It was @@ -221,15 +224,12 @@ object ClassManifestFactory { * todo: remove after next bootstrap */ def abstractType[T](prefix: OptManifest[_], name: String, upperbound: ClassManifest[_], args: OptManifest[_]*): ClassManifest[T] = - new ClassManifest[T] { - override def runtimeClass = upperbound.runtimeClass - override val typeArguments = args.toList - override def toString = prefix.toString+"#"+name+argString - } + new AbstractTypeClassManifest(prefix, name, upperbound.runtimeClass) } /** Manifest for the class type `clazz[args]`, where `clazz` is * a top-level or static class */ +@SerialVersionUID(1L) private class ClassTypeManifest[T]( prefix: Option[OptManifest[_]], val runtimeClass: jClass[_], diff --git a/src/library/scala/reflect/ClassTag.scala b/src/library/scala/reflect/ClassTag.scala index eb3aeeecfc..30ceadceeb 100644 --- a/src/library/scala/reflect/ClassTag.scala +++ b/src/library/scala/reflect/ClassTag.scala @@ -119,6 +119,7 @@ object ClassTag { val Nothing : ClassTag[scala.Nothing] = Manifest.Nothing val Null : ClassTag[scala.Null] = Manifest.Null + @SerialVersionUID(1L) private class GenericClassTag[T](val runtimeClass: jClass[_]) extends ClassTag[T] def apply[T](runtimeClass1: jClass[_]): ClassTag[T] = diff --git a/src/library/scala/reflect/Manifest.scala b/src/library/scala/reflect/Manifest.scala index 9c38864194..8e5ba6376e 100644 --- a/src/library/scala/reflect/Manifest.scala +++ b/src/library/scala/reflect/Manifest.scala @@ -87,6 +87,7 @@ object ManifestFactory { def valueManifests: List[AnyValManifest[_]] = List(Byte, Short, Char, Int, Long, Float, Double, Boolean, Unit) + @SerialVersionUID(1L) private class ByteManifest extends AnyValManifest[scala.Byte]("Byte") { def runtimeClass = java.lang.Byte.TYPE override def newArray(len: Int): Array[Byte] = new Array[Byte](len) @@ -96,6 +97,7 @@ object ManifestFactory { } val Byte: AnyValManifest[Byte] = new ByteManifest + @SerialVersionUID(1L) private class ShortManifest extends AnyValManifest[scala.Short]("Short") { def runtimeClass = java.lang.Short.TYPE override def newArray(len: Int): Array[Short] = new Array[Short](len) @@ -105,6 +107,7 @@ object ManifestFactory { } val Short: AnyValManifest[Short] = new ShortManifest + @SerialVersionUID(1L) private class CharManifest extends AnyValManifest[scala.Char]("Char") { def runtimeClass = java.lang.Character.TYPE override def newArray(len: Int): Array[Char] = new Array[Char](len) @@ -114,6 +117,7 @@ object ManifestFactory { } val Char: AnyValManifest[Char] = new CharManifest + @SerialVersionUID(1L) private class IntManifest extends AnyValManifest[scala.Int]("Int") { def runtimeClass = java.lang.Integer.TYPE override def newArray(len: Int): Array[Int] = new Array[Int](len) @@ -123,6 +127,7 @@ object ManifestFactory { } val Int: AnyValManifest[Int] = new IntManifest + @SerialVersionUID(1L) private class LongManifest extends AnyValManifest[scala.Long]("Long") { def runtimeClass = java.lang.Long.TYPE override def newArray(len: Int): Array[Long] = new Array[Long](len) @@ -132,6 +137,7 @@ object ManifestFactory { } val Long: AnyValManifest[Long] = new LongManifest + @SerialVersionUID(1L) private class FloatManifest extends AnyValManifest[scala.Float]("Float") { def runtimeClass = java.lang.Float.TYPE override def newArray(len: Int): Array[Float] = new Array[Float](len) @@ -141,6 +147,7 @@ object ManifestFactory { } val Float: AnyValManifest[Float] = new FloatManifest + @SerialVersionUID(1L) private class DoubleManifest extends AnyValManifest[scala.Double]("Double") { def runtimeClass = java.lang.Double.TYPE override def newArray(len: Int): Array[Double] = new Array[Double](len) @@ -150,6 +157,7 @@ object ManifestFactory { } val Double: AnyValManifest[Double] = new DoubleManifest + @SerialVersionUID(1L) private class BooleanManifest extends AnyValManifest[scala.Boolean]("Boolean") { def runtimeClass = java.lang.Boolean.TYPE override def newArray(len: Int): Array[Boolean] = new Array[Boolean](len) @@ -159,6 +167,7 @@ object ManifestFactory { } val Boolean: AnyValManifest[Boolean] = new BooleanManifest + @SerialVersionUID(1L) private class UnitManifest extends AnyValManifest[scala.Unit]("Unit") { def runtimeClass = java.lang.Void.TYPE override def newArray(len: Int): Array[Unit] = new Array[Unit](len) @@ -175,6 +184,7 @@ object ManifestFactory { private val NothingTYPE = classOf[scala.runtime.Nothing$] private val NullTYPE = classOf[scala.runtime.Null$] + @SerialVersionUID(1L) private class AnyManifest extends PhantomManifest[scala.Any](ObjectTYPE, "Any") { override def newArray(len: Int) = new Array[scala.Any](len) override def <:<(that: ClassManifest[_]): Boolean = (that eq this) @@ -182,6 +192,7 @@ object ManifestFactory { } val Any: Manifest[scala.Any] = new AnyManifest + @SerialVersionUID(1L) private class ObjectManifest extends PhantomManifest[java.lang.Object](ObjectTYPE, "Object") { override def newArray(len: Int) = new Array[java.lang.Object](len) override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) @@ -191,6 +202,7 @@ object ManifestFactory { val AnyRef: Manifest[scala.AnyRef] = Object.asInstanceOf[Manifest[scala.AnyRef]] + @SerialVersionUID(1L) private class AnyValPhantomManifest extends PhantomManifest[scala.AnyVal](ObjectTYPE, "AnyVal") { override def newArray(len: Int) = new Array[scala.AnyVal](len) override def <:<(that: ClassManifest[_]): Boolean = (that eq this) || (that eq Any) @@ -198,6 +210,7 @@ object ManifestFactory { } val AnyVal: Manifest[scala.AnyVal] = new AnyValPhantomManifest + @SerialVersionUID(1L) private class NullManifest extends PhantomManifest[scala.Null](NullTYPE, "Null") { override def newArray(len: Int) = new Array[scala.Null](len) override def <:<(that: ClassManifest[_]): Boolean = @@ -206,6 +219,7 @@ object ManifestFactory { } val Null: Manifest[scala.Null] = new NullManifest + @SerialVersionUID(1L) private class NothingManifest extends PhantomManifest[scala.Nothing](NothingTYPE, "Nothing") { override def newArray(len: Int) = new Array[scala.Nothing](len) override def <:<(that: ClassManifest[_]): Boolean = (that ne null) @@ -213,6 +227,7 @@ object ManifestFactory { } val Nothing: Manifest[scala.Nothing] = new NothingManifest + @SerialVersionUID(1L) private class SingletonTypeManifest[T <: AnyRef](value: AnyRef) extends Manifest[T] { lazy val runtimeClass = value.getClass override lazy val toString = value.toString + ".type" @@ -243,6 +258,7 @@ object ManifestFactory { def classType[T](prefix: Manifest[_], clazz: Predef.Class[_], args: Manifest[_]*): Manifest[T] = new ClassTypeManifest[T](Some(prefix), clazz, args.toList) + @SerialVersionUID(1L) private abstract class PhantomManifest[T](_runtimeClass: Predef.Class[_], override val toString: String) extends ClassTypeManifest[T](None, _runtimeClass, Nil) { override def equals(that: Any): Boolean = this eq that.asInstanceOf[AnyRef] @@ -252,6 +268,7 @@ object ManifestFactory { /** Manifest for the class type `clazz[args]`, where `clazz` is * a top-level or static class. */ + @SerialVersionUID(1L) private class ClassTypeManifest[T](prefix: Option[Manifest[_]], val runtimeClass: Predef.Class[_], override val typeArguments: List[Manifest[_]]) extends Manifest[T] { @@ -264,6 +281,7 @@ object ManifestFactory { def arrayType[T](arg: Manifest[_]): Manifest[Array[T]] = arg.asInstanceOf[Manifest[T]].arrayManifest + @SerialVersionUID(1L) private class AbstractTypeManifest[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: Seq[Manifest[_]]) extends Manifest[T] { def runtimeClass = upperBound override val typeArguments = args.toList @@ -276,6 +294,7 @@ object ManifestFactory { def abstractType[T](prefix: Manifest[_], name: String, upperBound: Predef.Class[_], args: Manifest[_]*): Manifest[T] = new AbstractTypeManifest[T](prefix, name, upperBound, args) + @SerialVersionUID(1L) private class WildcardManifest[T](lowerBound: Manifest[_], upperBound: Manifest[_]) extends Manifest[T] { def runtimeClass = upperBound.runtimeClass override def toString = @@ -289,6 +308,7 @@ object ManifestFactory { def wildcardType[T](lowerBound: Manifest[_], upperBound: Manifest[_]): Manifest[T] = new WildcardManifest[T](lowerBound, upperBound) + @SerialVersionUID(1L) private class IntersectionTypeManifest[T](parents: Seq[Manifest[_]]) extends Manifest[T] { def runtimeClass = parents.head.runtimeClass override def toString = parents.mkString(" with ") diff --git a/src/library/scala/reflect/NameTransformer.scala b/src/library/scala/reflect/NameTransformer.scala index ae36f5edc2..bdf5165df5 100644 --- a/src/library/scala/reflect/NameTransformer.scala +++ b/src/library/scala/reflect/NameTransformer.scala @@ -13,15 +13,16 @@ package reflect * Also provides some constants. */ object NameTransformer { - // XXX Short term: providing a way to alter these without having to recompile - // the compiler before recompiling the compiler. - val MODULE_SUFFIX_STRING = sys.props.getOrElse("SCALA_MODULE_SUFFIX_STRING", "$") - val NAME_JOIN_STRING = sys.props.getOrElse("SCALA_NAME_JOIN_STRING", "$") - val MODULE_INSTANCE_NAME = "MODULE$" - val LOCAL_SUFFIX_STRING = " " - val LAZY_LOCAL_SUFFIX_STRING = "$lzy" - val SETTER_SUFFIX_STRING = "_$eq" - val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" + // TODO: reduce duplication with and in StdNames + // I made these constants because we cannot change them without bumping our major version anyway. + final val NAME_JOIN_STRING = "$" + final val MODULE_SUFFIX_STRING = "$" + final val MODULE_INSTANCE_NAME = "MODULE$" + final val LOCAL_SUFFIX_STRING = " " + final val LAZY_LOCAL_SUFFIX_STRING = "$lzy" + final val MODULE_VAR_SUFFIX_STRING = "$module" + final val SETTER_SUFFIX_STRING = "_$eq" + final val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" private val nops = 128 private val ncodes = 26 * 26 diff --git a/src/library/scala/runtime/LazyRef.scala b/src/library/scala/runtime/LazyRef.scala new file mode 100644 index 0000000000..5a0bd5442c --- /dev/null +++ b/src/library/scala/runtime/LazyRef.scala @@ -0,0 +1,157 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2016, LAMP/EPFL and Lightbend, Inc ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.runtime + +/** Classes used as holders for lazy vals defined in methods. */ + +class LazyRef[T] { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: T = _ + def value: T = _value + def initialize(value: T): T = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyRef ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyBoolean { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Boolean = _ + def value: Boolean = _value + def initialize(value: Boolean): Boolean = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyBoolean ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyByte { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Byte = _ + + def value: Byte = _value + + def initialize(value: Byte): Byte = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyByte ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyChar { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Char = _ + def value: Char = _value + def initialize(value: Char): Char = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyChar ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyShort { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Short = _ + def value: Short = _value + def initialize(value: Short): Short = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyShort ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyInt { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Int = _ + def value: Int = _value + def initialize(value: Int): Int = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyInt ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyLong { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Long = _ + def value: Long = _value + def initialize(value: Long): Long = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyLong ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyFloat { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Float = _ + def value: Float = _value + def initialize(value: Float): Float = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyFloat ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyDouble { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + private[this] var _value: Double = _ + def value: Double = _value + def initialize(value: Double): Double = { + _value = value + _initialized = true + value + } + + override def toString = s"LazyDouble ${if (_initialized) s"of: ${_value}" else "thunk"}" +} + +class LazyUnit { + @volatile private[this] var _initialized: Boolean = _ + def initialized = _initialized + + def initialize(): Unit = _initialized = true + + override def toString = s"LazyUnit${if (_initialized) "" else " thunk"}" +} diff --git a/src/library/scala/sys/process/ProcessBuilder.scala b/src/library/scala/sys/process/ProcessBuilder.scala index 9713b712fc..fe4c30ee50 100644 --- a/src/library/scala/sys/process/ProcessBuilder.scala +++ b/src/library/scala/sys/process/ProcessBuilder.scala @@ -90,19 +90,19 @@ import ProcessBuilder._ * * If not specified, the input of the external commands executed with `run` or * `!` will not be tied to anything, and the output will be redirected to the - * stdout and stderr of the Scala process. For the methods `!!` and `lines`, no + * stdout and stderr of the Scala process. For the methods `!!` and `lineStream`, no * input will be provided, and the output will be directed according to the * semantics of these methods. * * Some methods will cause stdin to be used as input. Output can be controlled - * with a [[scala.sys.process.ProcessLogger]] -- `!!` and `lines` will only + * with a [[scala.sys.process.ProcessLogger]] -- `!!` and `lineStream` will only * redirect error output when passed a `ProcessLogger`. If one desires full * control over input and output, then a [[scala.sys.process.ProcessIO]] can be * used with `run`. * - * For example, we could silence the error output from `lines_!` like this: + * For example, we could silence the error output from `lineStream_!` like this: * {{{ - * val etcFiles = "find /etc" lines_! ProcessLogger(line => ()) + * val etcFiles = "find /etc" lineStream_! ProcessLogger(line => ()) * }}} * * ==Extended Example== diff --git a/src/library/scala/sys/process/package.scala b/src/library/scala/sys/process/package.scala index ff0fd920c9..bf4287dfc3 100644 --- a/src/library/scala/sys/process/package.scala +++ b/src/library/scala/sys/process/package.scala @@ -25,7 +25,7 @@ package scala.sys { * * {{{ * import scala.sys.process._ - * "ls" #| "grep .scala" #&& Seq("sh", "-c", "scalac *.scala") #|| "echo nothing found" lines + * "ls" #| "grep .scala" #&& Seq("sh", "-c", "scalac *.scala") #|| "echo nothing found" lineStream * }}} * * We describe below the general concepts and architecture of the package, @@ -92,7 +92,7 @@ package scala.sys { * * - Return status of the process (`!` methods) * - Output of the process as a `String` (`!!` methods) - * - Continuous output of the process as a `Stream[String]` (`lines` methods) + * - Continuous output of the process as a `Stream[String]` (`lineStream` methods) * - The `Process` representing it (`run` methods) * * Some simple examples of these methods: @@ -109,7 +109,7 @@ package scala.sys { * // a Stream[String] * def sourceFilesAt(baseDir: String): Stream[String] = { * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") - * cmd.lines + * cmd.lineStream * } * }}} * @@ -167,8 +167,8 @@ package scala.sys { * def sourceFilesAt(baseDir: String): (Stream[String], StringBuffer) = { * val buffer = new StringBuffer() * val cmd = Seq("find", baseDir, "-name", "*.scala", "-type", "f") - * val lines = cmd lines_! ProcessLogger(buffer append _) - * (lines, buffer) + * val lineStream = cmd lineStream_! ProcessLogger(buffer append _) + * (lineStream, buffer) * } * }}} * diff --git a/src/library/scala/util/Either.scala b/src/library/scala/util/Either.scala index 169786d31b..7bded972f2 100644 --- a/src/library/scala/util/Either.scala +++ b/src/library/scala/util/Either.scala @@ -55,31 +55,31 @@ package util * val left23: Left[Double, Int] = Left(23.0) * val left42 = Left(42.0) * - * for ( - * a <- right1; - * b <- right2; + * for { + * a <- right1 + * b <- right2 * c <- right3 - * ) yield a + b + c // Right(6) + * } yield a + b + c // Right(6) * - * for ( - * a <- right1; - * b <- right2; + * for { + * a <- right1 + * b <- right2 * c <- left23 - * ) yield a + b + c // Left(23.0) + * } yield a + b + c // Left(23.0) * - * for ( - * a <- right1; - * b <- left23; + * for { + * a <- right1 + * b <- left23 * c <- right2 - * ) yield a + b + c // Left(23.0) + * } yield a + b + c // Left(23.0) * * // It is advisable to provide the type of the “missing” value (especially the right value for `Left`) * // as otherwise that type might be infered as `Nothing` without context: - * for ( - * a <- left23; - * b <- right1; + * for { + * a <- left23 + * b <- right1 * c <- left42 // type at this position: Either[Double, Nothing] - * ) yield a + b + c + * } yield a + b + c * // ^ * // error: ambiguous reference to overloaded definition, * // both method + in class Int of type (x: Char)Int @@ -95,13 +95,15 @@ sealed abstract class Either[+A, +B] extends Product with Serializable { /** * Projects this `Either` as a `Left`. */ - @deprecated("use swap instead", "2.12.0") def left = Either.LeftProjection(this) /** * Projects this `Either` as a `Right`. + * + * Because `Either` is right-biased, this method is not normally needed. + * (It is retained in the API for now for easy cross-compilation between Scala + * 2.11 and 2.12.) */ - @deprecated("Either is now right-biased", "2.12.0") def right = Either.RightProjection(this) /** @@ -134,10 +136,10 @@ sealed abstract class Either[+A, +B] extends Product with Serializable { * @example {{{ * val right = Right(2) * val left = Left(3) - * for ( - * r1 <- right; + * for { + * r1 <- right * r2 <- left.swap - * ) yield r1 * r2 // Right(6) + * } yield r1 * r2 // Right(6) * }}} */ def swap: Either[B, A] = this match { @@ -245,7 +247,7 @@ sealed abstract class Either[+A, +B] extends Product with Serializable { /** * Returns `true` if `Left` or returns the result of the application of - * the given function to the `Right` value. + * the given predicate to the `Right` value. * * {{{ * Right(12).forall(_ > 10) // true @@ -260,7 +262,7 @@ sealed abstract class Either[+A, +B] extends Product with Serializable { /** * Returns `false` if `Left` or returns the result of the application of - * the given function to the `Right` value. + * the given predicate to the `Right` value. * * {{{ * Right(12).exists(_ > 10) // true @@ -426,7 +428,10 @@ object Either { /** * Projects an `Either` into a `Left`. * - * This allows for-comprehensions over Either instances - for example {{{ + * This allows for-comprehensions over the left side of Either instances, + * reversing Either's usual right-bias. + * + * For example {{{ * for (s <- Left("flower").left) yield s.length // Left(6) * }}} * @@ -472,7 +477,6 @@ object Either { * @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse * @version 1.0, 11/10/2008 */ - @deprecated("use swap instead", "2.12.0") final case class LeftProjection[+A, +B](e: Either[A, B]) { /** * Returns the value from this `Left` or throws `java.util.NoSuchElementException` @@ -624,19 +628,13 @@ object Either { /** * Projects an `Either` into a `Right`. * - * This allows for-comprehensions over Either instances - for example {{{ - * for (s <- Right("flower").right) yield s.length // Right(6) - * }}} - * - * Continuing the analogy with [[scala.Option]], a `RightProjection` declares - * that `Right` should be analogous to `Some` in some code. - * - * Analogous to `LeftProjection`, see example usage in its documentation above. + * Because `Either` is already right-biased, this class is not normally needed. + * (It is retained in the library for now for easy cross-compilation between Scala + * 2.11 and 2.12.) * * @author <a href="mailto:research@workingmouse.com">Tony Morris</a>, Workingmouse * @version 1.0, 11/10/2008 */ - @deprecated("Either is now right-biased", "2.12.0") final case class RightProjection[+A, +B](e: Either[A, B]) { /** diff --git a/src/library/scala/util/Properties.scala b/src/library/scala/util/Properties.scala index a176748cd6..9d62bfe6ef 100644 --- a/src/library/scala/util/Properties.scala +++ b/src/library/scala/util/Properties.scala @@ -105,7 +105,7 @@ private[scala] trait PropertiesTrait { * or "version (unknown)" if it cannot be determined. */ val versionString = "version " + scalaPropOrElse("version.number", "(unknown)") - val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2016, LAMP/EPFL") + val copyrightString = scalaPropOrElse("copyright.string", "Copyright 2002-2016, LAMP/EPFL and Lightbend, Inc.") /** This is the encoding to use reading in source files, overridden with -encoding. * Note that it uses "prop" i.e. looks in the scala jar, not the system properties. @@ -168,29 +168,61 @@ private[scala] trait PropertiesTrait { /** Compares the given specification version to the specification version of the platform. * - * @param version a specification version of the form "major.minor" - * @return `true` iff the specification version of the current runtime - * is equal to or higher than the version denoted by the given string. - * @throws NumberFormatException if the given string is not a version string + * @param version a specification version number (legacy forms acceptable) + * @return `true` if the specification version of the current runtime + * is equal to or higher than the version denoted by the given string. + * @throws NumberFormatException if the given string is not a version string * - * @example {{{ - * // In this example, the runtime's Java specification is assumed to be at version 1.7. - * isJavaAtLeast("1.6") // true - * isJavaAtLeast("1.7") // true - * isJavaAtLeast("1.8") // false - * }}} + * @example {{{ + * // In this example, the runtime's Java specification is assumed to be at version 8. + * isJavaAtLeast("1.8") // true + * isJavaAtLeast("8") // true + * isJavaAtLeast("9") // false + * isJavaAtLeast("9.1") // false + * isJavaAtLeast("1.9") // throws + * }}} */ def isJavaAtLeast(version: String): Boolean = { - def parts(x: String) = { - val i = x.indexOf('.') - if (i < 0) throw new NumberFormatException("Not a version: " + x) - (x.substring(0, i), x.substring(i+1, x.length)) + def versionOf(s: String, depth: Int): (Int, String) = + s.indexOf('.') match { + case 0 => + (-2, s.substring(1)) + case 1 if depth == 0 && s.charAt(0) == '1' => + val r0 = s.substring(2) + val (v, r) = versionOf(r0, 1) + val n = if (v > 8 || r0.isEmpty) -2 else v // accept 1.8, not 1.9 or 1. + (n, r) + case -1 => + val n = if (!s.isEmpty) s.toInt else if (depth == 0) -2 else 0 + (n, "") + case i => + val r = s.substring(i + 1) + val n = if (depth < 2 && r.isEmpty) -2 else s.substring(0, i).toInt + (n, r) + } + def compareVersions(s: String, v: String, depth: Int): Int = { + if (depth >= 3) 0 + else { + val (sn, srest) = versionOf(s, depth) + val (vn, vrest) = versionOf(v, depth) + if (vn < 0) -2 + else if (sn < vn) -1 + else if (sn > vn) 1 + else compareVersions(srest, vrest, depth + 1) + } + } + compareVersions(javaSpecVersion, version, 0) match { + case -2 => throw new NumberFormatException(s"Not a version: $version") + case i => i >= 0 } - val (v, _v) = parts(version) - val (s, _s) = parts(javaSpecVersion) - s.toInt >= v.toInt && _s.toInt >= _v.toInt } + /** Tests whether the major version of the platform specification is at least the given value. + * + * @param version a major version number + */ + def isJavaAtLeast(version: Int): Boolean = isJavaAtLeast(version.toString) + // provide a main method so version info can be obtained by running this def main(args: Array[String]) { val writer = new PrintWriter(Console.err, true) diff --git a/src/manual/scala/man1/scala.scala b/src/manual/scala/man1/scala.scala index 9f97dd546c..3cfa9f8cb1 100644 --- a/src/manual/scala/man1/scala.scala +++ b/src/manual/scala/man1/scala.scala @@ -144,17 +144,14 @@ object scala extends Command { Mono("-nocompdaemon") & " or " & Mono("-nc") & " option can be used to " & "prevent this.", - "If " & Mono("scala") & " is run from an sbaz(1) directory, " & - "then it will add to its classpath any jars installed in the " & - "lib directory of the sbaz directory. Additionally, if no " & - "-classpath option is specified, then " & Mono("scala") & + "If no -classpath option is specified, then " & Mono("scala") & " will add " & Quote(".") & ", the current directory, to the " & "end of the classpath.") val options = Section("OPTIONS", "If any compiler options are specified, they must be first in the " & - "command line and must be followed by a bare hypen (" & Quote("-") & + "command line and must be followed by a bare hyphen (" & Quote("-") & ") character. " & "If no arguments are specified after the optional compiler arguments, " & "then an interactive Scala shell is started. Otherwise, either a " & diff --git a/src/manual/scala/man1/scalac.scala b/src/manual/scala/man1/scalac.scala index 6ffcccea25..79c175e0f0 100644 --- a/src/manual/scala/man1/scalac.scala +++ b/src/manual/scala/man1/scalac.scala @@ -379,8 +379,8 @@ object scalac extends Command { MItalic("posterasure"), "clean up erased inline classes"), Definition( - MItalic("lazyvals"), - "allocate bitmaps, translate lazy vals into lazified defs"), + MItalic("fields"), + "synthesize accessors and fields, including bitmaps for lazy vals"), Definition( MItalic("lambdalift"), "move nested functions to top level"), diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index d0539dfd42..fc7e184918 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -81,7 +81,7 @@ trait Definitions extends api.StandardDefinitions { } } - private[Definitions] def classesMap[T](f: Name => T) = symbolsMap(ScalaValueClassesNoUnit, f) + private[Definitions] def classesMap[T](f: Name => T): Map[Symbol, T] = symbolsMap(ScalaValueClassesNoUnit, f) private def symbolsMap[T](syms: List[Symbol], f: Name => T): Map[Symbol, T] = mapFrom(syms)(x => f(x.name)) private def symbolsMapFilt[T](syms: List[Symbol], p: Name => Boolean, f: Name => T) = symbolsMap(syms filter (x => p(x.name)), f) @@ -93,6 +93,9 @@ trait Definitions extends api.StandardDefinitions { lazy val boxedClass = classesMap(x => getClassByName(boxedName(x))) lazy val refClass = classesMap(x => getRequiredClass("scala.runtime." + x + "Ref")) lazy val volatileRefClass = classesMap(x => getRequiredClass("scala.runtime.Volatile" + x + "Ref")) + lazy val lazyHolders = symbolsMap(ScalaValueClasses, x => getClassIfDefined("scala.runtime.Lazy" + x)) + lazy val LazyRefClass = getClassIfDefined("scala.runtime.LazyRef") + lazy val LazyUnitClass = getClassIfDefined("scala.runtime.LazyUnit") lazy val allRefClasses: Set[Symbol] = { refClass.values.toSet ++ volatileRefClass.values.toSet ++ Set(VolatileObjectRefClass, ObjectRefClass) @@ -837,14 +840,14 @@ trait Definitions extends api.StandardDefinitions { * * The method must be monomorphic and have exactly one parameter list. * The class defining the method is a supertype of `tp` that - * has a public no-arg primary constructor. + * has a public no-arg primary constructor and it can be subclassed (not final or sealed). */ def samOf(tp: Type): Symbol = if (!doSam) NoSymbol else if (!isNonRefinementClassType(unwrapToClass(tp))) NoSymbol else { // look at erased type because we (only) care about what ends up in bytecode // (e.g., an alias type is fine as long as is compiles to a single-abstract-method) val tpSym: Symbol = erasure.javaErasure(tp).typeSymbol - if (tpSym.exists && tpSym.isClass + if (tpSym.exists && tpSym.isClass && !(tpSym hasFlag (FINAL | SEALED)) // if tp has a constructor (its class is not a trait), it must be public and must not take any arguments // (implementation restriction: implicit argument lists are excluded to simplify type inference in adaptToSAM) && { val ctor = tpSym.primaryConstructor @@ -1151,7 +1154,6 @@ trait Definitions extends api.StandardDefinitions { lazy val ElidableMethodClass = requiredClass[scala.annotation.elidable] lazy val ImplicitNotFoundClass = requiredClass[scala.annotation.implicitNotFound] lazy val ImplicitAmbiguousClass = getClassIfDefined("scala.annotation.implicitAmbiguous") - lazy val JUnitTestClass = getClassIfDefined("org.junit.Test") lazy val MigrationAnnotationClass = requiredClass[scala.annotation.migration] lazy val ScalaStrictFPAttr = requiredClass[scala.annotation.strictfp] lazy val SwitchClass = requiredClass[scala.annotation.switch] @@ -1193,6 +1195,8 @@ trait Definitions extends api.StandardDefinitions { lazy val MethodTargetClass = requiredClass[meta.companionMethod] // TODO: module, moduleClass? package, packageObject? lazy val LanguageFeatureAnnot = requiredClass[meta.languageFeature] + lazy val JUnitAnnotations = List("Test", "Ignore", "Before", "After", "BeforeClass", "AfterClass").map(n => getClassIfDefined("org.junit." + n)) + // Language features lazy val languageFeatureModule = getRequiredModule("scala.languageFeature") diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index 3d1c160d52..6b1063ccd9 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -91,7 +91,6 @@ trait Mirrors extends api.Mirrors { private def ensureClassSymbol(fullname: String, sym: Symbol): ClassSymbol = { var result = sym - while (result.isAliasType) result = result.info.typeSymbol result match { case x: ClassSymbol => x case _ => MissingRequirementError.notFound("class " + fullname) @@ -212,27 +211,6 @@ trait Mirrors extends api.Mirrors { try body catch { case _: MissingRequirementError => NoSymbol } - /** getModule2/getClass2 aren't needed at present but may be again, - * so for now they're mothballed. - */ - // def getModule2(name1: Name, name2: Name) = { - // try getModuleOrClass(name1.toTermName) - // catch { case ex1: FatalError => - // try getModuleOrClass(name2.toTermName) - // catch { case ex2: FatalError => throw ex1 } - // } - // } - // def getClass2(name1: Name, name2: Name) = { - // try { - // val result = getModuleOrClass(name1.toTypeName) - // if (result.isAliasType) getClass(name2) else result - // } - // catch { case ex1: FatalError => - // try getModuleOrClass(name2.toTypeName) - // catch { case ex2: FatalError => throw ex1 } - // } - // } - def init() { if (initialized) return // Still fiddling with whether it's cleaner to do some of this setup here diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 925018d3a6..2e820a68e0 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -92,14 +92,15 @@ trait StdNames { def flattenedName(segments: Name*): NameType = compactify(segments mkString NAME_JOIN_STRING) - val NAME_JOIN_STRING: String = NameTransformer.NAME_JOIN_STRING - val MODULE_SUFFIX_STRING: String = NameTransformer.MODULE_SUFFIX_STRING - val LOCAL_SUFFIX_STRING: String = NameTransformer.LOCAL_SUFFIX_STRING - val LAZY_LOCAL_SUFFIX_STRING: String = NameTransformer.LAZY_LOCAL_SUFFIX_STRING - - val TRAIT_SETTER_SEPARATOR_STRING: String = NameTransformer.TRAIT_SETTER_SEPARATOR_STRING - - val SINGLETON_SUFFIX: String = ".type" + // TODO: what is the purpose of all this duplication!?!?! + // I made these constants because we cannot change them without bumping our major version anyway. + final val NAME_JOIN_STRING = NameTransformer.NAME_JOIN_STRING + final val MODULE_SUFFIX_STRING = NameTransformer.MODULE_SUFFIX_STRING + final val MODULE_VAR_SUFFIX_STRING = NameTransformer.MODULE_VAR_SUFFIX_STRING + final val LOCAL_SUFFIX_STRING = NameTransformer.LOCAL_SUFFIX_STRING + final val LAZY_LOCAL_SUFFIX_STRING = NameTransformer.LAZY_LOCAL_SUFFIX_STRING + final val TRAIT_SETTER_SEPARATOR_STRING = NameTransformer.TRAIT_SETTER_SEPARATOR_STRING + final val SINGLETON_SUFFIX = ".type" val ANON_CLASS_NAME: NameType = "$anon" val DELAMBDAFY_LAMBDA_CLASS_NAME: NameType = "$lambda" @@ -108,7 +109,7 @@ trait StdNames { val EMPTY_PACKAGE_NAME: NameType = "<empty>" val IMPORT: NameType = "<import>" val MODULE_SUFFIX_NAME: NameType = MODULE_SUFFIX_STRING - val MODULE_VAR_SUFFIX: NameType = "$module" + val MODULE_VAR_SUFFIX: NameType = MODULE_VAR_SUFFIX_STRING val PACKAGE: NameType = "package" val ROOT: NameType = "<root>" val SPECIALIZED_SUFFIX: NameType = "$sp" @@ -704,6 +705,8 @@ trait StdNames { val immutable: NameType = "immutable" val implicitly: NameType = "implicitly" val in: NameType = "in" + val initialize : NameType = "initialize" + val initialized : NameType = "initialized" val internal: NameType = "internal" val inlinedEquals: NameType = "inlinedEquals" val isArray: NameType = "isArray" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index 487aadf5e5..8d77e334db 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -302,9 +302,9 @@ trait Symbols extends api.Symbols { self: SymbolTable => def newClassConstructor(pos: Position): MethodSymbol = newConstructor(pos) setInfo MethodType(Nil, this.tpe) - def newLinkedModule(clazz: Symbol, newFlags: Long = 0L): ModuleSymbol = { - val m = newModuleSymbol(clazz.name.toTermName, clazz.pos, MODULE | newFlags) - connectModuleToClass(m, clazz.asInstanceOf[ClassSymbol]) + def newLinkedModule(moduleClass: Symbol, newFlags: Long = 0L): ModuleSymbol = { + val m = newModuleSymbol(moduleClass.name.toTermName, moduleClass.pos, MODULE | newFlags) + connectModuleToClass(m, moduleClass.asInstanceOf[ClassSymbol]) } final def newModule(name: TermName, pos: Position = NoPosition, newFlags0: Long = 0L): ModuleSymbol = { val newFlags = newFlags0 | MODULE @@ -655,7 +655,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => isClass && isFinal && loop(typeParams) } - final def isLazyAccessor = isLazy && lazyAccessor != NoSymbol final def isOverridableMember = !(isClass || isEffectivelyFinal) && safeOwner.isClass /** Does this symbol denote a wrapper created by the repl? */ @@ -1064,7 +1063,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => // parent LowPriorityImplicits. See comment in c5441dc for more elaboration. // Since the fix for SI-7335 Predef parents must be defined in Predef.scala, and we should not // get here anymore. - devWarning(s"calling Symbol#exists with sourcefile based symbol loader may give incorrect results."); + devWarning(s"calling Symbol#exists with sourcefile based symbol loader may give incorrect results.") } rawInfo load this @@ -1683,7 +1682,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * * - packageobjects (follows namer) * - superaccessors (follows typer) - * - lazyvals (follows erasure) + * - lambdaLift (follows erasure) * - null */ private def unsafeTypeParamPhase = { @@ -2075,11 +2074,11 @@ trait Symbols extends api.Symbols { self: SymbolTable => */ def alias: Symbol = NoSymbol - /** For a lazy value, its lazy accessor. NoSymbol for all others. */ + @deprecated("No longer applicable, as lazy vals are not desugared until the fields phase", "2.12.0") // used by scala-refactoring def lazyAccessor: Symbol = NoSymbol - /** If this is a lazy value, the lazy accessor; otherwise this symbol. */ - def lazyAccessorOrSelf: Symbol = if (isLazy) lazyAccessor else this + @deprecated("No longer applicable, as lazy vals are not desugared until the fields phase", "2.12.0") + def lazyAccessorOrSelf: Symbol = NoSymbol /** `accessed`, if this is an accessor that should have an underlying field. Otherwise, `this`. * Note that a "regular" accessor in a trait does not have a field, as an interface cannot define a field. @@ -2088,7 +2087,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * as they are an implementation detail that's irrelevant to type checking. */ def accessedOrSelf: Symbol = - if (hasAccessorFlag && (!owner.isTrait || hasFlag(PRESUPER | LAZY))) accessed + if (hasAccessorFlag && (!owner.isTrait || hasFlag(PRESUPER))) accessed else this /** For an outer accessor: The class from which the outer originates. @@ -2167,7 +2166,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => final def logicallyEnclosingMember: Symbol = if (isLocalDummy) enclClass.primaryConstructor else if (isMethod || isClass || this == NoSymbol) this - else if (this == NoSymbol) { devWarningDumpStack("NoSymbol.logicallyEnclosingMember", 15); this } else owner.logicallyEnclosingMember /** The top-level class containing this symbol. */ @@ -2225,7 +2223,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * to the class. As presently implemented this potentially returns class for * any symbol except NoSymbol. */ - def companionClass: Symbol = flatOwnerInfo.decl(name.toTypeName).suchThat(_ isCoDefinedWith this) + def companionClass: Symbol = flatOwnerInfo.decl(name.toTypeName).suchThat(d => d.isClass && d.isCoDefinedWith(this)) /** For a class: the module or case class factory with the same name in the same package. * For all others: NoSymbol @@ -2834,17 +2832,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => this } - def setLazyAccessor(sym: Symbol): TermSymbol = { - assert(isLazy && (referenced == NoSymbol || referenced == sym), (this, debugFlagString, referenced, sym)) - referenced = sym - this - } - - override def lazyAccessor: Symbol = { - assert(isLazy, this) - referenced - } - /** change name by appending $$<fully-qualified-name-of-class `base`> * Do the same for any accessed symbols or setters/getters */ @@ -2873,8 +2860,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => override def associatedFile_=(f: AbstractFile) { moduleClass.associatedFile = f } override def moduleClass = referenced - override def companionClass = - flatOwnerInfo.decl(name.toTypeName).suchThat(sym => sym.isClass && (sym isCoDefinedWith this)) override def owner = { if (Statistics.hotEnabled) Statistics.incCounter(ownerCount) diff --git a/src/reflect/scala/reflect/internal/TreeInfo.scala b/src/reflect/scala/reflect/internal/TreeInfo.scala index b9f3e987ee..61937958dd 100644 --- a/src/reflect/scala/reflect/internal/TreeInfo.scala +++ b/src/reflect/scala/reflect/internal/TreeInfo.scala @@ -293,6 +293,26 @@ abstract class TreeInfo { } } + + // No field for these vals, which means the ValDef carries the symbol of the getter (and not the field symbol) + // - abstract vals have no value we could store (until they become concrete, potentially) + // - lazy vals: the ValDef carries the symbol of the lazy accessor. + // The sausage factory will spew out the inner workings during the fields phase (actual bitmaps won't follow + // until lazyvals & mixins, though we should move this stuff from mixins to lazyvals now that fields takes care of mixing in lazy vals) + // - concrete vals in traits don't yield a field here either (their getter's RHS has the initial value) + // Constructors will move the assignment to the constructor, abstracting over the field using the field setter, + // and Fields will add a field to the class that mixes in the trait, implementing the accessors in terms of it + // + // The following case does receive a field symbol (until it's eliminated during the fields phase): + // - a concrete val with a statically known value (ConstantType) + // performs its side effect according to lazy/strict semantics, but doesn't need to store its value + // each access will "evaluate" the RHS (a literal) again + // + // We would like to avoid emitting unnecessary fields, but the required knowledge isn't available until after typer. + // The only way to avoid emitting & suppressing, is to not emit at all until we are sure to need the field, as dotty does. + def noFieldFor(vd: ValDef, owner: Symbol) = vd.mods.isDeferred || vd.mods.isLazy || (owner.isTrait && !vd.mods.hasFlag(PRESUPER)) + + def isDefaultGetter(tree: Tree) = { tree.symbol != null && tree.symbol.isDefaultGetter } diff --git a/src/reflect/scala/reflect/internal/TypeDebugging.scala b/src/reflect/scala/reflect/internal/TypeDebugging.scala index 63f897cd32..e9050b4e33 100644 --- a/src/reflect/scala/reflect/internal/TypeDebugging.scala +++ b/src/reflect/scala/reflect/internal/TypeDebugging.scala @@ -110,7 +110,7 @@ trait TypeDebugging { val hi_s = if (noPrint(hi)) "" else " <: " + ptTree(hi) lo_s + hi_s case _ if (t.symbol eq null) || (t.symbol eq NoSymbol) => to_s(t) - case _ => "" + t.symbol.tpe + case _ => "" + t.symbol.rawInfo.safeToString } def ptTypeParam(td: TypeDef): String = { val TypeDef(_, name, tparams, rhs) = td diff --git a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala index 4bc804445c..6dea184826 100644 --- a/src/reflect/scala/reflect/internal/pickling/UnPickler.scala +++ b/src/reflect/scala/reflect/internal/pickling/UnPickler.scala @@ -17,6 +17,7 @@ import PickleFormat._ import scala.collection.mutable import scala.collection.mutable.ListBuffer import scala.annotation.switch +import scala.util.control.NonFatal /** @author Martin Odersky * @version 1.0 @@ -29,25 +30,22 @@ abstract class UnPickler { * from an array of bytes. * @param bytes bytearray from which we unpickle * @param offset offset from which unpickling starts - * @param classRoot the top-level class which is unpickled, or NoSymbol if inapplicable - * @param moduleRoot the top-level module which is unpickled, or NoSymbol if inapplicable + * @param classRoot the top-level class which is unpickled + * @param moduleRoot the top-level module which is unpickled * @param filename filename associated with bytearray, only used for error messages */ - def unpickle(bytes: Array[Byte], offset: Int, classRoot: Symbol, moduleRoot: Symbol, filename: String) { + def unpickle(bytes: Array[Byte], offset: Int, classRoot: ClassSymbol, moduleRoot: ModuleSymbol, filename: String) { try { + assert(classRoot != NoSymbol && moduleRoot != NoSymbol, s"The Unpickler expects a class and module symbol: $classRoot - $moduleRoot") new Scan(bytes, offset, classRoot, moduleRoot, filename).run() } catch { - case ex: IOException => - throw ex - case ex: MissingRequirementError => - throw ex - case ex: Throwable => + case NonFatal(ex) => /*if (settings.debug.value)*/ ex.printStackTrace() throw new RuntimeException("error reading Scala signature of "+filename+": "+ex.getMessage()) } } - class Scan(_bytes: Array[Byte], offset: Int, classRoot: Symbol, moduleRoot: Symbol, filename: String) extends PickleBuffer(_bytes, offset, -1) { + class Scan(_bytes: Array[Byte], offset: Int, classRoot: ClassSymbol, moduleRoot: ModuleSymbol, filename: String) extends PickleBuffer(_bytes, offset, -1) { //println("unpickle " + classRoot + " and " + moduleRoot)//debug protected def debug = settings.debug.value @@ -227,9 +225,7 @@ abstract class UnPickler { return NoSymbol if (tag == EXTMODCLASSref) { - val moduleVar = owner.info.decl(nme.moduleVarName(name.toTermName)) - if (moduleVar.isLazyAccessor) - return moduleVar.lazyAccessor.lazyAccessor + owner.info.decl(nme.moduleVarName(name.toTermName)) } NoSymbol } @@ -295,10 +291,11 @@ abstract class UnPickler { case Right(sym) => sym -> readNat() } - def isModuleFlag = (flags & MODULE) != 0L - def isClassRoot = (name == classRoot.name) && (owner == classRoot.owner) - def isModuleRoot = (name == moduleRoot.name) && (owner == moduleRoot.owner) - def pflags = flags & PickledFlags + def isModuleFlag = (flags & MODULE) != 0L + def isClassRoot = (name == classRoot.name) && (owner == classRoot.owner) + def isModuleRoot = (name == moduleRoot.name) && (owner == moduleRoot.owner) + def isModuleClassRoot = (name == moduleRoot.name.toTypeName) && (owner == moduleRoot.owner) + def pflags = flags & PickledFlags def finishSym(sym: Symbol): Symbol = { /** @@ -343,22 +340,22 @@ abstract class UnPickler { finishSym(tag match { case TYPEsym | ALIASsym => owner.newNonClassSymbol(name.toTypeName, NoPosition, pflags) + case CLASSsym => - val sym = ( - if (isClassRoot) { - if (isModuleFlag) moduleRoot.moduleClass setFlag pflags - else classRoot setFlag pflags - } + val sym = { + if (isModuleFlag && isModuleClassRoot) moduleRoot.moduleClass setFlag pflags + else if (!isModuleFlag && isClassRoot) classRoot setFlag pflags else owner.newClassSymbol(name.toTypeName, NoPosition, pflags) - ) + } if (!atEnd) sym.typeOfThis = newLazyTypeRef(readNat()) - sym + case MODULEsym => - val clazz = at(inforef, () => readType()).typeSymbol // after NMT_TRANSITION, we can leave off the () => ... () + val moduleClass = at(inforef, () => readType()).typeSymbol // after NMT_TRANSITION, we can leave off the () => ... () if (isModuleRoot) moduleRoot setFlag pflags - else owner.newLinkedModule(clazz, pflags) + else owner.newLinkedModule(moduleClass, pflags) + case VALsym => if (isModuleRoot) { abort(s"VALsym at module root: owner = $owner, name = $name") } else owner.newTermSymbol(name.toTermName, NoPosition, pflags) diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 62ca50d035..e2f1e74740 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -147,21 +147,20 @@ trait Erasure { case AnnotatedType(_, atp) => apply(atp) case ClassInfoType(parents, decls, clazz) => - ClassInfoType( - if (clazz == ObjectClass || isPrimitiveValueClass(clazz) || parents.isEmpty) Nil + val newParents = + if (parents.isEmpty || clazz == ObjectClass || isPrimitiveValueClass(clazz)) Nil else if (clazz == ArrayClass) ObjectTpe :: Nil else { - val erasedParents = parents map this + val erasedParents = parents mapConserve this // drop first parent for traits -- it has been normalized to a class by now, // but we should drop that in bytecode - val firstParent = - if (clazz.hasFlag(Flags.TRAIT) && !clazz.hasFlag(Flags.JAVA)) ObjectTpe - else erasedParents.head - - firstParent :: erasedParents.tail.filter(_.typeSymbol != ObjectClass) - }, - decls, clazz) + if (clazz.hasFlag(Flags.TRAIT) && !clazz.hasFlag(Flags.JAVA)) + ObjectTpe :: erasedParents.tail.filter(_.typeSymbol != ObjectClass) + else erasedParents + } + if (newParents eq parents) tp + else ClassInfoType(newParents, decls, clazz) case _ => mapOver(tp) } @@ -343,23 +342,30 @@ trait Erasure { } } - /** The symbol's erased info. This is the type's erasure, except for the following symbols: - * - * - For $asInstanceOf : [T]T - * - For $isInstanceOf : [T]scala#Boolean - * - For class Array : [T]C where C is the erased classinfo of the Array class. - * - For Array[T].<init> : {scala#Int)Array[T] - * - For a type parameter : A type bounds type consisting of the erasures of its bounds. - */ + /** The symbol's erased info. This is the type's erasure, except for the following primitive symbols: + * + * - $asInstanceOf --> [T]T + * - $isInstanceOf --> [T]scala#Boolean + * - synchronized --> [T](x: T)T + * - class Array --> [T]C where C is the erased classinfo of the Array class. + * - Array[T].<init> --> {scala#Int)Array[T] + * + * An abstract type's info erases to a TypeBounds type consisting of the erasures of the abstract type's bounds. + */ def transformInfo(sym: Symbol, tp: Type): Type = { - if (sym == Object_asInstanceOf) + // Do not erase the primitive `synchronized` method's info or the info of its parameter. + // We do erase the info of its type param so that subtyping can relate its bounds after erasure. + def synchronizedPrimitive(sym: Symbol) = + sym == Object_synchronized || (sym.owner == Object_synchronized && sym.isTerm) + + if (sym == Object_asInstanceOf || synchronizedPrimitive(sym)) sym.info else if (sym == Object_isInstanceOf || sym == ArrayClass) PolyType(sym.info.typeParams, specialErasure(sym)(sym.info.resultType)) else if (sym.isAbstractType) - TypeBounds(WildcardType, WildcardType) + TypeBounds(WildcardType, WildcardType) // TODO why not use the erasure of the type's bounds, as stated in the doc? else if (sym.isTerm && sym.owner == ArrayClass) { - if (sym.isClassConstructor) + if (sym.isClassConstructor) // TODO: switch on name for all branches -- this one is sym.name == nme.CONSTRUCTOR tp match { case MethodType(params, TypeRef(pre, sym1, args)) => MethodType(cloneSymbolsAndModify(params, specialErasure(sym)), @@ -376,12 +382,14 @@ trait Erasure { } else if ( sym.owner != NoSymbol && sym.owner.owner == ArrayClass && - sym == Array_update.paramss.head(1)) { + sym == Array_update.paramss.head(1)) { // TODO: can we simplify the guard, perhaps cache the symbol to compare to? // special case for Array.update: the non-erased type remains, i.e. (Int,A)Unit // since the erasure type map gets applied to every symbol, we have to catch the // symbol here tp } else { + // TODO OPT: altogether, there are 9 symbols that we special-case. + // Could we get to the common case more quickly by looking them up in a set? specialErasure(sym)(tp) } } diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 9b0d66f41c..95440ebc00 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -578,7 +578,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive * @param jclazz The Java class which contains the unpickled information in a * ScalaSignature or ScalaLongSignature annotation. */ - def unpickleClass(clazz: Symbol, module: Symbol, jclazz: jClass[_]): Unit = { + def unpickleClass(clazz: ClassSymbol, module: ModuleSymbol, jclazz: jClass[_]): Unit = { def markAbsent(tpe: Type) = setAllInfos(clazz, module, tpe) def handleError(ex: Exception) = { markAbsent(ErrorType) diff --git a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala index f55b33959a..b74ccb9177 100644 --- a/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala +++ b/src/reflect/scala/reflect/runtime/JavaUniverseForce.scala @@ -379,7 +379,6 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ElidableMethodClass definitions.ImplicitNotFoundClass definitions.ImplicitAmbiguousClass - definitions.JUnitTestClass definitions.MigrationAnnotationClass definitions.ScalaStrictFPAttr definitions.SwitchClass @@ -417,6 +416,7 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.ClassTargetClass definitions.MethodTargetClass definitions.LanguageFeatureAnnot + definitions.JUnitAnnotations definitions.languageFeatureModule definitions.metaAnnotations definitions.AnnotationDefaultAttr @@ -432,6 +432,9 @@ trait JavaUniverseForce { self: runtime.JavaUniverse => definitions.boxedClass definitions.refClass definitions.volatileRefClass + definitions.lazyHolders + definitions.LazyRefClass + definitions.LazyUnitClass definitions.allRefClasses definitions.UnitClass definitions.ByteClass diff --git a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala index 768a3d5ce5..3f2864ee7b 100644 --- a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala +++ b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala @@ -14,7 +14,7 @@ private[reflect] trait SymbolLoaders { self: SymbolTable => * by unpickling information from the corresponding Java class. If no Java class * is found, a package is created instead. */ - class TopClassCompleter(clazz: Symbol, module: Symbol) extends SymLoader with FlagAssigningCompleter { + class TopClassCompleter(clazz: ClassSymbol, module: ModuleSymbol) extends SymLoader with FlagAssigningCompleter { markFlagsCompleted(clazz, module)(mask = ~TopLevelPickledFlags) override def complete(sym: Symbol) = { debugInfo("completing "+sym+"/"+clazz.fullName) @@ -36,7 +36,7 @@ private[reflect] trait SymbolLoaders { self: SymbolTable => * @param name The simple name of the newly created class * @param completer The completer to be used to set the info of the class and the module */ - protected def initAndEnterClassAndModule(owner: Symbol, name: TypeName, completer: (Symbol, Symbol) => LazyType) = { + protected def initAndEnterClassAndModule(owner: Symbol, name: TypeName, completer: (ClassSymbol, ModuleSymbol) => LazyType) = { assert(!(name.toString endsWith "[]"), name) val clazz = owner.newClass(name) val module = owner.newModule(name.toTermName) diff --git a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala index 95964e18d9..912ac26329 100644 --- a/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala +++ b/src/repl-jline/scala/tools/nsc/interpreter/jline/JLineReader.scala @@ -32,11 +32,14 @@ class InteractiveReader(completer: () => Completion) extends interpreter.Interac private val consoleReader = { val reader = new JLineConsoleReader() - reader setPaginationEnabled interpreter.`package`.isPaged + reader setPaginationEnabled interpreter.isPaged - // ASAP + // turn off magic ! reader setExpandEvents false + // enable detecting pasted tab char (when next char is immediately available) which is taken raw, not completion + reader setCopyPasteDetection true + reader setHistory history.asInstanceOf[JHistory] reader @@ -91,11 +94,19 @@ private class JLineConsoleReader extends jconsole.ConsoleReader with interpreter printColumns_(items: List[String]) } + // Workaround for JLine weirdness. (See https://github.com/scala/scala-dev/issues/240) + // Emit control characters as-is, instead of representing them as e.g. "^J" (for '\n'). + // `rawPrint` is package protected in jline.console.ConsoleReader, while `rawPrintln` is private + // Copy/paste part of it as `_rawPrint` (to avoid name clash); + // the super class impl also sets `cursorOk`, but that's out of reach for us. + private def _rawPrint(str: String) = getOutput.write(str) + private def rawPrintln(str: String) = { _rawPrint(str); println() } + private def printColumns_(items: List[String]): Unit = if (items exists (_ != "")) { val grouped = tabulate(items) var linesLeft = if (isPaginationEnabled()) height - 1 else Int.MaxValue grouped foreach { xs => - println(xs.mkString) + rawPrintln(xs.mkString) linesLeft -= 1 if (linesLeft <= 0) { linesLeft = emulateMore() @@ -106,7 +117,7 @@ private class JLineConsoleReader extends jconsole.ConsoleReader with interpreter } def readOneKey(prompt: String) = { - this.print(prompt) + _rawPrint(prompt) this.flush() this.readCharacter() } diff --git a/src/repl/scala/tools/nsc/MainGenericRunner.scala b/src/repl/scala/tools/nsc/MainGenericRunner.scala index a09e797e07..894157ff6c 100644 --- a/src/repl/scala/tools/nsc/MainGenericRunner.scala +++ b/src/repl/scala/tools/nsc/MainGenericRunner.scala @@ -49,10 +49,6 @@ class MainGenericRunner { def isI = !settings.loadfiles.isDefault def dashi = settings.loadfiles.value - // Deadlocks on startup under -i unless we disable async. - if (isI) - settings.Yreplsync.value = true - def combinedCode = { val files = if (isI) dashi map (file => File(file).slurp()) else Nil val str = if (isE) List(dashe) else Nil @@ -98,7 +94,7 @@ class MainGenericRunner { if (!command.ok) errorFn(f"%n$shortUsageMsg") else if (shouldStopWithInfo) - errorFn(command getInfoMessage sampleCompiler, isFailure = false) + errorFn(command.getInfoMessage(sampleCompiler), isFailure = false) else run() } diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 44784aa953..65f2c95f73 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -1203,7 +1203,7 @@ class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends /** Utility methods for the Interpreter. */ object IMain { - /** Dummy identifier fragement inserted at the cursor before presentation compilation. Needed to support completion of `global.def<TAB>` */ + /** Dummy identifier fragment inserted at the cursor before presentation compilation. Needed to support completion of `global.def<TAB>` */ val DummyCursorFragment = "_CURSOR_" // The two name forms this is catching are the two sides of this assignment: diff --git a/src/repl/scala/tools/nsc/interpreter/Phased.scala b/src/repl/scala/tools/nsc/interpreter/Phased.scala index d1d422ce3e..dd327a13d4 100644 --- a/src/repl/scala/tools/nsc/interpreter/Phased.scala +++ b/src/repl/scala/tools/nsc/interpreter/Phased.scala @@ -88,7 +88,7 @@ trait Phased { lazy val all = List( Parser, Namer, Packageobjects, Typer, Superaccessors, Pickler, Refchecks, - Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Lazyvals, Lambdalift, + Uncurry, Tailcalls, Specialize, Explicitouter, Erasure, Fields, Lambdalift, Constructors, Flatten, Mixin, Cleanup, Delambdafy, Jvm, Terminal ) lazy val nameMap = all.map(x => x.name -> x).toMap withDefaultValue NoPhaseName @@ -114,12 +114,12 @@ trait Phased { case object Pickler extends PhaseName case object Refchecks extends PhaseName case object Uncurry extends PhaseName + case object Fields extends PhaseName case object Tailcalls extends PhaseName case object Specialize extends PhaseName case object Explicitouter extends PhaseName case object Erasure extends PhaseName case object PostErasure extends PhaseName - case object Lazyvals extends PhaseName case object Lambdalift extends PhaseName case object Constructors extends PhaseName case object Flatten extends PhaseName diff --git a/src/scaladoc/scala/tools/nsc/doc/Settings.scala b/src/scaladoc/scala/tools/nsc/doc/Settings.scala index 063a949323..fbb2dd9f87 100644 --- a/src/scaladoc/scala/tools/nsc/doc/Settings.scala +++ b/src/scaladoc/scala/tools/nsc/doc/Settings.scala @@ -22,7 +22,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) val docformat = ChoiceSetting ( "-doc-format", "format", - "Selects in which format documentation is rendered", + "Selects in which format documentation is rendered.", List("html"), "html" ) diff --git a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala index 928cb34d30..fb9a5ce7eb 100644 --- a/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/scaladoc/scala/tools/nsc/doc/model/ModelFactory.scala @@ -106,10 +106,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // in the doc comment of MyClass def linkTarget: DocTemplateImpl = inTpl - lazy val comment = { - val documented = if (sym.hasAccessorFlag) sym.accessed else sym - thisFactory.comment(documented, linkTarget, inTpl) - } + // if there is a field symbol, the ValDef will use it, which means docs attached to it will be under the field symbol, not the getter's + protected[this] def commentCarryingSymbol(sym: Symbol) = + if (sym.hasAccessorFlag && sym.accessed.exists) sym.accessed else sym + + lazy val comment = thisFactory.comment(commentCarryingSymbol(sym), linkTarget, inTpl) + def group = comment flatMap (_.group) getOrElse defaultGroup override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot @@ -476,17 +478,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override lazy val comment = { def nonRootTemplate(sym: Symbol): Option[DocTemplateImpl] = if (sym eq RootPackage) None else findTemplateMaybe(sym) + /* Variable precedence order for implicitly added members: Take the variable definitions from ... * 1. the target of the implicit conversion * 2. the definition template (owner) * 3. the current template */ - val inRealTpl = conversion.flatMap { conv => - nonRootTemplate(conv.toType.typeSymbol) - } orElse nonRootTemplate(sym.owner) orElse Option(inTpl) - inRealTpl flatMap { tpl => - thisFactory.comment(sym, tpl, tpl) - } + val inRealTpl = ( + conversion.flatMap(conv => nonRootTemplate(conv.toType.typeSymbol)) + orElse nonRootTemplate(sym.owner) + orElse Option(inTpl)) + + inRealTpl flatMap (tpl => thisFactory.comment(commentCarryingSymbol(sym), tpl, tpl)) } override def inDefinitionTemplates = useCaseOf.fold(super.inDefinitionTemplates)(_.inDefinitionTemplates) |