Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
pkskpro committed Nov 15, 2024
2 parents 4b75069 + 6a7d5d3 commit d7487c2
Show file tree
Hide file tree
Showing 19 changed files with 174 additions and 43 deletions.
8 changes: 8 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
Closure(tree: Tree)(env, meth, tpt)
}

// This is a more fault-tolerant copier that does not cause errors when
// function types in applications are undefined.
// This was called `Inliner.InlineCopier` before 3.6.3.
class ConservativeTreeCopier() extends TypedTreeCopier:
override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply =
if fun.tpe.widen.exists then super.Apply(tree)(fun, args)
else untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe)

override def skipTransform(tree: Tree)(using Context): Boolean = tree.tpe.isError

implicit class TreeOps[ThisTree <: tpd.Tree](private val tree: ThisTree) extends AnyVal {
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import reporting.{trace, Message, OverrideError}
object CheckCaptures:
import ast.tpd.*

val name: String = "cc"
val description: String = "capture checking"

enum EnvKind:
case Regular // normal case
case NestedInOwner // environment is a temporary one nested in the owner's environment,
Expand Down Expand Up @@ -192,7 +195,9 @@ class CheckCaptures extends Recheck, SymTransformer:
import ast.tpd.*
import CheckCaptures.*

def phaseName: String = "cc"
override def phaseName: String = CheckCaptures.name

override def description: String = CheckCaptures.description

override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere

Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ trait SetupAPI:

object Setup:

val name: String = "ccSetup"
val description: String = "prepare compilation unit for capture checking"

/** Recognizer for `res $throws exc`, returning `(res, exc)` in case of success */
object throwsAlias:
def unapply(tp: Type)(using Context): Option[(Type, Type)] = tp match
Expand All @@ -53,6 +56,10 @@ import Setup.*
class Setup extends PreRecheck, SymTransformer, SetupAPI:
thisPhase =>

override def phaseName: String = Setup.name

override def description: String = Setup.description

override def isRunnable(using Context) =
super.isRunnable && Feature.ccEnabledSomewhere

Expand Down
9 changes: 8 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6291,7 +6291,14 @@ object Types extends TypeUtils {
}
}

private def treeTypeMap = new TreeTypeMap(typeMap = this)
private def treeTypeMap = new TreeTypeMap(
typeMap = this,
// Using `ConservativeTreeCopier` is needed when copying dependent annoted
// types, where we can refer to a previous parameter represented as
// `TermParamRef` that has no underlying type yet.
// See tests/pos/annot-17242.scala.
cpy = ConservativeTreeCopier()
)

def mapOver(syms: List[Symbol]): List[Symbol] = mapSymbols(syms, treeTypeMap)

Expand Down
17 changes: 7 additions & 10 deletions compiler/src/dotty/tools/dotc/inlines/Inliner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,6 @@ object Inliner:
}
end isElideableExpr

// InlineCopier is a more fault-tolerant copier that does not cause errors when
// function types in applications are undefined. This is necessary since we copy at
// the same time as establishing the proper context in which the copied tree should
// be evaluated. This matters for opaque types, see neg/i14653.scala.
private class InlineCopier() extends TypedTreeCopier:
override def Apply(tree: Tree)(fun: Tree, args: List[Tree])(using Context): Apply =
if fun.tpe.widen.exists then super.Apply(tree)(fun, args)
else untpd.cpy.Apply(tree)(fun, args).withTypeUnchecked(tree.tpe)

// InlinerMap is a TreeTypeMap with special treatment for inlined arguments:
// They are generally left alone (not mapped further, and if they wrap a type
// the type Inlined wrapper gets dropped
Expand All @@ -116,7 +107,13 @@ object Inliner:
substFrom: List[Symbol],
substTo: List[Symbol])(using Context)
extends TreeTypeMap(
typeMap, treeMap, oldOwners, newOwners, substFrom, substTo, InlineCopier()):
typeMap, treeMap, oldOwners, newOwners, substFrom, substTo,
// It is necessary to use the `ConservativeTreeCopier` since we copy at
// the same time as establishing the proper context in which the copied
// tree should be evaluated. This matters for opaque types, see
// neg/i14653.scala.
ConservativeTreeCopier()
):

override def copy(
typeMap: Type => Type,
Expand Down
30 changes: 15 additions & 15 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -806,20 +806,20 @@ object Checking {
*
*/
def checkAndAdaptExperimentalImports(trees: List[Tree])(using Context): Unit =
def nonExperimentalTopLevelDefs(pack: Symbol): Iterator[Symbol] =
def isNonExperimentalTopLevelDefinition(sym: Symbol) =
sym.isDefinedInCurrentRun
&& sym.source == ctx.compilationUnit.source
&& !sym.isConstructor // not constructor of package object
&& !sym.is(Package) && !sym.name.isPackageObjectName
&& !sym.isExperimental

pack.info.decls.toList.iterator.flatMap: sym =>
if sym.isClass && (sym.is(Package) || sym.isPackageObject) then
nonExperimentalTopLevelDefs(sym)
else if isNonExperimentalTopLevelDefinition(sym) then
sym :: Nil
else Nil
def nonExperimentalTopLevelDefs(): List[Symbol] =
new TreeAccumulator[List[Symbol]] {
override def apply(x: List[Symbol], tree: tpd.Tree)(using Context): List[Symbol] =
def addIfNotExperimental(sym: Symbol) =
if !sym.isExperimental then sym :: x
else x
tree match {
case tpd.PackageDef(_, contents) => apply(x, contents)
case typeDef @ tpd.TypeDef(_, temp: Template) if typeDef.symbol.isPackageObject =>
apply(x, temp.body)
case mdef: tpd.MemberDef => addIfNotExperimental(mdef.symbol)
case _ => x
}
}.apply(Nil, ctx.compilationUnit.tpdTree)

def unitExperimentalLanguageImports =
def isAllowedImport(sel: untpd.ImportSelector) =
Expand All @@ -837,7 +837,7 @@ object Checking {

if ctx.owner.is(Package) || ctx.owner.name.startsWith(str.REPL_SESSION_LINE) then
def markTopLevelDefsAsExperimental(why: String): Unit =
for sym <- nonExperimentalTopLevelDefs(ctx.owner) do
for sym <- nonExperimentalTopLevelDefs() do
sym.addAnnotation(ExperimentalAnnotation(s"Added by $why", sym.span))

unitExperimentalLanguageImports match
Expand Down
9 changes: 9 additions & 0 deletions sbt-test/sbt-dotty/scaladoc-regressions/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ThisBuild / scalaVersion := sys.props("plugin.scalaVersion")

lazy val i20476 = project
.in(file("i20476"))
.enablePlugins(ScalaJSPlugin)

lazy val i18231 = project
.in(file("i18231"))
.settings(scalacOptions += "-release:8")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Foo {
@Deprecated
def foo(): Unit = ???
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package demo

import scala.scalajs.js

def bar: js.Promise[Int] = js.Promise.resolve(()).`then`(_ => 1)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % sys.props("plugin.scalaJSVersion"))
2 changes: 2 additions & 0 deletions sbt-test/sbt-dotty/scaladoc-regressions/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
> i18231/doc
> i20476/doc
4 changes: 2 additions & 2 deletions scaladoc/src/dotty/tools/scaladoc/tasty/TastyParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package tasty
import java.util.regex.Pattern

import scala.util.{Try, Success, Failure}
import scala.tasty.inspector.{TastyInspector, Inspector, Tasty}
import scala.tasty.inspector.{ScaladocInternalTastyInspector, Inspector, Tasty}
import scala.quoted._

import dotty.tools.dotc
Expand Down Expand Up @@ -160,7 +160,7 @@ object ScaladocTastyInspector:
report.error("File extension is not `tasty` or `jar`: " + invalidPath)

if tastyPaths.nonEmpty then
TastyInspector.inspectAllTastyFiles(tastyPaths, jarPaths, classpath)(inspector)
ScaladocInternalTastyInspector.inspectAllTastyFilesInContext(tastyPaths, jarPaths, classpath)(inspector)(using ctx.compilerContext)

val all = inspector.topLevels.result()
all.groupBy(_._1).map { case (pckName, members) =>
Expand Down
43 changes: 32 additions & 11 deletions scaladoc/src/scala/tasty/inspector/TastyInspector.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copy of tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala
// Renamed copy of tasty-inspector/src/scala/tasty/inspector/TastyInspector.scala
// FIXME remove this copy of the file
// Since copying, an inspectAllTastyFilesInContext method was added for scaladoc only
// to fix regressions introduced by the switch from old to a new TastyInspector

package scala.tasty.inspector

Expand All @@ -21,7 +23,7 @@ import dotty.tools.dotc.report

import java.io.File.pathSeparator

object TastyInspector:
object ScaladocInternalTastyInspector:

/** Load and process TASTy files using TASTy reflect
*
Expand All @@ -41,6 +43,32 @@ object TastyInspector:
def inspectTastyFilesInJar(jar: String)(inspector: Inspector): Boolean =
inspectAllTastyFiles(Nil, List(jar), Nil)(inspector)

private def checkFiles(tastyFiles: List[String], jars: List[String]): Unit =
def checkFile(fileName: String, ext: String): Unit =
val file = dotty.tools.io.Path(fileName)
if !file.ext.toLowerCase.equalsIgnoreCase(ext) then
throw new IllegalArgumentException(s"File extension is not `.$ext`: $file")
else if !file.exists then
throw new IllegalArgumentException(s"File not found: ${file.toAbsolute}")
tastyFiles.foreach(checkFile(_, "tasty"))
jars.foreach(checkFile(_, "jar"))

/**
* Added for Scaladoc-only.
* Meant to fix regressions introduces by the switch from old to new TastyInspector:
* https://github.com/scala/scala3/issues/18231
* https://github.com/scala/scala3/issues/20476
* Stable TastyInspector API does not support passing compiler context.
*/
def inspectAllTastyFilesInContext(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector)(using Context): Boolean =
checkFiles(tastyFiles, jars)
val classes = tastyFiles ::: jars
classes match
case Nil => true
case _ =>
val reporter = inspectorDriver(inspector).process(inspectorArgs(dependenciesClasspath, classes), summon[Context])
!reporter.hasErrors

/** Load and process TASTy files using TASTy reflect
*
* @param tastyFiles List of paths of `.tasty` files
Expand All @@ -50,14 +78,7 @@ object TastyInspector:
* @return boolean value indicating whether the process succeeded
*/
def inspectAllTastyFiles(tastyFiles: List[String], jars: List[String], dependenciesClasspath: List[String])(inspector: Inspector): Boolean =
def checkFile(fileName: String, ext: String): Unit =
val file = dotty.tools.io.Path(fileName)
if !file.ext.toLowerCase.equalsIgnoreCase(ext) then
throw new IllegalArgumentException(s"File extension is not `.$ext`: $file")
else if !file.exists then
throw new IllegalArgumentException(s"File not found: ${file.toAbsolute}")
tastyFiles.foreach(checkFile(_, "tasty"))
jars.foreach(checkFile(_, "jar"))
checkFiles(tastyFiles, jars)
val files = tastyFiles ::: jars
inspectFiles(dependenciesClasspath, files)(inspector)

Expand Down Expand Up @@ -124,4 +145,4 @@ object TastyInspector:
end inspectFiles


end TastyInspector
end ScaladocInternalTastyInspector
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ class Scaladoc3ExternalLocationProviderIntegrationTest extends ExternalLocationP

def getScalaLibraryPath: String = {
val classpath: List[String] = System.getProperty("java.class.path").split(java.io.File.pathSeparatorChar).toList
val stdlib = classpath.find(_.contains("scala-library-2")).getOrElse("foobarbazz") // If we don't find the scala 2 library, the test will fail
new java.io.File(stdlib).getCanonicalPath() // canonicalize for case-insensitive file systems
// For an unclear reason, depending on if we pass the compiler context onto the tasty inspector
// the scala-2-library path needs to have its characters case fixed with new java.io.File(stdlib).getCanonicalPath()
classpath.find(_.contains("scala-library-2")).getOrElse("foobarbazz") // If we don't find the scala 2 library, the test will fail
}

class Scaladoc2LegacyExternalLocationProviderIntegrationTest extends LegacyExternalLocationProviderIntegrationTest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class LinkWarningsTest extends ScaladocTest("noLinkWarnings"):

override def runTest = afterRendering {
val diagnostics = summon[DocContext].compilerContext.reportedDiagnostics
assertEquals("There should be exactly one warning", 1, diagnostics.warningMsgs.size)
val filteredWarnings = diagnostics.warningMsgs.filter(_ != "1 warning found")
assertEquals("There should be exactly one warning", 1, filteredWarnings.size)
assertNoErrors(diagnostics)
}
15 changes: 15 additions & 0 deletions tests/pos-macros/i21802/Macro.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class MetricsGroup[A]
object MetricsGroup:
import scala.quoted.*

transparent inline final def refine[A]: MetricsGroup[A] =
${ refineImpl[A] }

private def refineImpl[A](using qctx: Quotes, tpe: Type[A]): Expr[MetricsGroup[A]] =
import qctx.reflect.*

val mt = MethodType(Nil)(_ => Nil, _ => TypeRepr.of[A])
val tpe = Refinement(TypeRepr.of[MetricsGroup[A]], "apply", mt).asType
tpe match
case '[tpe] =>
'{ MetricsGroup[A]().asInstanceOf[MetricsGroup[A] & tpe] }
13 changes: 13 additions & 0 deletions tests/pos-macros/i21802/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//> using options -experimental -Ydebug

class ProbeFailedException(cause: Exception) extends Exception(cause)
trait Probing:
self: Metrics =>
val probeFailureCounter: MetricsGroup[Counter] =
counters("ustats_probe_failures_count").labelled


trait Counter
class Metrics:
class counters(name: String):
transparent inline final def labelled: MetricsGroup[Counter] = MetricsGroup.refine[Counter]
5 changes: 5 additions & 0 deletions tests/pos/annot-17242.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// See also tests/pos/annot-21595.scala

class local(predicate: Int) extends annotation.StaticAnnotation

def failing1(x: Int, z: Int @local(x + x)) = ()
30 changes: 30 additions & 0 deletions tests/pos/dependent-annot2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class dummy(b: Any) extends annotation.StaticAnnotation

class X:
def foo() = 1
def bar() = 2
def eq(x: X) = true
def id(): this.type = this

class Y extends X:
override def bar() = 2
override def eq(x: X) = true

def f(x: Int) = x
def g(x: String) = x
def g(x: Int) = x

object AnnotationTests:
def foo1(elem: Int, bla: Int @dummy(Array(elem))) = bla
def foo2(elem: X, bla: Int @dummy(elem.foo())) = bla
def foo3(elem: Y, bla: Int @dummy(elem.foo())) = bla
def foo4(elem: X, bla: Int @dummy(elem.bar())) = bla
def foo5(elem: Y, bla: Int @dummy(elem.bar())) = bla
def foo6(elem: X, bla: Int @dummy(elem.eq(X()))) = bla
def foo7(elem: Y, bla: Int @dummy(elem.eq(Y()))) = bla
def foo8(elem: X, bla: Int @dummy(elem.id().foo())) = bla
def foo9(elem: Y, bla: Int @dummy(elem.id().foo())) = bla
def foo10(elem: Int, bla: Int @dummy(f(elem))) = bla
def foo11(elem: Int, bla: Int @dummy(g(elem))) = bla
def foo12(elem: Int, bla: Int @dummy(0 == elem)) = bla
def foo13(elem: Int, bla: Int @dummy(elem == 0)) = bla

0 comments on commit d7487c2

Please sign in to comment.