Skip to content

Assume some capture sets in user-defined types #12892

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

Closed
wants to merge 90 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
0036ecb
First version of RefineTypes
odersky May 17, 2021
4a3eb52
Create fresh TypeVars when refining
odersky May 19, 2021
247f4e0
Fix typedSelect for avoiding private members
odersky May 20, 2021
febed32
Remember instantiation direction of type variables
odersky May 20, 2021
7cfc1cf
Don't use inst for class linking
odersky May 20, 2021
451059a
Leave types as they are for outer references
odersky May 21, 2021
1aca428
Prefer old instance over extremal bound when refining
odersky May 21, 2021
66ea734
Rename TypeVarBinder --> InferredTypeTree
odersky May 22, 2021
4a8b619
Cleanups
odersky May 22, 2021
b49fcc5
Move typedTyped from ReTyper
odersky May 22, 2021
7feecda
Add some logic needed for Namer
odersky May 22, 2021
3a7275b
Pull out inferredResultType into separate method
odersky May 25, 2021
6158498
Re-infer inferred types of ValDefs and DefDefs
odersky May 25, 2021
800e831
Improve expected types for dependent functions
odersky May 25, 2021
52be81e
Fix owners for completion contexts
odersky May 25, 2021
f0f1fb0
Make refiners conditional on -Yrefine-types
odersky May 25, 2021
fc04aa1
Fix typevar instantiation to allow several instantiate calls
odersky May 25, 2021
dfeea9b
Add InferredTypeTree to Trees.Instance
odersky May 27, 2021
47dd4a3
Retype inferred closure parameters
odersky May 27, 2021
054708e
Add tastyBootstrap to refine tests
odersky May 28, 2021
9776b16
Replace leaked type variables bound in callee
odersky May 28, 2021
72caef3
Fix rebase breakage
odersky Jun 5, 2021
87a3e02
Re-propagate result types of anonymous functions
odersky Jun 8, 2021
0d2de67
Readapt to latest master
odersky Jun 8, 2021
c565d08
Re-infer type ascriptions added in ensureNoLocalRefs
odersky Jun 9, 2021
06d8093
Declare that PreRefine can change base types
odersky Jun 9, 2021
538e211
Re-infer types of symbols in local and private classes
odersky Jun 12, 2021
6c95ccc
Add functionality to SimpleIdentitySet
odersky Jun 5, 2021
5a2cd34
Basic support for CapturingType
odersky Jun 5, 2021
7ae43f3
Pickling and unpickling of capturing types
odersky Jun 5, 2021
758d7cd
Add support for CapturingTypes elsewhere
odersky Jun 5, 2021
789b04e
Add TopType
odersky Jun 5, 2021
9457e54
Don't check capture sets in bounds checks
odersky Jun 5, 2021
7b65772
Some tests
odersky Jun 5, 2021
1a6d694
Refactor LambdaLift
odersky Jun 6, 2021
7bffc31
Enrich Types infrastructure for captures
odersky Jun 6, 2021
9af8498
Add CheckCaptures phase
odersky Jun 6, 2021
b79931e
Normalize refs in CapturingTypes and CaptureSets
odersky Jun 7, 2021
91cb1bc
Add missing case in type comparer
odersky Jun 7, 2021
bd1da71
Typecheck applications
odersky Jun 7, 2021
2905795
Ignore `holds` unless `-Yrefine-types` is set
odersky Jun 7, 2021
6cee8c0
Introduce -Ycc setting
odersky Jun 7, 2021
2f085ac
Fix tests
odersky Jun 7, 2021
c6fc5f3
Move capture parameter substitution into normal typer
odersky Jun 7, 2021
ea7012a
More fixes and tests
odersky Jun 8, 2021
ecaa1c1
Fix TypeMap for CapturingTypes
odersky Jun 8, 2021
890aa72
Improve error message
odersky Jun 8, 2021
662e76b
Make "capture `*`" check configurable
odersky Jun 11, 2021
e273bfa
Update tests
odersky Jun 11, 2021
3206e9a
Fix argForParam
odersky Jun 11, 2021
3e8b0f6
Fix printing of function type trees
odersky Jun 11, 2021
a537a40
Declare subcategories of abilities with @ability
odersky Jun 11, 2021
6c5a1cb
Rename holds -> retains
odersky Jun 11, 2021
2b60994
Define capture sets of classes
odersky Jun 11, 2021
64d6875
Test curried tries
odersky Jun 11, 2021
efeec2d
Simplify singleton subtype rule
odersky Jun 11, 2021
1750139
Refuse to instantiate type variables with other global capabilities
odersky Jun 11, 2021
f5611e3
Fixes and well-formedness check for capture-dependent types
odersky Jun 11, 2021
0341f7f
Also check well-formedness of method signatures
odersky Jun 12, 2021
e15bfc4
Add List encoding test
odersky Jun 12, 2021
4e349cc
Fix joins and join approximations of capturing types
odersky Jun 12, 2021
e96f653
Target minor version
odersky Jun 15, 2021
8af8dbc
Fix rebase breakage
odersky Jun 15, 2021
639361b
Allow curried methods that refer to capture refs contravariantly
odersky Jun 16, 2021
6cc124e
Avoid abbreviation in warning
odersky Jun 16, 2021
de62914
Tests for strict and lazy maps over boxes
odersky Jun 16, 2021
9fb45c9
Fix neg test
odersky Jun 16, 2021
c03b577
Revert "Target minor version"
odersky Jun 17, 2021
7433f3f
Drop implementation restriction for polymorphic functions
odersky Jun 17, 2021
797e261
Fix arity check
odersky Jun 17, 2021
d1fab19
Add IO test
odersky Jun 17, 2021
3a2190a
Merge pull request #12863 from dotty-staging/generalize-polyfuns
odersky Jun 18, 2021
fe31d81
Merge pull request #12859 from dotty-staging/add-io-test
odersky Jun 18, 2021
c226577
Refactor tests and fix isGlobal
odersky Jun 19, 2021
acc93bb
Fix printing of poly function types
odersky Jun 19, 2021
87c92d1
Allow capturing types as CFT protos
odersky Jun 19, 2021
651b791
Don't print trees twice after refiner phases
odersky Jun 19, 2021
f5d25da
Shallow capture sets
odersky Jun 19, 2021
9b7fe8b
Fix condition in TreeChecker
odersky Jun 19, 2021
3cf68e3
Fix precedence of dependent and poly function printing
odersky Jun 19, 2021
25b23c1
Fix capture sets of applied types
odersky Jun 20, 2021
27c9fc5
Fix decomposeProto
odersky Jun 20, 2021
988c4c0
Adapt tests to shallow capture sets
odersky Jun 20, 2021
6977ad1
Handle TypeVars in CaptureSets
odersky Jun 21, 2021
cceff77
Assume capture sets of result type in enclosing function
odersky Jun 21, 2021
7a282ad
Drop unused val in appliedTo
odersky Jun 21, 2021
59151bd
Print `retains` type trees infix
odersky Jun 21, 2021
18e768c
Merge pull request #12875 from dotty-staging/shallow-capture-sets
odersky Jun 28, 2021
3274eb2
Track variable insytantiations in capture sets
odersky Jun 21, 2021
4e0d18d
More tests
odersky Jun 22, 2021
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ jobs:
./project/scripts/sbt ";scala3-bootstrapped/compile ;scala3-bootstrapped/test;sjsSandbox/run;sjsSandbox/test;sjsJUnitTests/test;sjsCompilerTests/test ;sbt-test/scripted scala2-compat/* ;configureIDE ;stdlib-bootstrapped/test:run ;stdlib-bootstrapped-tasty-tests/test"
./project/scripts/bootstrapCmdTests

- name: MiMa
run: |
./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues"
#- name: MiMa
# run: |
# ./project/scripts/sbt ";scala3-interfaces/mimaReportBinaryIssues ;scala3-library-bootstrapped/mimaReportBinaryIssues ;scala3-library-bootstrappedJS/mimaReportBinaryIssues"

test_windows_fast:
runs-on: [self-hosted, Windows]
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package dotc

import core._
import Contexts._
import typer.{FrontEnd, RefChecks}
import typer.{FrontEnd, RefChecks, PreRefine, CheckCaptures, TestRefineTypes}
import Phases.Phase
import transform._
import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode}
Expand Down Expand Up @@ -37,6 +37,9 @@ class Compiler {
/** Phases dealing with the frontend up to trees ready for TASTY pickling */
protected def frontendPhases: List[List[Phase]] =
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
List(new PreRefine) ::
List(new CheckCaptures) ::
List(new TestRefineTypes) ::
List(new YCheckPositions) :: // YCheck positions
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Types._
import Scopes._
import Names.Name
import Denotations.Denotation
import typer.Typer
import typer.{Typer, RefineTypes}
import typer.ImportInfo._
import Decorators._
import io.{AbstractFile, PlainFile, VirtualFile}
Expand Down Expand Up @@ -204,7 +204,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
val profileBefore = profiler.beforePhase(phase)
units = phase.runOn(units)
profiler.afterPhase(phase, profileBefore)
if (ctx.settings.Xprint.value.containsPhase(phase))
if ctx.settings.Xprint.value.containsPhase(phase) && !phase.isInstanceOf[RefineTypes] then
for (unit <- units)
lastPrintedTree =
printTree(lastPrintedTree)(using ctx.fresh.setPhase(phase.next).setCompilationUnit(unit))
Expand Down
50 changes: 27 additions & 23 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1632,12 +1632,12 @@ object desugar {
}
}

def makePolyFunction(targs: List[Tree], body: Tree): Tree = body match {
def makePolyFunction(targs: List[Tree], body: Tree): Tree = body match
case Parens(body1) =>
makePolyFunction(targs, body1)
case Block(Nil, body1) =>
makePolyFunction(targs, body1)
case Function(vargs, res) =>
case _ =>
assert(targs.nonEmpty)
// TODO: Figure out if we need a `PolyFunctionWithMods` instead.
val mods = body match {
Expand All @@ -1646,33 +1646,37 @@ object desugar {
}
val polyFunctionTpt = ref(defn.PolyFunctionType)
val applyTParams = targs.asInstanceOf[List[TypeDef]]
if (ctx.mode.is(Mode.Type)) {
if ctx.mode.is(Mode.Type) then
// Desugar [T_1, ..., T_M] -> (P_1, ..., P_N) => R
// Into scala.PolyFunction { def apply[T_1, ..., T_M](x$1: P_1, ..., x$N: P_N): R }

val applyVParams = vargs.zipWithIndex.map {
case (p: ValDef, _) => p.withAddedFlags(mods.flags)
case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags)
}
val (res, applyVParamss) = body match
case Function(vargs, res) =>
( res,
vargs.zipWithIndex.map {
case (p: ValDef, _) => p.withAddedFlags(mods.flags)
case (p, n) => makeSyntheticParameter(n + 1, p).withAddedFlags(mods.flags)
} :: Nil
)
case _ =>
(body, Nil)
RefinedTypeTree(polyFunctionTpt, List(
DefDef(nme.apply, applyTParams :: applyVParams :: Nil, res, EmptyTree)
DefDef(nme.apply, applyTParams :: applyVParamss, res, EmptyTree)
))
}
else {
else
// Desugar [T_1, ..., T_M] -> (x_1: P_1, ..., x_N: P_N) => body
// Into new scala.PolyFunction { def apply[T_1, ..., T_M](x_1: P_1, ..., x_N: P_N) = body }

val applyVParams = vargs.asInstanceOf[List[ValDef]]
.map(varg => varg.withAddedFlags(mods.flags | Param))
New(Template(emptyConstructor, List(polyFunctionTpt), Nil, EmptyValDef,
List(DefDef(nme.apply, applyTParams :: applyVParams :: Nil, TypeTree(), res))
))
}
case _ =>
// may happen for erroneous input. An error will already have been reported.
assert(ctx.reporter.errorsReported)
EmptyTree
}
val (res, applyVParamss) = body match
case Function(vargs, res) =>
( res,
vargs.asInstanceOf[List[ValDef]]
.map(varg => varg.withAddedFlags(mods.flags | Param))
:: Nil
)
case _ =>
(body, Nil)
New(Template(emptyConstructor, List(polyFunctionTpt), Nil, EmptyValDef,
List(DefDef(nme.apply, applyTParams :: applyVParamss, TypeTree(), res))
))

// begin desugar

Expand Down
17 changes: 7 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -261,16 +261,10 @@ object Trees {
/** Tree's denotation can be derived from its type */
abstract class DenotingTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends Tree[T] {
type ThisTree[-T >: Untyped] <: DenotingTree[T]
override def denot(using Context): Denotation = typeOpt match {
override def denot(using Context): Denotation = typeOpt.stripped match
case tpe: NamedType => tpe.denot
case tpe: ThisType => tpe.cls.denot
case tpe: AnnotatedType => tpe.stripAnnots match {
case tpe: NamedType => tpe.denot
case tpe: ThisType => tpe.cls.denot
case _ => NoDenotation
}
case _ => NoDenotation
}
}

/** Tree's denot/isType/isTerm properties come from a subtree
Expand Down Expand Up @@ -699,10 +693,12 @@ object Trees {
s"TypeTree${if (hasType) s"[$typeOpt]" else ""}"
}

/** A type tree that defines a new type variable. Its type is always a TypeVar.
* Every TypeVar is created as the type of one TypeVarBinder.
/** A type tree whose type is inferred. These trees appear in two contexts
* - as an argument of a TypeApply. In that case its type is always a TypeVar
* - as a (result-)type of an inferred ValDef or DefDef.
* Every TypeVar is created as the type of one InferredTypeTree.
*/
class TypeVarBinder[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]
class InferredTypeTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends TypeTree[T]

/** ref.type */
case class SingletonTypeTree[-T >: Untyped] private[ast] (ref: Tree[T])(implicit @constructorOnly src: SourceFile)
Expand Down Expand Up @@ -1079,6 +1075,7 @@ object Trees {
type JavaSeqLiteral = Trees.JavaSeqLiteral[T]
type Inlined = Trees.Inlined[T]
type TypeTree = Trees.TypeTree[T]
type InferredTypeTree = Trees.InferredTypeTree[T]
type SingletonTypeTree = Trees.SingletonTypeTree[T]
type RefinedTypeTree = Trees.RefinedTypeTree[T]
type AppliedTypeTree = Trees.AppliedTypeTree[T]
Expand Down
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -979,11 +979,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}

/** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */
def cast(tp: Type)(using Context): Tree = {
assert(tp.isValueType, i"bad cast: $tree.asInstanceOf[$tp]")
def cast(tp: Type)(using Context): Tree = cast(TypeTree(tp))

/** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */
def cast(tpt: TypeTree)(using Context): Tree =
assert(tpt.tpe.isValueType, i"bad cast: $tree.asInstanceOf[$tpt]")
tree.select(if (ctx.erasedTypes) defn.Any_asInstanceOf else defn.Any_typeCast)
.appliedToType(tp)
}
.appliedToTypeTree(tpt)

/** cast `tree` to `tp` (or its box/unbox/cast equivalent when after
* erasure and value and non-value types are mixed),
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/Printers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object Printers {

val default = new Printer

val capt = noPrinter
val constr = noPrinter
val core = noPrinter
val checks = noPrinter
Expand Down Expand Up @@ -39,6 +40,7 @@ object Printers {
val quotePickling = noPrinter
val plugins = noPrinter
val refcheck = noPrinter
val refinr = noPrinter
val simplify = noPrinter
val staging = noPrinter
val subtyping = noPrinter
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ trait AllScalaSettings extends CommonScalaSettings { self: Settings.SettingGroup
val YexplicitNulls: Setting[Boolean] = BooleanSetting("-Yexplicit-nulls", "Make reference types non-nullable. Nullable types can be expressed with unions: e.g. String|Null.")
val YcheckInit: Setting[Boolean] = BooleanSetting("-Ysafe-init", "Ensure safe initialization of objects")
val YrequireTargetName: Setting[Boolean] = BooleanSetting("-Yrequire-targetName", "Warn if an operator is defined without a @targetName annotation")
val YrefineTypes: Setting[Boolean] = BooleanSetting("-Yrefine-types", "Run experimental type refiner (test only)")
val Ycc: Setting[Boolean] = BooleanSetting("-Ycc", "Check captured references")
val YccNoAbbrev: Setting[Boolean] = BooleanSetting("-Ycc-no-abbrev", "Used in conjunction with -Ycc, suppress type abbreviations")

/** Area-specific debug output */
val YexplainLowlevel: Setting[Boolean] = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")
Expand Down
122 changes: 122 additions & 0 deletions compiler/src/dotty/tools/dotc/core/CaptureSet.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package dotty.tools
package dotc
package core

import util.*
import Types.*, Symbols.*, Flags.*, Contexts.*, Decorators.*
import config.Printers.capt
import annotation.threadUnsafe
import annotation.internal.sharable
import reporting.trace
import printing.{Showable, Printer}
import printing.Texts.*

case class CaptureSet private (elems0: CaptureSet.Refs) extends Showable:
import CaptureSet.*

def isEmpty(using Context): Boolean = elems.isEmpty
def nonEmpty(using Context): Boolean = !isEmpty

private var isProvisional = true
private var myElems: CaptureSet.Refs = elems0

def elems(using Context): CaptureSet.Refs =
if isProvisional then
isProvisional = false
myElems.foreach {
case tv: TypeVar =>
if tv.isInstantiated then myElems = myElems - tv ++ tv.inst.captureSet.elems
else isProvisional = true
case _ =>
}
myElems

def ++ (that: CaptureSet)(using Context): CaptureSet =
if this.isEmpty then that
else if that.isEmpty then this
else CaptureSet(myElems ++ that.elems)

def + (ref: CaptureRef)(using Context) =
if elems.contains(ref) then this
else CaptureSet(elems + ref)

def intersect (that: CaptureSet)(using Context): CaptureSet =
CaptureSet(this.elems.intersect(that.elems))

/** {x} <:< this where <:< is subcapturing */
def accountsFor(x: CaptureRef)(using Context) =
elems.contains(x) || !x.isRootCapability && x.captureSetOfInfo <:< this

/** The subcapturing test */
def <:< (that: CaptureSet)(using Context): Boolean =
elems.isEmpty || elems.forall(that.accountsFor)

def flatMap(f: CaptureRef => CaptureSet)(using Context): CaptureSet =
(empty /: elems)((cs, ref) => cs ++ f(ref))

def substParams(tl: BindingType, to: List[Type])(using Context) =
flatMap {
case ref: ParamRef if ref.binder eq tl => to(ref.paramNum).captureSet
case ref => ref.singletonCaptureSet
}

override def toString = myElems.toString

override def toText(printer: Printer): Text =
Str("{") ~ Text(myElems.toList.map(printer.toTextCaptureRef), ", ") ~ Str("}")

object CaptureSet:
type Refs = SimpleIdentitySet[CaptureRef]

@sharable val empty: CaptureSet = CaptureSet(SimpleIdentitySet.empty)

/** Used as a recursion brake */
@sharable private[core] val Pending = CaptureSet(SimpleIdentitySet.empty)

def apply(elems: CaptureRef*)(using Context): CaptureSet =
if elems.isEmpty then empty
else CaptureSet(SimpleIdentitySet(elems.map(_.normalizedRef)*))

def ofClass(cinfo: ClassInfo, argTypes: List[Type])(using Context): CaptureSet =
def captureSetOf(tp: Type): CaptureSet = tp match
case tp: TypeRef if tp.symbol.is(ParamAccessor) =>
def mapArg(accs: List[Symbol], tps: List[Type]): CaptureSet = accs match
case acc :: accs1 if tps.nonEmpty =>
if acc == tp.symbol then tps.head.captureSet
else mapArg(accs1, tps.tail)
case _ =>
empty
mapArg(cinfo.cls.paramAccessors, argTypes)
case _ =>
tp.captureSet
val css =
for
parent <- cinfo.parents if parent.classSymbol == defn.RetainsClass
arg <- parent.argInfos
yield captureSetOf(arg)
css.foldLeft(empty)(_ ++ _)

def ofType(tp: Type)(using Context): CaptureSet =
def recur(tp: Type): CaptureSet = tp match
case tp: CaptureRef =>
tp.captureSet
case CapturingType(parent, ref) =>
recur(parent) + ref
case AppliedType(tycon, args) =>
val cs = recur(tycon)
tycon.typeParams match
case tparams @ (LambdaParam(tl, _) :: _) => cs.substParams(tl, args)
case _ => cs
case tp: TypeProxy =>
recur(tp.underlying)
case AndType(tp1, tp2) =>
recur(tp1).intersect(recur(tp2))
case OrType(tp1, tp2) =>
recur(tp1) ++ recur(tp2)
case tp: ClassInfo =>
ofClass(tp, Nil)
case _ =>
empty
recur(tp)
.showing(i"capture set of $tp = $result", capt)

2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ object Contexts {
/** Is current phase after FrontEnd? */
final def isAfterTyper = base.isAfterTyper(phase)

final def isAfterRefiner = base.isAfterRefiner(phase)

/** Is this a context for the members of a class definition? */
def isClassDefContext: Boolean =
owner.isClass && (owner ne outer.owner)
Expand Down
Loading