Skip to content

Backport "Simplify avoidance of local types of Hole" to LTS #18939

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1513,16 +1513,29 @@ object SymDenotations {
* See tests/pos/i10769.scala
*/
def reachableTypeRef(using Context) =
TypeRef(owner.reachableThisType, symbol)
TypeRef(owner.reachablePrefix, symbol)

/** Like termRef, but objects in the prefix are represented by their singleton type,
/** The reachable typeRef with wildcard arguments for each type parameter */
def reachableRawTypeRef(using Context) =
reachableTypeRef.appliedTo(typeParams.map(_ => TypeBounds.emptyPolyKind))

/** Like termRef, if it is addressable from the current context,
* but objects in the prefix are represented by their singleton type,
* this means we output `pre.O.member` rather than `pre.O$.this.member`.
*
* This is required to avoid owner crash in ExplicitOuter.
* See tests/pos/i10769.scala
*
* If the reference is to an object that is not accessible from the
* current context since the object is nested in a class that is not an outer
* class of the current context, fall back to a TypeRef to the module class.
* Test case is tests/pos/i17556.scala.
* If the reference is to some other inaccessible object, throw an AssertionError.
*/
def reachableTermRef(using Context) =
TermRef(owner.reachableThisType, symbol)
def reachableTermRef(using Context): Type = owner.reachablePrefix match
case pre: SingletonType => TermRef(pre, symbol)
case pre if symbol.is(ModuleVal) => TypeRef(pre, symbol.moduleClass)
case _ => throw AssertionError(i"cannot compute path to TermRef $this from ${ctx.owner}")

/** Like thisType, but objects in the type are represented by their singleton type,
* this means we output `pre.O.member` rather than `pre.O$.this.member`.
Expand All @@ -1537,6 +1550,18 @@ object SymDenotations {
else
ThisType.raw(TypeRef(owner.reachableThisType, symbol.asType))

/** Like `reachableThisType`, except if that would refer to a class where
* the `this` cannot be accessed. In that case, fall back to the
* rawTypeRef of the class. E.g. instead of `A.this.X` where `A.this`
* is inaccessible, use `A#X`.
*/
def reachablePrefix(using Context): Type = reachableThisType match
case pre: ThisType
if !pre.cls.isStaticOwner && !ctx.owner.isContainedIn(pre.cls) =>
pre.cls.reachableRawTypeRef
case pre =>
pre

/** The variance of this type parameter or type member as a subset of
* {Covariant, Contravariant}
*/
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3037,7 +3037,8 @@ object Types {
abstract case class SuperType(thistpe: Type, supertpe: Type) extends CachedProxyType with SingletonType {
override def underlying(using Context): Type = supertpe
override def superType(using Context): Type =
thistpe.baseType(supertpe.typeSymbol)
if supertpe.typeSymbol.exists then thistpe.baseType(supertpe.typeSymbol)
else super.superType
def derivedSuperType(thistpe: Type, supertpe: Type)(using Context): Type =
if ((thistpe eq this.thistpe) && (supertpe eq this.supertpe)) this
else SuperType(thistpe, supertpe)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,9 @@ class ClassfileParser(
return unpickleTASTY(tastyBytes)
}
}
else return unpickleTASTY(bytes)
else
// Before 3.0.0 we had a mode where we could embed the TASTY bytes in the classfile. This has not been supported in any stable release.
report.error(s"Found a TASTY attribute with a length different from 16 in $classfile. This is likely a bug in the compiler. Please report.", NoSourcePosition)
}

if scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1468,8 +1468,8 @@ object Parsers {
* PolyFunType ::= HKTypeParamClause '=>' Type
* | HKTypeParamClause ‘->’ [CaptureSet] Type -- under pureFunctions
* FunTypeArgs ::= InfixType
* | `(' [ [ ‘[using]’ ‘['erased'] FunArgType {`,' FunArgType } ] `)'
* | '(' [ ‘[using]’ ‘['erased'] TypedFunParam {',' TypedFunParam } ')'
* | `(' [ [ ‘['erased'] FunArgType {`,' FunArgType } ] `)'
* | '(' [ ‘['erased'] TypedFunParam {',' TypedFunParam } ')'
*/
def typ(): Tree =
val start = in.offset
Expand Down
48 changes: 12 additions & 36 deletions compiler/src/dotty/tools/dotc/transform/PickleQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.untpd
import dotty.tools.dotc.config.ScalaRelease.*

import scala.collection.mutable
import dotty.tools.dotc.core.Annotations._
import dotty.tools.dotc.core.StdNames._
import dotty.tools.dotc.quoted._
import dotty.tools.dotc.inlines.Inlines

import scala.annotation.constructorOnly
import scala.collection.mutable

/** Translates quoted terms and types to `unpickleExprV2` or `unpickleType` method calls.
*
Expand Down Expand Up @@ -106,16 +106,19 @@ class PickleQuotes extends MacroTransform {
private def extractHolesContents(quote: tpd.Quote)(using Context): (List[Tree], tpd.Quote) =
class HoleContentExtractor extends Transformer:
private val holeContents = List.newBuilder[Tree]
private val stagedClasses = mutable.HashSet.empty[Symbol]
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =
tree match
case tree @ Hole(isTerm, _, _, content) =>
assert(isTerm)
assert(!content.isEmpty)
holeContents += content
val holeType = getTermHoleType(tree.tpe)
val holeType = getPicklableHoleType(tree.tpe, stagedClasses)
val hole = untpd.cpy.Hole(tree)(content = EmptyTree).withType(holeType)
cpy.Inlined(tree)(EmptyTree, Nil, hole)
case tree: DefTree =>
if tree.symbol.isClass then
stagedClasses += tree.symbol
val newAnnotations = tree.symbol.annotations.mapconserve { annot =>
annot.derivedAnnotation(transform(annot.tree)(using ctx.withOwner(tree.symbol)))
}
Expand All @@ -134,19 +137,6 @@ class PickleQuotes extends MacroTransform {
}
}

/** Remove references to local types that will not be defined in this quote */
private def getTermHoleType(using Context) = new TypeMap() {
override def apply(tp: Type): Type = tp match
case tp @ TypeRef(NoPrefix, _) =>
// reference to term with a type defined in outer quote
getTypeHoleType(tp)
case tp @ TermRef(NoPrefix, _) =>
// widen term refs to terms defined in outer quote
apply(tp.widenTermRefExpr)
case tp =>
mapOver(tp)
}

/** Get the holeContents of the transformed tree */
def getContents() =
val res = holeContents.result
Expand Down Expand Up @@ -196,11 +186,11 @@ class PickleQuotes extends MacroTransform {
cpy.Quote(quote)(Block(tdefs, body1), quote.tags)

private def mkTagSymbolAndAssignType(typeArg: Tree, idx: Int)(using Context): TypeDef = {
val holeType = getTypeHoleType(typeArg.tpe.select(tpnme.Underlying))
val holeType = getPicklableHoleType(typeArg.tpe.select(tpnme.Underlying), _ => false)
val hole = untpd.cpy.Hole(typeArg)(isTerm = false, idx, Nil, EmptyTree).withType(holeType)
val local = newSymbol(
owner = ctx.owner,
name = UniqueName.fresh(hole.tpe.dealias.typeSymbol.name.toTypeName),
name = UniqueName.fresh(typeArg.symbol.name.toTypeName),
flags = Synthetic,
info = TypeAlias(typeArg.tpe.select(tpnme.Underlying)),
coord = typeArg.span
Expand All @@ -209,25 +199,11 @@ class PickleQuotes extends MacroTransform {
ctx.typeAssigner.assignType(untpd.TypeDef(local.name, hole), local).withSpan(typeArg.span)
}

/** Remove references to local types that will not be defined in this quote */
private def getTypeHoleType(using Context) = new TypeMap() {
override def apply(tp: Type): Type = tp match
case tp: TypeRef if tp.typeSymbol.isTypeSplice =>
apply(tp.dealias)
case tp @ TypeRef(pre, _) if isLocalPath(pre) =>
val hiBound = tp.typeSymbol.info match
case info: ClassInfo => info.parents.reduce(_ & _)
case info => info.hiBound
apply(hiBound)
case tp =>
mapOver(tp)

private def isLocalPath(tp: Type): Boolean = tp match
case NoPrefix => true
case tp: TermRef if !tp.symbol.is(Package) => isLocalPath(tp.prefix)
case tp => false
}

/** Avoid all non-static types except those defined in the quote. */
private def getPicklableHoleType(tpe: Type, isStagedClasses: Symbol => Boolean)(using Context) =
new TypeOps.AvoidMap {
def toAvoid(tp: NamedType) = !isStagedClasses(tp.typeSymbol) && !isStaticPrefix(tp)
}.apply(tpe)
}

object PickleQuotes {
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,6 @@ object SymUtils:
else owner.isLocal
}

/** The reachable typeRef with wildcard arguments for each type parameter */
def reachableRawTypeRef(using Context) =
self.reachableTypeRef.appliedTo(self.typeParams.map(_ => TypeBounds.emptyPolyKind))

/** Is symbol a type splice operation? */
def isTypeSplice(using Context): Boolean =
self == defn.QuotedType_splice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
.map((pre, child) => rawRef(child).asSeenFrom(pre, child.owner))
case _ =>
cls.children.map(rawRef)
end computeChildTypes

val childTypes = computeChildTypes
val cases =
for (patType, idx) <- childTypes.zipWithIndex yield
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,10 @@ class Namer { typer: Typer =>
No("is already an extension method, cannot be exported into another one")
else if targets.contains(alias) then
No(i"clashes with another export in the same export clause")
else if sym.is(Override) then
else if sym.is(Override) || sym.is(JavaDefined) then
// The tests above are used to avoid futile searches of `allOverriddenSymbols`.
// Scala defined symbols can override concrete symbols only if declared override.
// For Java defined symbols, this does not hold, so we have to search anyway.
sym.allOverriddenSymbols.find(
other => cls.derivesFrom(other.owner) && !other.is(Deferred)
) match
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ i15922.scala
t5031_2.scala
i16997.scala
i7414.scala
i17588.scala

# Tree is huge and blows stack for printing Text
i7034.scala
Expand Down
7 changes: 0 additions & 7 deletions docs/_docs/reference/changed-features/type-checking.md

This file was deleted.

22 changes: 9 additions & 13 deletions docs/_docs/reference/dropped-features/nonlocal-returns.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,17 @@ Returning from nested anonymous functions has been deprecated, and will produce

Nonlocal returns are implemented by throwing and catching `scala.runtime.NonLocalReturnException`-s. This is rarely what is intended by the programmer. It can be problematic because of the hidden performance cost of throwing and catching exceptions. Furthermore, it is a leaky implementation: a catch-all exception handler can intercept a `NonLocalReturnException`.

A drop-in library replacement is provided in [`scala.util.control.NonLocalReturns`](https://scala-lang.org/api/3.x/scala/util/control/NonLocalReturns$.html). Example:
A better alternative to nonlocal returns and also the `scala.util.control.Breaks` API is provided by [`scala.util.boundary` and `boundary.break`](http://dotty.epfl.ch/api/scala/util/boundary$.html).

```scala
import scala.util.control.NonLocalReturns.*

extension [T](xs: List[T])
def has(elem: T): Boolean = returning {
for x <- xs do
if x == elem then throwReturn(true)
false
}
Example:

@main def test(): Unit =
val xs = List(1, 2, 3, 4, 5)
assert(xs.has(2) == xs.contains(2))
```scala
import scala.util.boundary, boundary.break
def firstIndex[T](xs: List[T], elem: T): Int =
boundary:
for (x, i) <- xs.zipWithIndex do
if x == elem then break(i)
-1
```

Note: compiler produces deprecation error on nonlocal returns only with `-source:future` option.
1 change: 0 additions & 1 deletion docs/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ subsection:
- page: reference/changed-features/operators.md
- page: reference/changed-features/wildcards.md
- page: reference/changed-features/imports.md
- page: reference/changed-features/type-checking.md
- page: reference/changed-features/type-inference.md
- page: reference/changed-features/implicit-resolution.md
- page: reference/changed-features/implicit-conversions.md
Expand Down
1 change: 0 additions & 1 deletion project/resources/referenceReplacements/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ subsection:
- page: reference/changed-features/operators.md
- page: reference/changed-features/wildcards.md
- page: reference/changed-features/imports.md
- page: reference/changed-features/type-checking.md
- page: reference/changed-features/type-inference.md
- page: reference/changed-features/implicit-resolution.md
- page: reference/changed-features/implicit-conversions.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
./changed-features/pattern-matching.html
./changed-features/structural-types-spec.html
./changed-features/structural-types.html
./changed-features/type-checking.html
./changed-features/type-inference.html
./changed-features/vararg-splices.html
./changed-features/wildcards.html
Expand Down
8 changes: 8 additions & 0 deletions tests/pos/i17556.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
sealed trait A {
// must be `object` or `case class`
object X extends A
case class Y() extends A
}

// companion object must exist
object A
2 changes: 2 additions & 0 deletions tests/pos/i17588.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class StringBox(inner: String):
export inner.*
20 changes: 20 additions & 0 deletions tests/run/i15913.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// https://github.com/lampepfl/dotty/issues/15913

class injector[F]

object injectorFactory {
def apply[F](overrides: String*): injector[F] = new injector[F]

def apply[F](
bootstrapActivation: Int = ???,
overrides: Seq[String] = Seq.empty,
): injector[F] = new injector[F]
}

object Test extends App {
println(
injectorFactory[String](
bootstrapActivation = 0
)
)
}
17 changes: 17 additions & 0 deletions tests/run/i17555.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class Root {
override def toString() = "Root"
}
trait A extends Root with B { }
trait B {
override def toString() = "B"
}
case class C() extends A {
override def toString() = super.toString()
}
class D() extends A, Serializable {
override def toString() = super.toString()
}

@main def Test =
assert(C().toString == "B")
assert(D().toString == "B")