Skip to content

Commit 352c09e

Browse files
authored
Merge pull request #2532 from dotty-staging/ide
IDE support for Dotty via the Language Server Protocol, including a Visual Studio Code extension
2 parents 58a4cc3 + dcdbd8e commit 352c09e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1956
-59
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ project/local-plugins.sbt
1717
.ensime
1818
.ensime_cache/
1919

20+
# npm
21+
node_modules
22+
2023
# Scala-IDE specific
2124
.scala_dependencies
2225
.cache
@@ -28,6 +31,11 @@ project/local-plugins.sbt
2831
classes/
2932
*/bin/
3033

34+
# Dotty IDE
35+
/.dotty-ide-dev-port
36+
/.dotty-ide-artifact
37+
/.dotty-ide.json
38+
3139
# idea
3240
.idea
3341
.idea_modules

build.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ val `dotty-library-bootstrapped` = Build.`dotty-library-bootstrapped`
1212
val `dotty-sbt-bridge` = Build.`dotty-sbt-bridge`
1313
val `dotty-sbt-bridge-bootstrapped` = Build.`dotty-sbt-bridge-bootstrapped`
1414
val `dotty-sbt-scripted-tests` = Build.`dotty-sbt-scripted-tests`
15+
val `dotty-language-server` = Build.`dotty-language-server`
1516
val sjsSandbox = Build.sjsSandbox
1617
val `dotty-bench` = Build.`dotty-bench`
1718
val `scala-library` = Build.`scala-library`
@@ -21,5 +22,6 @@ val scalap = Build.scalap
2122
val dist = Build.dist
2223

2324
val `sbt-dotty` = Build.`sbt-dotty`
25+
val `vscode-dotty` = Build.`vscode-dotty`
2426

2527
inThisBuild(Build.thisBuildSettings)

compiler/src/dotty/tools/backend/jvm/GenBCode.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import Symbols._
2525
import Denotations._
2626
import Phases._
2727
import java.lang.AssertionError
28-
import java.io.{FileOutputStream, File => JFile}
28+
import java.io.{DataOutputStream, File => JFile}
2929

3030
import scala.tools.asm
3131
import scala.tools.asm.tree._
@@ -205,12 +205,16 @@ class GenBCodePipeline(val entryPoints: List[Symbol], val int: DottyBackendInter
205205
val dataAttr = new CustomAttr(nme.TASTYATTR.mangledString, binary)
206206
val store = if (mirrorC ne null) mirrorC else plainC
207207
store.visitAttribute(dataAttr)
208+
val outTastyFile = getFileForClassfile(outF, store.name, ".tasty")
208209
if (ctx.settings.emitTasty.value) {
209-
val outTastyFile = getFileForClassfile(outF, store.name, ".tasty").file
210-
val fos = new FileOutputStream(outTastyFile, false)
211-
fos.write(binary)
212-
fos.close()
213-
210+
val outstream = new DataOutputStream(outTastyFile.bufferedOutput)
211+
212+
try outstream.write(binary)
213+
finally outstream.close()
214+
} else if (!outTastyFile.isVirtual) {
215+
// Create an empty file to signal that a tasty section exist in the corresponding .class
216+
// This is much cheaper and simpler to check than doing classfile parsing
217+
outTastyFile.create()
214218
}
215219
}
216220

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,4 @@ class CompilationUnit(val source: SourceFile) {
1919

2020
/** Pickled TASTY binaries, indexed by class. */
2121
var pickled: Map[ClassSymbol, Array[Byte]] = Map()
22-
23-
var unpicklers: Map[ClassSymbol, TastyUnpickler] = Map()
2422
}

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class Compiler {
129129
val start = bootstrap.fresh
130130
.setOwner(defn.RootClass)
131131
.setTyper(new Typer)
132-
.setMode(Mode.ImplicitsEnabled)
132+
.addMode(Mode.ImplicitsEnabled)
133133
.setTyperState(new MutableTyperState(ctx.typerState, ctx.typerState.reporter, isCommittable = true))
134134
.setFreshNames(new FreshNameCreator.Default)
135135
ctx.initialize()(start) // re-initialize the base context with start

compiler/src/dotty/tools/dotc/FromTasty.scala

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,15 @@ object FromTasty extends Driver {
8383
case clsd: ClassDenotation =>
8484
clsd.infoOrCompleter match {
8585
case info: ClassfileLoader =>
86-
info.load(clsd) match {
87-
case Some(unpickler: DottyUnpickler) =>
88-
val List(unpickled) = unpickler.body(ctx.addMode(Mode.ReadPositions))
86+
info.load(clsd)
87+
val unpickled = clsd.symbol.asClass.tree
88+
if (unpickled != null) {
8989
val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq()))
9090
unit1.tpdTree = unpickled
91-
unit1.unpicklers += (clsd.classSymbol -> unpickler.unpickler)
9291
force.traverse(unit1.tpdTree)
9392
unit1
94-
case _ =>
93+
} else
9594
cannotUnpickle(s"its class file ${info.classfile} does not have a TASTY attribute")
96-
}
9795
case info =>
9896
cannotUnpickle(s"its info of type ${info.getClass} is not a ClassfileLoader")
9997
}

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ object desugar {
555555
val clsRef = Ident(clsName)
556556
val modul = ValDef(moduleName, clsRef, New(clsRef, Nil))
557557
.withMods(mods | ModuleCreationFlags | mods.flags & AccessFlags)
558-
.withPos(mdef.pos)
558+
.withPos(mdef.pos.startPos)
559559
val ValDef(selfName, selfTpt, _) = impl.self
560560
val selfMods = impl.self.mods
561561
if (!selfTpt.isEmpty) ctx.error(ObjectMayNotHaveSelfType(mdef), impl.self.pos)
@@ -785,7 +785,7 @@ object desugar {
785785
/** { label def lname(): Unit = rhs; call }
786786
*/
787787
def labelDefAndCall(lname: TermName, rhs: Tree, call: Tree) = {
788-
val ldef = DefDef(lname, Nil, ListOfNil, TypeTree(defn.UnitType), rhs).withFlags(Label)
788+
val ldef = DefDef(lname, Nil, ListOfNil, TypeTree(defn.UnitType), rhs).withFlags(Label | Synthetic)
789789
Block(ldef, call)
790790
}
791791

compiler/src/dotty/tools/dotc/ast/NavigateAST.scala

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package ast
44
import core.Contexts.Context
55
import core.Decorators._
66
import util.Positions._
7-
import Trees.{MemberDef, DefTree}
7+
import Trees.{MemberDef, DefTree, WithLazyField}
88

99
/** Utility functions to go from typed to untyped ASTs */
1010
object NavigateAST {
@@ -61,8 +61,13 @@ object NavigateAST {
6161
/** The reverse path from node `from` to the node that closest encloses position `pos`,
6262
* or `Nil` if no such path exists. If a non-empty path is returned it starts with
6363
* the node closest enclosing `pos` and ends with `from`.
64+
*
65+
* @param skipZeroExtent If true, skip over zero-extent nodes in the search. These nodes
66+
* do not correspond to code the user wrote since their start and
67+
* end point are the same, so this is useful when trying to reconcile
68+
* nodes with source code.
6469
*/
65-
def pathTo(pos: Position, from: Positioned)(implicit ctx: Context): List[Positioned] = {
70+
def pathTo(pos: Position, from: Positioned, skipZeroExtent: Boolean = false)(implicit ctx: Context): List[Positioned] = {
6671
def childPath(it: Iterator[Any], path: List[Positioned]): List[Positioned] = {
6772
while (it.hasNext) {
6873
val path1 = it.next match {
@@ -75,8 +80,17 @@ object NavigateAST {
7580
path
7681
}
7782
def singlePath(p: Positioned, path: List[Positioned]): List[Positioned] =
78-
if (p.pos contains pos) childPath(p.productIterator, p :: path)
79-
else path
83+
if (p.pos.exists && !(skipZeroExtent && p.pos.isZeroExtent) && p.pos.contains(pos)) {
84+
// FIXME: We shouldn't be manually forcing trees here, we should replace
85+
// our usage of `productIterator` by something in `Positioned` that takes
86+
// care of low-level details like this for us.
87+
p match {
88+
case p: WithLazyField[_] =>
89+
p.forceIfLazy
90+
case _ =>
91+
}
92+
childPath(p.productIterator, p :: path)
93+
} else path
8094
singlePath(from, Nil)
8195
}
82-
}
96+
}

compiler/src/dotty/tools/dotc/ast/Positioned.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,16 @@ abstract class Positioned extends DotClass with Product {
7272
// is known, from left to right.
7373
def fillIn(ps: List[Positioned], start: Int, end: Int): Unit = ps match {
7474
case p :: ps1 =>
75-
p.setPos(Position(start, end))
76-
fillIn(ps1, end, end)
75+
// If a tree has no position or a zero-extent position, it should be
76+
// synthetic. We can preserve this invariant by always setting a
77+
// zero-extent position for these trees here.
78+
if (!p.pos.exists || p.pos.isZeroExtent) {
79+
p.setPos(Position(start, start))
80+
fillIn(ps1, start, end)
81+
} else {
82+
p.setPos(Position(start, end))
83+
fillIn(ps1, end, end)
84+
}
7785
case nil =>
7886
}
7987
while (true) {

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class ScalaSettings extends Settings.SettingGroup {
9898
val YforceSbtPhases = BooleanSetting("-Yforce-sbt-phases", "Run the phases used by sbt for incremental compilation (ExtractDependencies and ExtractAPI) even if the compiler is ran outside of sbt, for debugging.")
9999
val YdumpSbtInc = BooleanSetting("-Ydump-sbt-inc", "For every compiled foo.scala, output the API representation and dependencies used for sbt incremental compilation in foo.inc, implies -Yforce-sbt-phases.")
100100
val YcheckAllPatmat = BooleanSetting("-Ycheck-all-patmat", "Check exhaustivity and redundancy of all pattern matching (used for testing the algorithm)")
101+
val YretainTrees = BooleanSetting("-Yretain-trees", "Retain trees for top-level classes, accessible from ClassSymbol#tree")
101102

102103
/** Area-specific debug output */
103104
val Yexplainlowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.")

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ trait SymDenotations { this: Context =>
6262
|| owner.isRefinementClass
6363
|| owner.is(Scala2x)
6464
|| (owner.unforcedDecls.lookupAll(denot.name) contains denot.symbol)
65-
|| denot.isSelfSym)
65+
|| denot.isSelfSym
66+
|| denot.isLocalDummy)
6667
} catch {
6768
case ex: StaleSymbol => false
6869
}

compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Decorators.{PreNamedString, StringInterpolators}
1717
import classfile.ClassfileParser
1818
import util.Stats
1919
import scala.util.control.NonFatal
20+
import ast.Trees._
2021

2122
object SymbolLoaders {
2223
/** A marker trait for a completer that replaces the original
@@ -320,9 +321,14 @@ class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
320321
override def doComplete(root: SymDenotation)(implicit ctx: Context): Unit =
321322
load(root)
322323

323-
def load(root: SymDenotation)(implicit ctx: Context): Option[ClassfileParser.Embedded] = {
324+
def load(root: SymDenotation)(implicit ctx: Context): Unit = {
324325
val (classRoot, moduleRoot) = rootDenots(root.asClass)
325-
new ClassfileParser(classfile, classRoot, moduleRoot)(ctx).run()
326+
(new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)).run() match {
327+
case Some(unpickler: tasty.DottyUnpickler) if ctx.settings.YretainTrees.value =>
328+
classRoot.symbol.asClass.unpickler = unpickler
329+
moduleRoot.symbol.asClass.unpickler = unpickler
330+
case _ =>
331+
}
326332
}
327333
}
328334

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import DenotTransformers._
2020
import StdNames._
2121
import NameOps._
2222
import NameKinds.LazyImplicitName
23-
import ast.tpd.Tree
23+
import ast.tpd
24+
import tpd.Tree
2425
import ast.TreeTypeMap
2526
import Constants.Constant
2627
import reporting.diagnostic.Message
@@ -552,6 +553,34 @@ object Symbols {
552553

553554
type ThisName = TypeName
554555

556+
/** If this is a top-level class, and if `-Yretain-trees` is set, return the TypeDef tree
557+
* for this class, otherwise EmptyTree.
558+
*/
559+
def tree(implicit ctx: Context): tpd.Tree /* tpd.TypeDef | tpd.EmptyTree */ = {
560+
// TODO: Consider storing this tree like we store lazy trees for inline functions
561+
if (unpickler != null && !denot.isAbsent) {
562+
assert(myTree.isEmpty)
563+
564+
import ast.Trees._
565+
566+
def findTree(tree: tpd.Tree): Option[tpd.TypeDef] = tree match {
567+
case PackageDef(_, stats) =>
568+
stats.flatMap(findTree).headOption
569+
case tree: tpd.TypeDef if tree.symbol == this =>
570+
Some(tree)
571+
case _ =>
572+
None
573+
}
574+
val List(unpickledTree) = unpickler.body(ctx.addMode(Mode.ReadPositions))
575+
unpickler = null
576+
577+
myTree = findTree(unpickledTree).get
578+
}
579+
myTree
580+
}
581+
private[dotc] var myTree: tpd.Tree = tpd.EmptyTree
582+
private[dotc] var unpickler: tasty.DottyUnpickler = _
583+
555584
/** The source or class file from which this class was generated, null if not applicable. */
556585
override def associatedFile(implicit ctx: Context): AbstractFile =
557586
if (assocFile != null || (this.owner is PackageClass) || this.isEffectiveRoot) assocFile

compiler/src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,14 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
314314
case _ =>
315315
tpe
316316
}
317-
tpe.prefix match {
318-
case pre: ThisType if pre.cls is Package => tryInsert(pre.cls)
319-
case pre: TermRef if pre.symbol is Package => tryInsert(pre.symbol.moduleClass)
320-
case _ => tpe
321-
}
317+
if (tpe.symbol.isRoot)
318+
tpe
319+
else
320+
tpe.prefix match {
321+
case pre: ThisType if pre.cls is Package => tryInsert(pre.cls)
322+
case pre: TermRef if pre.symbol is Package => tryInsert(pre.symbol.moduleClass)
323+
case _ => tpe
324+
}
322325
}
323326

324327
/** Normalize a list of parent types of class `cls` that may contain refinements

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -430,8 +430,9 @@ class ClassfileParser(
430430
tag match {
431431
case STRING_TAG =>
432432
if (skip) None else Some(Literal(Constant(pool.getName(index).toString)))
433-
case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | INT_TAG |
434-
LONG_TAG | FLOAT_TAG | DOUBLE_TAG =>
433+
case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG =>
434+
if (skip) None else Some(Literal(pool.getConstant(index, tag)))
435+
case INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG =>
435436
if (skip) None else Some(Literal(pool.getConstant(index)))
436437
case CLASS_TAG =>
437438
if (skip) None else Some(Literal(Constant(pool.getType(index))))
@@ -827,7 +828,12 @@ class ClassfileParser(
827828
def getMember(sym: Symbol, name: Name)(implicit ctx: Context): Symbol =
828829
if (static)
829830
if (sym == classRoot.symbol) staticScope.lookup(name)
830-
else sym.companionModule.info.member(name).symbol
831+
else {
832+
var module = sym.companionModule
833+
if (!module.exists && sym.isAbsent)
834+
module = sym.scalacLinkedClass
835+
module.info.member(name).symbol
836+
}
831837
else
832838
if (sym == classRoot.symbol) instanceScope.lookup(name)
833839
else sym.info.member(name).symbol
@@ -1040,14 +1046,28 @@ class ClassfileParser(
10401046
getClassSymbol(index)
10411047
}
10421048

1043-
def getConstant(index: Int)(implicit ctx: Context): Constant = {
1049+
def getConstant(index: Int, tag: Int = -1)(implicit ctx: Context): Constant = {
10441050
if (index <= 0 || len <= index) errorBadIndex(index)
10451051
var value = values(index)
10461052
if (value eq null) {
10471053
val start = starts(index)
10481054
value = (in.buf(start).toInt: @switch) match {
10491055
case CONSTANT_STRING =>
10501056
Constant(getName(in.getChar(start + 1).toInt).toString)
1057+
case CONSTANT_INTEGER if tag != -1 =>
1058+
val value = in.getInt(start + 1)
1059+
(tag: @switch) match {
1060+
case BOOL_TAG =>
1061+
Constant(value != 0)
1062+
case BYTE_TAG =>
1063+
Constant(value.toByte)
1064+
case CHAR_TAG =>
1065+
Constant(value.toChar)
1066+
case SHORT_TAG =>
1067+
Constant(value.toShort)
1068+
case _ =>
1069+
errorBadTag(tag)
1070+
}
10511071
case CONSTANT_INTEGER =>
10521072
Constant(in.getInt(start + 1))
10531073
case CONSTANT_FLOAT =>

compiler/src/dotty/tools/dotc/core/tasty/DottyUnpickler.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,15 @@ class DottyUnpickler(bytes: Array[Byte]) extends ClassfileParser.Embedded {
4646
def enter(roots: Set[SymDenotation])(implicit ctx: Context): Unit =
4747
treeUnpickler.enterTopLevel(roots)
4848

49+
/** Only used if `-Yretain-trees` is set. */
50+
private[this] var myBody: List[Tree] = _
4951
/** The unpickled trees, and the source file they come from. */
5052
def body(implicit ctx: Context): List[Tree] = {
51-
treeUnpickler.unpickle()
53+
def computeBody() = treeUnpickler.unpickle()
54+
if (ctx.settings.YretainTrees.value) {
55+
if (myBody == null)
56+
myBody = computeBody()
57+
myBody
58+
} else computeBody()
5259
}
5360
}

0 commit comments

Comments
 (0)