Skip to content

Fix #4939: Add rewrite type match construct. #4962

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 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
5943dd1
Handle bound types in type patterns
odersky Aug 6, 2018
0c76ce6
Don't bind wildcards as type symbols in type patterns
odersky Aug 8, 2018
0ee018c
Drop blocks with empty statements from inlined expansions
odersky Aug 8, 2018
b0021aa
Dealias type bindings in inlined expansions
odersky Aug 8, 2018
716241a
Fix match-in-match marking of toplevel matches
odersky Aug 9, 2018
cb9c432
Enter pattern-bound variables into scope when checking guard
odersky Aug 9, 2018
665a330
Fix inline printing of types
odersky Aug 9, 2018
ebdaca7
PoC for generic tuples
odersky Aug 9, 2018
bd7211d
Update check file
odersky Aug 9, 2018
ffc1a1f
Transparent methods don't need inline accessors
odersky Aug 9, 2018
c35cb17
Change spec, renaming `transparent` to `rewrite`.
odersky Aug 10, 2018
9f670bc
Rename transparent methods -> rewrite methods
odersky Aug 12, 2018
7957455
Fix test
odersky Aug 12, 2018
f7fe4af
Change overriding rule for erased values
odersky Aug 13, 2018
1194786
Change test to use non-toplevel rewrite match
odersky Aug 13, 2018
87f13d2
Add Tuple and *: types
odersky Aug 13, 2018
070b5f7
Add version-specific source directories in the library
smarter Aug 14, 2018
bf53b7e
Fix Inlined abbreviation in erased code
odersky Aug 14, 2018
3f8de3e
Remove rewrite annotation again
odersky Aug 14, 2018
02bdf5e
Add generative operations to tuples
odersky Aug 14, 2018
329dfc0
Fix TupleXXL equals
odersky Aug 14, 2018
c0ab760
More generative operations on Tuples: tail, apply, ++
odersky Aug 14, 2018
bf358a4
Erase *: to tuple classes
odersky Aug 14, 2018
d9263e6
Allow tuple literals to extend beyond 22
odersky Aug 15, 2018
5029642
Fix merged tests
odersky Aug 15, 2018
ecb2255
Don't generate *: in generic Java signatures
odersky Aug 15, 2018
5f49c78
Test pattern matching of tuples >= 23.
odersky Aug 16, 2018
754ccc5
Whitelist some Scala-2 classes as pure
odersky Aug 17, 2018
cf6a1f7
Fix side-effect handling in reduceProjection
odersky Aug 17, 2018
c27da06
Support a limited form of rewrite unapplys
odersky Aug 17, 2018
2bdbf4e
Enforce implementation restrictions
odersky Aug 17, 2018
46e0413
Allow matching on types
odersky Aug 17, 2018
fc32857
Add `rewrite type match` construct
odersky Aug 18, 2018
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
2 changes: 1 addition & 1 deletion bench/tests/power-macro/PowerMacro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import scala.quoted.Expr

object PowerMacro {

transparent def power(transparent n: Long, x: Double) = ~powerCode(n, '(x))
rewrite def power(transparent n: Long, x: Double) = ~powerCode(n, '(x))

def powerCode(n: Long, x: Expr[Double]): Expr[Double] =
if (n == 0) '(1.0)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import core.Types.Type // Do not remove me #3383
import util.SourceFile
import ast.{tpd, untpd}
import tpd.{ Tree, TreeTraverser }
import typer.PrepareTransparent.InlineAccessors
import typer.PrepareInlineable.InlineAccessors
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.SymDenotations.ClassDenotation
import dotty.tools.dotc.core.Symbols._
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import scala.io.Codec
import util.{Set => _, _}
import reporting.Reporter
import transform.TreeChecker
import rewrite.Rewrites
import rewrites.Rewrites
import java.io.{BufferedWriter, OutputStreamWriter}

import profile.Profiler
Expand Down
27 changes: 17 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,23 @@ object desugar {
makeOp(right, left, Position(op.pos.start, right.pos.end))
}

/** Translate tuple expressions of arity <= 22
*
* () ==> ()
* (t) ==> t
* (t1, ..., tN) ==> TupleN(t1, ..., tN)
*/
def smallTuple(tree: Tuple)(implicit ctx: Context): Tree = {
val ts = tree.trees
val arity = ts.length
assert(arity <= Definitions.MaxTupleArity)
def tupleTypeRef = defn.TupleType(arity)
if (arity == 1) ts.head
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else if (arity == 0) unitLiteral
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
}

/** Make closure corresponding to function.
* params => body
* ==>
Expand Down Expand Up @@ -1141,16 +1158,6 @@ object desugar {
case PrefixOp(op, t) =>
val nspace = if (ctx.mode.is(Mode.Type)) tpnme else nme
Select(t, nspace.UNARY_PREFIX ++ op.name)
case Tuple(ts) =>
val arity = ts.length
def tupleTypeRef = defn.TupleType(arity)
if (arity > Definitions.MaxTupleArity) {
ctx.error(TupleTooLong(ts), tree.pos)
unitLiteral
} else if (arity == 1) ts.head
else if (ctx.mode is Mode.Type) AppliedTypeTree(ref(tupleTypeRef), ts)
else if (arity == 0) unitLiteral
else Apply(ref(tupleTypeRef.classSymbol.companionModule.termRef), ts)
case WhileDo(cond, body) =>
// { <label> def while$(): Unit = if (cond) { body; while$() } ; while$() }
val call = Apply(Ident(nme.WHILE_PREFIX), Nil).withPos(tree.pos)
Expand Down
15 changes: 14 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = {
val sym = tree.symbol
if (!tree.hasType) Impure
else if (!tree.tpe.widen.isParameterless || sym.is(Erased)) SimplyPure
else if (!tree.tpe.widen.isParameterless || sym.isEffectivelyErased) SimplyPure
else if (!sym.isStable) Impure
else if (sym.is(Module))
if (sym.moduleClass.isNoInitsClass) Pure else Idempotent
Expand Down Expand Up @@ -716,6 +716,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
Nil
}

/** If `tree` is an instance of `TupleN[...](e1, ..., eN)`, the arguments `e1, ..., eN`
* otherwise the empty list.
*/
def tupleArgs(tree: Tree)(implicit ctx: Context): List[Tree] = tree match {
case Block(Nil, expr) => tupleArgs(expr)
case Inlined(_, Nil, expr) => tupleArgs(expr)
case Apply(fn, args)
if fn.symbol.name == nme.apply &&
fn.symbol.owner.is(Module) &&
defn.isTupleClass(fn.symbol.owner.companionClass) => args
case _ => Nil
}

/** The qualifier part of a Select or Ident.
* For an Ident, this is the `This` of the current class.
*/
Expand Down
17 changes: 17 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ object Trees {
extends TermTree[T] {
type ThisTree[-T >: Untyped] = If[T]
}
class RewriteIf[T >: Untyped] private[ast] (cond: Tree[T], thenp: Tree[T], elsep: Tree[T])
extends If(cond, thenp, elsep) {
override def toString = s"RewriteIf($cond, $thenp, $elsep)"
}

/** A closure with an environment and a reference to a method.
* @param env The captured parameters of the closure
Expand All @@ -516,6 +520,11 @@ object Trees {
case class Match[-T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]])
extends TermTree[T] {
type ThisTree[-T >: Untyped] = Match[T]
def isTypeMatch = false
}
class RewriteMatch[T >: Untyped] private[ast] (selector: Tree[T], cases: List[CaseDef[T]], override val isTypeMatch: Boolean)
extends Match(selector, cases) {
override def toString = s"RewriteMatch($selector, $cases, $isTypeMatch)"
}

/** case pat if guard => body; only appears as child of a Match */
Expand Down Expand Up @@ -883,8 +892,10 @@ object Trees {
type Assign = Trees.Assign[T]
type Block = Trees.Block[T]
type If = Trees.If[T]
type RewriteIf = Trees.RewriteIf[T]
type Closure = Trees.Closure[T]
type Match = Trees.Match[T]
type RewriteMatch = Trees.RewriteMatch[T]
type CaseDef = Trees.CaseDef[T]
type Return = Trees.Return[T]
type Try = Trees.Try[T]
Expand Down Expand Up @@ -1013,6 +1024,9 @@ object Trees {
case _ => finalize(tree, untpd.Block(stats, expr))
}
def If(tree: Tree)(cond: Tree, thenp: Tree, elsep: Tree)(implicit ctx: Context): If = tree match {
case tree: RewriteIf =>
if ((cond eq tree.cond) && (thenp eq tree.thenp) && (elsep eq tree.elsep)) tree
else finalize(tree, untpd.RewriteIf(cond, thenp, elsep))
case tree: If if (cond eq tree.cond) && (thenp eq tree.thenp) && (elsep eq tree.elsep) => tree
case _ => finalize(tree, untpd.If(cond, thenp, elsep))
}
Expand All @@ -1021,6 +1035,9 @@ object Trees {
case _ => finalize(tree, untpd.Closure(env, meth, tpt))
}
def Match(tree: Tree)(selector: Tree, cases: List[CaseDef])(implicit ctx: Context): Match = tree match {
case tree: RewriteMatch =>
if ((selector eq tree.selector) && (cases eq tree.cases)) tree
else finalize(tree, untpd.RewriteMatch(selector, cases, tree.isTypeMatch))
case tree: Match if (selector eq tree.selector) && (cases eq tree.cases) => tree
case _ => finalize(tree, untpd.Match(selector, cases))
}
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {

case class Lazy() extends Mod(Flags.Lazy)

case class Rewrite() extends Mod(Flags.Rewrite)

case class Transparent() extends Mod(Flags.Transparent)

case class Enum() extends Mod(Flags.Enum)
Expand Down Expand Up @@ -273,8 +275,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def Assign(lhs: Tree, rhs: Tree): Assign = new Assign(lhs, rhs)
def Block(stats: List[Tree], expr: Tree): Block = new Block(stats, expr)
def If(cond: Tree, thenp: Tree, elsep: Tree): If = new If(cond, thenp, elsep)
def RewriteIf(cond: Tree, thenp: Tree, elsep: Tree): If = new RewriteIf(cond, thenp, elsep)
def Closure(env: List[Tree], meth: Tree, tpt: Tree): Closure = new Closure(env, meth, tpt)
def Match(selector: Tree, cases: List[CaseDef]): Match = new Match(selector, cases)
def RewriteMatch(selector: Tree, cases: List[CaseDef], typeMatch: Boolean): Match = new RewriteMatch(selector, cases, typeMatch)
def CaseDef(pat: Tree, guard: Tree, body: Tree): CaseDef = new CaseDef(pat, guard, body)
def Return(expr: Tree, from: Tree): Return = new Return(expr, from)
def Try(expr: Tree, cases: List[CaseDef], finalizer: Tree): Try = new Try(expr, cases, finalizer)
Expand Down Expand Up @@ -310,7 +314,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
* where `Ts` are the class type arguments of `T` or its class type alias.
* Note: we also keep any type arguments as parts of `T`. This is necessary to allow
* navigation into these arguments from the IDE, and to do the right thing in
* PrepareTransparent.
* PrepareInlineable.
*/
def New(tpt: Tree, argss: List[List[Tree]])(implicit ctx: Context): Tree = {
val (tycon, targs) = tpt match {
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.io.File
import dotty.tools.io.{ Directory, PlainDirectory }

import PathResolver.Defaults
import rewrite.Rewrites
import rewrites.Rewrites

class ScalaSettings extends Settings.SettingGroup {

Expand Down Expand Up @@ -43,7 +43,7 @@ class ScalaSettings extends Settings.SettingGroup {
val pageWidth = IntSetting("-pagewidth", "Set page width", 80)
val strict = BooleanSetting("-strict", "Use strict type rules, which means some formerly legal code does not typecheck anymore.")
val language = MultiStringSetting("-language", "feature", "Enable one or more language features.")
val rewrite = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax")
val `rewrite` = OptionSetting[Rewrites]("-rewrite", "When used in conjunction with -language:Scala2 rewrites sources to migrate to new syntax")
val silentWarnings = BooleanSetting("-nowarn", "Silence all warnings.")
val fromTasty = BooleanSetting("-from-tasty", "Compile classes from tasty in classpath. The arguments are used as class names.")

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object Annotations {
}

/** An annotation indicating the body of a right-hand side,
* typically of a transparent method. Treated specially in
* typically of a rewrite or transparent method. Treated specially in
* pickling/unpickling and TypeTreeMaps
*/
abstract class BodyAnnotation extends Annotation {
Expand Down
56 changes: 24 additions & 32 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,13 @@ trait ConstraintHandling {
}
}

/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
* lower bounds; otherwise it is the glb of its upper bounds. However,
* a lower bound instantiation can be a singleton type only if the upper bound
* is also a singleton type.
/** Widen inferred type `tp` with upper bound `bound`, according to the following rules:
* 1. If `tp` is a singleton type, yet `bound` is not a singleton type, nor a subtype
* of `scala.Singleton`, widen `tp`.
* 2. If `tp` is a union type, yet upper bound is not a union type,
* approximate the union type from above by an intersection of all common base types.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
def upperBound = constraint.fullUpperBound(param)
def widenInferred(tp: Type, bound: Type): Type = {
def isMultiSingleton(tp: Type): Boolean = tp.stripAnnots match {
case tp: SingletonType => true
case AndType(tp1, tp2) => isMultiSingleton(tp1) | isMultiSingleton(tp2)
Expand All @@ -268,39 +267,32 @@ trait ConstraintHandling {
case tp: TypeParamRef => isMultiSingleton(bounds(tp).hi)
case _ => false
}
def isFullyDefined(tp: Type): Boolean = tp match {
case tp: TypeVar => tp.isInstantiated && isFullyDefined(tp.instanceOpt)
case tp: TypeProxy => isFullyDefined(tp.underlying)
case tp: AndType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
case tp: OrType => isFullyDefined(tp.tp1) && isFullyDefined(tp.tp2)
case _ => true
}
def isOrType(tp: Type): Boolean = tp.dealias match {
case tp: OrType => true
case tp: RefinedOrRecType => isOrType(tp.parent)
case AndType(tp1, tp2) => isOrType(tp1) | isOrType(tp2)
case WildcardType(bounds: TypeBounds) => isOrType(bounds.hi)
case _ => false
}
def widenOr(tp: Type) =
if (isOrType(tp) && !isOrType(bound)) tp.widenUnion
else tp
def widenSingle(tp: Type) =
if (isMultiSingleton(tp) && !isMultiSingleton(bound) &&
!isSubTypeWhenFrozen(bound, defn.SingletonType)) tp.widen
else tp
widenOr(widenSingle(tp))
}

// First, solve the constraint.
var inst = approximation(param, fromBelow).simplified

// Then, approximate by (1.) - (3.) and simplify as follows.
// 1. If instance is from below and is a singleton type, yet upper bound is
// not a singleton type or a subtype of `scala.Singleton`, widen the
// instance.
if (fromBelow && isMultiSingleton(inst) && !isMultiSingleton(upperBound)
&& !isSubTypeWhenFrozen(upperBound, defn.SingletonType))
inst = inst.widen

// 2. If instance is from below and is a fully-defined union type, yet upper bound
// is not a union type, approximate the union type from above by an intersection
// of all common base types.
if (fromBelow && isOrType(inst) && !isOrType(upperBound))
inst = inst.widenUnion

inst
/** The instance type of `param` in the current constraint (which contains `param`).
* If `fromBelow` is true, the instance type is the lub of the parameter's
* lower bounds; otherwise it is the glb of its upper bounds. However,
* a lower bound instantiation can be a singleton type only if the upper bound
* is also a singleton type.
*/
def instanceType(param: TypeParamRef, fromBelow: Boolean): Type = {
val inst = approximation(param, fromBelow).simplified
if (fromBelow) widenInferred(inst, constraint.fullUpperBound(param)) else inst
}

/** Constraint `c1` subsumes constraint `c2`, if under `c2` as constraint we have
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ object Contexts {
def isNonEmptyScopeContext: Boolean =
(this.scope ne outer.scope) && !this.scope.isEmpty

/** Is this a context for typechecking an inlined body? */
def isInlineContext: Boolean =
typer.isInstanceOf[Inliner#InlineTyper]

/** The next outer context whose tree is a template or package definition
* Note: Currently unused
def enclTemplate: Context = {
Expand Down
47 changes: 44 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,18 @@ class Definitions {

lazy val XMLTopScopeModuleRef = ctx.requiredModuleRef("scala.xml.TopScope")

lazy val TupleTypeRef = ctx.requiredClassRef("scala.Tuple")
def TupleClass(implicit ctx: Context) = TupleTypeRef.symbol.asClass

lazy val PairType = ctx.requiredClassRef("scala.*:")
def PairClass(implicit ctx: Context) = PairType.symbol.asClass
lazy val TupleXXLType = ctx.requiredClassRef("scala.TupleXXL")
def TupleXXLClass(implicit ctx: Context) = TupleXXLType.symbol.asClass
def TupleXXLModule(implicit ctx: Context) = TupleXXLClass.companionModule

def TupleXXL_apply(implicit ctx: Context) =
TupleXXLModule.info.member(nme.apply).requiredSymbol(_.info.isVarArgsMethod)

// Annotation base classes
lazy val AnnotationType = ctx.requiredClassRef("scala.annotation.Annotation")
def AnnotationClass(implicit ctx: Context) = AnnotationType.symbol.asClass
Expand Down Expand Up @@ -877,7 +889,7 @@ class Definitions {
private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0)
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))

lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 1)

def FunctionClass(n: Int, isImplicit: Boolean = false, isErased: Boolean = false)(implicit ctx: Context) =
if (isImplicit && isErased)
Expand All @@ -898,8 +910,6 @@ class Definitions {
if (n <= MaxImplementedFunctionArity && (!isImplicit || ctx.erasedTypes) && !isErased) ImplementedFunctionType(n)
else FunctionClass(n, isImplicit, isErased).typeRef

private lazy val TupleTypes: Set[TypeRef] = TupleType.toSet

/** If `cls` is a class in the scala package, its name, otherwise EmptyTypeName */
def scalaClassName(cls: Symbol)(implicit ctx: Context): TypeName =
if (cls.isClass && cls.owner == ScalaPackageClass) cls.asClass.name else EmptyTypeName
Expand Down Expand Up @@ -1124,6 +1134,10 @@ class Definitions {
def isErasedFunctionType(tp: Type)(implicit ctx: Context) =
isFunctionType(tp) && tp.dealias.typeSymbol.name.isErasedFunction

/** A whitelist of Scala-2 classes that are known to be pure */
def isAssuredNoInits(sym: Symbol) =
(sym `eq` SomeClass) || isTupleClass(sym)

// ----- primitive value class machinery ------------------------------------------

/** This class would also be obviated by the implicit function type design */
Expand Down Expand Up @@ -1196,6 +1210,8 @@ class Definitions {
def isValueSubClass(sym1: Symbol, sym2: Symbol) =
valueTypeEnc(sym2.asClass.name) % valueTypeEnc(sym1.asClass.name) == 0

lazy val erasedToObject = Set[Symbol](AnyClass, AnyValClass, TupleClass, SingletonClass)

// ----- Initialization ---------------------------------------------------

/** Lists core classes that don't have underlying bytecode, but are synthesized on-the-fly in every reflection universe */
Expand Down Expand Up @@ -1223,6 +1239,27 @@ class Definitions {

private[this] var isInitialized = false

/** Add a `Tuple` as a parent to `Unit`.
* Add the right `*:` instance as a parent to Tuple1..Tuple22
*/
def fixTupleCompleter(cls: ClassSymbol): Unit = cls.infoOrCompleter match {
case completer: LazyType =>
cls.info = new LazyType {
def syntheticParent(tparams: List[TypeSymbol]): Type =
if (tparams.isEmpty) TupleTypeRef
else (tparams :\ (UnitType: Type)) ((tparam, tail) => PairType.appliedTo(tparam.typeRef, tail))
override def complete(denot: SymDenotation)(implicit ctx: Context) = {
completer.complete(denot)
denot.info match {
case info: ClassInfo =>
denot.info = info.derivedClassInfo(
classParents = info.classParents :+ syntheticParent(cls.typeParams))
}
}
}
case _ =>
}

def init()(implicit ctx: Context) = {
this.ctx = ctx
if (!isInitialized) {
Expand All @@ -1240,6 +1277,10 @@ class Definitions {
// force initialization of every symbol that is synthesized or hijacked by the compiler
val forced = syntheticCoreClasses ++ syntheticCoreMethods ++ ScalaValueClasses()

fixTupleCompleter(UnitClass)
for (i <- 1 to MaxTupleArity)
fixTupleCompleter(TupleType(i).symbol.asClass)

isInitialized = true
}
}
Expand Down
Loading