Skip to content

Commit

Permalink
Add semantic highlighting for type Dynamic
Browse files Browse the repository at this point in the history
Calls like `d.foo(1)` or `d.foo(param = 1)` are treated as method
calls whereas calls like `d.foo` or `d.foo = 1` are treated as vars.

It can be that a call to `d.foo` must not be a mutable field but an
immutable one when no corresponding call to `updateDynamic` is done
before. However, in such a case `foo` is highlighted as a var becuase
it is impossible for the IDE to detect such runtime informations.

Fixes #1001656
  • Loading branch information
kiritsuku committed Apr 5, 2013
1 parent 5fce8d9 commit a9fb5df
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package scala.tools.eclipse.semantichighlighting.classifier

import SymbolTypes._
import org.junit._

class DynamicTest extends AbstractSymbolClassifierTest {

@Test
def selectDynamic() {
checkSymbolClassification("""
object X {
(new D).field
}
import language.dynamics
class D extends Dynamic {
def selectDynamic(name: String) = name
}
""", """
object X {
(new D).$VAR$
}
import language.dynamics
class D extends Dynamic {
def selectDynamic(name: String) = name
}
""",
Map("VAR" -> TemplateVar))
}

@Test
def updateDynamic() {
checkSymbolClassification("""
object X {
val d = new D
d.field = 10
d.field
}
import language.dynamics
class D extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) =
map(name)
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
}
""", """
object X {
val d = new D
d.$VAR$ = 10
d.$VAR$
}
import language.dynamics
class D extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) =
map(name)
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
}
""",
Map("VAR" -> TemplateVar))
}

@Test
def applyDynamic() {
checkSymbolClassification("""
object X {
val d = new D
d.method(10)
d(10)
}
import language.dynamics
class D extends Dynamic {
def applyDynamic(name: String)(value: Any) = name
}
""", """
object X {
val d = new D
d.$METH$(10)
d(10)
}
import language.dynamics
class D extends Dynamic {
def applyDynamic(name: String)(value: Any) = name
}
""",
Map("METH" -> Method))
}

@Test
def applyDynamicNamed() {
checkSymbolClassification("""
object X {
val d = new D
d.method(value = 10)
}
import language.dynamics
class D extends Dynamic {
def applyDynamicNamed(name: String)(value: (String, Any)) = name
}
""", """
object X {
val d = new D
d.$METH$($ARG$ = 10)
}
import language.dynamics
class D extends Dynamic {
def applyDynamicNamed(name: String)(value: (String, Any)) = name
}
""",
Map("METH" -> Method, "ARG" -> Param))
}

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package scala.tools.eclipse.semantichighlighting.classifier

import scala.tools.refactoring.common.CompilerAccess
import scala.tools.refactoring.common.PimpedTrees
import scala.tools.eclipse.ScalaPresentationCompiler
import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes._
import scala.tools.nsc.util.SourceFile
import scala.tools.refactoring.common.{ CompilerAccess, PimpedTrees }

/**
* Return the Symbols corresponding to this `Tree`, if any.
Expand Down Expand Up @@ -42,6 +42,29 @@ private[classifier] trait SafeSymbol extends CompilerAccess with PimpedTrees {
*/
protected def isSourceTree(t: Tree): Boolean = hasSourceCodeRepresentation(t) && !t.pos.isTransparent

// copy-pasted from scala.reflect.internal.Names because it is only available in 2.11
// TODO delete once the code base has moved to 2.11
private object TermName {
def apply(s: String) = newTermName(s)
def unapply(name: TermName): Option[String] = Some(name.toString)
}

/**
* Finds all occurrences of dynamic method calls. Because such method calls are
* transformed by the compiler, no symbols exist for them. Thus, this method
* returns the SymbolType directly.
*/
protected def findDynamicSymbols(t: Tree): List[(SymbolType, Position)] = t match {

case Apply(Select(_, TermName("selectDynamic" | "updateDynamic")), List(name)) =>
List(TemplateVar -> name.pos)

case Apply(Select(_, TermName("applyDynamic" | "applyDynamicNamed")), List(name)) =>
List(Method -> name.pos)

case _ => Nil
}

protected def safeSymbol(t: Tree): List[(Symbol, Position)] = t match {

case tpeTree: TypeTree =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
extends SafeSymbol with TypeTreeTraverser with SymbolClassificationDebugger with SymbolTests with HasLogger {

import SymbolClassification._
import global.{ Symbol, Position, NoSymbol }
import global.{ Symbol, Position, NoSymbol, Tree }

def compilationUnitOfFile(f: AbstractFile) = global.unitOfFile.get(f)

Expand Down Expand Up @@ -103,7 +103,28 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
val symbolInfosFromSyntax = getSymbolInfosFromSyntax(syntacticInfo, localVars, all)
if (progressMonitor.isCanceled()) return Nil

(symbolInfosFromSyntax ++ prunedSymbolInfos) filter { _.regions.nonEmpty } distinct
val dynamicSymbolInfos = getDynamicSymbolInfos(tree)
if (progressMonitor.isCanceled()) return Nil

(symbolInfosFromSyntax ++ prunedSymbolInfos ++ dynamicSymbolInfos) filter { _.regions.nonEmpty } distinct
}

private def getDynamicSymbolInfos(tree: Tree): Seq[SymbolInfo] = {

def posToRegion(p: Position) = new Region(p.start, p.end - p.start)

val symbols = for {
t <- tree
// Tree doesn't support a map operation
if true
sym <- findDynamicSymbols(t)
} yield sym

List(
SymbolInfo(Method, symbols collect { case (Method, r) => posToRegion(r) }, deprecated = false),
SymbolInfo(TemplateVar, symbols collect { case (TemplateVar, r) => posToRegion(r) }, deprecated = false),
SymbolInfo(Param, symbols collect { case (Param, r) => posToRegion(r) }, deprecated = false)
)
}

private def getSymbolInfo(sym: Symbol, poss: List[Position]): SymbolInfo = {
Expand Down

0 comments on commit a9fb5df

Please sign in to comment.