Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Issue/semantic highlight type params #179

Merged
merged 11 commits into from

3 participants

@sschaef
Collaborator

This PR adds a lot of semantic highlighting support for type params.

I'm not completely satisfied with the result because the presentation compiler doesn't store enough information in the AST to resolve all problems. Sometimes I had to analyze the AST by myself and add a some additional test cases to be sure that I did not broke anything. However, at the end, with each commit I could improve semantic highlighting.

  • compound types are not completely highlighted (only the type params of parameterized types)
  • nothing in structural types is highlighted but the method names are
  • view bounds were quite difficult but I hope the current solution (+ test cases) is enough.

Please review about

  • commit messages. Are they enough?
  • ignored test cases. Is it good to have them separated from the ones working?
  • my manually AST traverses. Maybe I have overseen a simple solution.
...mantichighlighting/classifier/TypeParameterTest.scala
@@ -70,4 +70,181 @@ class TypeParameterTest extends AbstractSymbolClassifierTest {
""",
Map("TPARAM" -> TypeParameter, "T" -> Type))
}
+
+ @Test
+ def partial_compound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Seq[TypeParam] with collection.IterableLike[TypeParam, TypeParam]
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: Seq[$TPARAM $] with collection.IterableLike[$TPARAM $, $TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
@dotta Owner
dotta added a note

I think you don't need "T" -> Type (wild copy/paste? :-))

@sschaef Collaborator
sschaef added a note

I will remove it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...mantichighlighting/classifier/TypeParameterTest.scala
((13 lines not shown))
+ def xs[$TPARAM $]: Seq[$TPARAM $] with collection.IterableLike[$TPARAM $, $TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
+ }
+
+ @Test
+ @Ignore("does not work until presentation compiler stores more information in the AST")
+ def full_compound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Seq[TypeParam] with collection.IterableLike[TypeParam, TypeParam]
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: $T$[$TPARAM $] with collection.$Trait $[$TPARAM $, $TPARAM $]
@dotta Owner
dotta added a note

This collection.$Trait looks suspicious. But I may be missing something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...mantichighlighting/classifier/TypeParameterTest.scala
((16 lines not shown))
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
+ }
+
+ @Test
+ @Ignore("does not work until presentation compiler stores more information in the AST")
+ def full_compound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Seq[TypeParam] with collection.IterableLike[TypeParam, TypeParam]
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: $T$[$TPARAM $] with collection.$Trait $[$TPARAM $, $TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type, "TRAIT" -> Trait))
@dotta Owner
dotta added a note

Do you need "T" -> Type, "TRAIT" -> Trait?

@sschaef Collaborator
sschaef added a note

Yes, typo error.

@dotta Owner
dotta added a note

Ok, this also explains my above comment ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dotta dotta commented on the diff
...mantichighlighting/classifier/TypeParameterTest.scala
((64 lines not shown))
+ }
+
+ @Test
+ def tuple_literal_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def f: (Int, String)
+ def g: Tuple2[Int, String]
+ }
+ """, """
+ trait X {
+ def f: ($C$, $TYPE$)
+ def g: $CC $[$C$, $TYPE$]
+ }
+ """,
+ Map("TYPE" -> Type, "C" -> Class, "CC" -> CaseClass))
@dotta Owner
dotta added a note

"CC" -> CaseClass not needed.

@sschaef Collaborator
sschaef added a note

def g needs CC. Have you overseen it or do you mean that it is unnecessary to check the tuple class?

@dotta Owner
dotta added a note

Ooops. Sorry, my mistake.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ipse/semantichighlighting/classifier/SafeSymbol.scala
@@ -52,7 +52,7 @@ trait SafeSymbol extends CompilerAccess with PimpedTrees {
// if the original tree did not find anything, we need to call
// symbol, which may trigger type checking of the underlying tree, so we
// wrap it in 'ask'
- if (originalSym.isEmpty) {
+ if (originalSym.isEmpty && tpeTree.pos.isRange) {
@dotta Owner
dotta added a note

Why the check for range position? Could you add a comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ipse/semantichighlighting/classifier/SafeSymbol.scala
((7 lines not shown))
}
+
+ private def isViewBound(args: List[Tree]): Boolean =
+ args.size == 2
+
+ private def isProbableTypeBound(name: Name): Boolean =
+ name.startsWith("evidence$")
+
+ private def isStructuralType(ts: List[Tree]): Boolean =
+ ts.size == 1
+
+ private def isTupleOrFunctionLiteral(tpt: Tree, qualifier: Tree, name: Name): Boolean = (
+ qualifier.nameString == "scala"
+ && (name.toString.startsWith("Function") || name.toString.startsWith("Tuple"))
@dotta Owner
dotta added a note

Why not using a regex, so that the naming match is precise. WDYT?

@sschaef Collaborator
sschaef added a note

Yes, good idea. I didn't think to that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ipse/semantichighlighting/classifier/SafeSymbol.scala
((10 lines not shown))
+
+ case tpe @ SelectFromTypeTree(qualifier, _) =>
+ tpe.symbol -> tpe.namePosition :: safeSymbol(qualifier)
+
+ case CompoundTypeTree(Template(parents, _, body)) =>
+ (if (isStructuralType(parents)) body else parents).flatMap(safeSymbol)
+
+ case TypeBoundsTree(lo, hi) =>
+ List(lo, hi).flatMap(safeSymbol)
+
+ case ValDef(_, name, tpt: TypeTree, _) if isProbableTypeBound(name) =>
+ tpt.original match {
+ case AppliedTypeTree(_, args) if isViewBound(args) =>
+ safeSymbol(args(1))
+ case AppliedTypeTree(tpe, args) if isContextBound(args) =>
+ List(tpe.symbol -> tpe.namePosition)
@dotta Owner
dotta added a note

tpt.symbol can trigger side-effects (i.e., lazy typechecking), you should wrap this in a askOption, i.e., replace List(tpe.symbol -> tpe.namePosition) with global.askOption(() => (tpe.symbol, tpe.namePosition)).toList

@sschaef Collaborator
sschaef added a note

Ok, good to know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ipse/semantichighlighting/classifier/SafeSymbol.scala
((7 lines not shown))
case AppliedTypeTree(tpe, args) =>
- tpe.symbol -> tpe.namePosition :: args.flatMap(safeSymbol)
+ (tpe :: args).flatMap(safeSymbol)
+
+ case tpe @ SelectFromTypeTree(qualifier, _) =>
+ tpe.symbol -> tpe.namePosition :: safeSymbol(qualifier)
@dotta Owner
dotta added a note

tpt.symbol can trigger side-effects (i.e., lazy typechecking), you should wrap this in a askOption, i.e., replace tpe.symbol -> tpe.namePosition :: safeSymbol(qualifier) with global.askOption(() => (tpe.symbol, tpe.namePosition)).toList ::: safeSymbol(qualifier)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ipse/semantichighlighting/classifier/SafeSymbol.scala
((7 lines not shown))
}
+
+ private def isViewBound(args: List[Tree]): Boolean =
+ args.size == 2
+
+ private def isProbableTypeBound(name: Name): Boolean =
+ name.startsWith("evidence$")
@dotta Owner
dotta added a note

If it works, it's better to use nme.EVIDENCE_PARAM_PREFIX. Though, it may not compile with Scala 2.9. (you need to test it :-)). If the name constant is not available in Scala 2.9, keep your code, but add a note so that we remember to get back to this after we drop support for Scala 2.9.

@sschaef Collaborator
sschaef added a note

I executed the 2.9 test suite and all tests succeeded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@dotta
Owner

WOW! This is such an awesome PR, Simon. Great job!

Please review about

* commit messages. Are they enough?

Are there any opened ticket for the issues you have fixed? If yes, remember to have a Fix #<number> in your commit message.

* ignored test cases. Is it good to have them separated from the ones working?

I'm ok with this, however you didn't separate them in this PR, did you?

* my manually AST traverses. Maybe I have overseen a simple solution.

Looks good to me, but I'm not the compiler guru (Iulian is, but he's currently on holiday ;-) \cc @dragos)

@dotta
Owner

Oh, there is one more thing I should ask you to do: can you rebase your PR on the latest master? The reason is that 2.10 build should then succeed.

  • Update: Actually, you may not need to do this, the PR validator was not in a clean state. Let me try to re-build
@dotta
Owner

And I noticed another thing: In this commit message you say:

The presentation compiler doesn't store enough information in the AST in order to exactly determine if a view bound is found

The one to blame is not the presentation compiler, but the actual compiler itself ;-) (you may want to update the message)

@dotta
Owner

PLS REBUILD ALL

@sschaef
Collaborator

I didn't push the amends (I did only amends no new commits) yet. Can I do this normally with git push origin issue/semantic-highlight-ype-params or will this break some comments above (pointing to line numbers)?

@dotta
Owner

No luck, your branch is based on a old master that does no longer compile because of binary incompatibilities in sbt (which we use as the IDE internal project's builder). You'll have to rebase...

@sschaef
Collaborator

The one to blame is not the presentation compiler, but the actual compiler itself ;-) (you may want to update the message)

I don't know where one ends and the other begins. ;) I thought the typer phase (the phase I working with in semantic highlighting?) is only done by the presentation compiler.

@dotta
Owner

I didn't push the amends (I did only amends no new commits) yet. Can I do this normally with git push origin issue/semantic-highlight-ype-params or will this break some comments above (pointing to line numbers)?

When you amend a commit, you are changing the history of your branch, and hence pushing will fail (you can always safely try out git commands by appending --dry-run). What you can do is a force push (i.e., git push -f origin issue/semantic-highlight-type-params), and this PR will be automatically updated. If you do so, make sure to go through all comments first, I'm not sure what will happen to the comments once you force push the branch (they are not deleted, but they can get into some weird unsync state and no longer be linked to a code line).

@sschaef
Collaborator

No luck, your branch is based on a old master that does no longer compile because of binary incompatibilities in sbt (which we use as the IDE internal project's builder). You'll have to rebase...

How to do this? A normal

git checkout master
git fetch upstream
git merge upstream/master
git checkout issue/semantic-highlighting-type-params
git rebase master

did not work (all up to date).

@sschaef
Collaborator

Are there any opened ticket for the issues you have fixed? If yes, remember to have a Fix # in your commit message.

No, there aren't. I was to lazy to create one. Is it useful to do it?

I'm ok with this, however you didn't separate them in this PR, did you?

With separating I meant separating them into an own test case.

@dotta
Owner

I don't know where one ends and the other begins. ;) I thought the typer phase (the phase I working with in semantic highlighting?) is only done by the presentation compiler.

That's a good question :-)

The presentation compiler executes only a subset of phases compared to the standard scala compiler, but these phases are the very same. The only real difference between the two is that a presentation compiler pass stops at the typer phase, instead of going until bytecode generation.

The main responsability of the presentation compiler is providing an interface for communicating with the scala compiler.

@dotta
Owner
  • About the rebasing, your commands looks good to me. Since it failed, try to go with an interactive rebase, i.e., git rebase -i master. You may need to solve some conflict, but I'd expect this to be easy for this PR.

  • It would be good to create at least one ticket with the list of functionalities you have implemented. This helps us creating a meaningful changelog when we release (so that people can know what has improved in the release)

With separating I meant separating them into an own test case.

What's a test case, a method? a class? Sorry, I'm having troubles following this. But if you want to split working and ignored tests in two separated test classes, I'm fine with this, as long as you add both classes in the TestSuite

sschaef added some commits
@sschaef sschaef Add semantic highlighting for view bounds
The compiler doesn't store enough information in the AST in order to
exactly determine if a view bound is found. Thus, it is necessary to
analyze the members of the tree types manually which is error-prone.
To be sure that no bugs are caused, additional test cases were introduced.
9c00a68
@sschaef sschaef Add test case for higher kinded types to semantic highlighting test s…
…uite
3327343
@sschaef sschaef Add semantic highlighting for existential types 00e131a
@sschaef sschaef Add semantic highlighting for type projections and path dependent types cee80b8
@sschaef
Collaborator

PLS REBUILD ALL

@sschaef
Collaborator

git rebase -i master works fine, thanks.

What's a test case, a method?

Yes, I meant a method. Sry for the wrong word. I'll let it as it is, it is ok.

@dotta
Owner

Yes, I meant a method. Sry for the wrong word. I'll let it as it is, it is ok.

Perfect. So, the only thing that is missing is a ticket that lists all your changes.

@dotta
Owner

I also wonder if there isn't a more precise way to check isViewBound, isStructuralType, isContextBound and similar. I'll ask the EPFL folks and get back to you during the day.

@dotta
Owner

I also wonder if there isn't a more precise way to check isViewBound, isStructuralType, isContextBound and similar. I'll ask the EPFL folks and get back to you during the day.

I checked and apparently there is no better way, so it's all good. As soon as you create a ticket for this, we can merge this PR.

@sschaef
Collaborator

As soon as you create a ticket for this, we can merge this PR.

Done: http://www.assembla.com/spaces/scala-ide/tickets/1001204

Does it provide enough information or should I add more (links to PR for example)?

@dotta dotta merged commit 3f934f8 into from
@dotta
Owner

Fantastic! I have merged and updated the ticket. Keep on rockin' :-)

@dotta
Owner

I see now we have quite a few open tickets for Semantich Highlighting: http://scala-ide-portfolio.assembla.com/spaces/ae55a-oWSr36hpeJe5avMc/tickets/report/u362933

Are you sure that none of them can be closed after your PR? (or, if you are having fun with semantic highlighting, you could have a look at them ;-))

@sschaef
Collaborator

I already looked at these tickets, but this PR doesn't solve any one them. Half of the tickets there are submitted by me and I spent already some time on them, but again it was the Scala compiler which doesn't store enough information in the AST. Thus, I could not solve the problems. :(

I will going on working on semantic highlighting. Hopefully I will find a ticket which can be solved...

@dotta
Owner

I already looked at these tickets, but this PR doesn't solve any one them. Half of the tickets there are submitted by me and I spent already some time on them, but again it was the Scala compiler which doesn't store enough information in the AST. Thus, I could not solve the problems. :(

Eh eh eh, I did not pay attention to the fact that it was you that filed the tickets :-) And you are absolutely right, the compiler AST doesn't always provide enough information, and that's why semantic highlight also use the scalariform AST (did you noticed it?). Maybe with the scalariform AST you can find the missing information (it's something to investigate if you have time and will).

I will going on working on semantic highlighting. Hopefully I will find a ticket which can be solved...

I know we're nowhere near XMas, but here is my wish list :-)

@sschaef
Collaborator

What shall I do with my local branch after the merge? Shall I delete it?

@sschaef
Collaborator

and that's why semantic highlight also use the scalariform AST (did you noticed it?).

Yeah, I noticed it, but didn't understand why it is there ;)

Maybe with the scalariform AST you can find the missing information (it's something to investigate if you have time and will).

Let's see what I can do.

@dotta
Owner

What shall I do with my local branch after the merge? Shall I delete it?

There is really no reason to keep them around ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 12, 2012
  1. @sschaef
  2. @sschaef
  3. @sschaef
  4. @sschaef
  5. @sschaef
  6. @sschaef
  7. @sschaef

    Update of `RegionParser`

    sschaef authored
    The previous version of `RegionParser` was unable to parse regions which
    contain the delimiter sign. Now it is possible to put delimiter signs to
    the parsed text by prefixing them with an escape sign.
  8. @sschaef

    Add semantic highlighting for view bounds

    sschaef authored
    The compiler doesn't store enough information in the AST in order to
    exactly determine if a view bound is found. Thus, it is necessary to
    analyze the members of the tree types manually which is error-prone.
    To be sure that no bugs are caused, additional test cases were introduced.
  9. @sschaef
  10. @sschaef
  11. @sschaef
This page is out of date. Refresh to see the latest.
View
72 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/semantichighlighting/classifier/RegionParser.scala
@@ -1,27 +1,57 @@
package scala.tools.eclipse.semantichighlighting.classifier
-/**
- * Search for regions delimited with $ signs.
- */
object RegionParser {
- def getRegions(text: String): Map[Region, String] = {
- var regions = Map[Region, String]()
- var regionStartOpt: Option[Int] = None
+ /**
+ * Search for regions delimited with a sign. In the default case the
+ * delimited sign is a '$'.
+ *
+ * It is possible to put delimiter signs into the text by escaping them with
+ * a '\'.
+ *
+ * @example {{{
+ * scala> getRegions("""$abc$ def $ghi$""")
+ * res200: Map[Region,String] = Map(Region(0,5) -> abc, Region(10,5) -> ghi)
+ *
+ * scala> getRegions("""$a\$bc$ de\$f $ghi$""")
+ * res201: Map[Region,String] = Map(Region(0,6) -> a$bc, Region(12,5) -> ghi)
+ *
+ * scala> getRegions("""|a\|bc| de\|f |ghi|""", delimiter = '|')
+ * res202: Map[Region,String] = Map(Region(0,6) -> a|bc, Region(12,5) -> ghi)
+ * }}}
+ * '''Note:''' When a delimiter sign is escaped, the resulting `Region` instances
+ * are handled as there were no escape sign. This means that the String `$a\$b$`
+ * is treated as `$a$b$`.
+ */
+ def getRegions(text: String, delimiter: Char = '$'): Map[Region, String] = {
+ val sb = new StringBuilder
+ var curPos = 0
+ var offset = 0
+ var regions = Map.empty[Region, String]
- for (('$', pos) <- text.zipWithIndex)
- regionStartOpt match {
- case Some(regionStart) =>
- regionStartOpt = None
- val region = Region(regionStart, pos - regionStart + 1)
- val regionLabel = text.substring(regionStart + 1, pos).trim
- regions += (region -> regionLabel)
- case None =>
- regionStartOpt = Some(pos)
- }
-
- require(regionStartOpt.isEmpty)
- regions
+ while (curPos < text.length) {
+ text.charAt(curPos) match {
+ case '\\' if text.charAt(curPos+1) == delimiter =>
+ if (!sb.isEmpty)
+ sb += delimiter
+ offset += 1
+ curPos += 1
+ case `delimiter` =>
+ if (sb.isEmpty)
+ sb += text.charAt(curPos)
+ else {
+ val start = curPos-sb.length
+ val label = sb.substring(1, sb.length).trim
+ sb.clear()
+ regions += (Region(start-offset, curPos-start+1) -> label)
+ }
+ case _ =>
+ if (!sb.isEmpty)
+ sb += text.charAt(curPos)
+ }
+ curPos += 1
}
-
- }
+ require(sb.isEmpty, "odd number of '"+delimiter+"' signs in text")
+ regions
+ }
+}
View
177 ...scala-ide.sdt.core.tests/src/scala/tools/eclipse/semantichighlighting/classifier/TypeParameterTest.scala
@@ -70,4 +70,181 @@ class TypeParameterTest extends AbstractSymbolClassifierTest {
""",
Map("TPARAM" -> TypeParameter, "T" -> Type))
}
+
+ @Test
+ def partial_compound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Seq[TypeParam] with collection.IterableLike[TypeParam, TypeParam]
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: Seq[$TPARAM $] with collection.IterableLike[$TPARAM $, $TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter))
+ }
+
+ @Test
+ @Ignore("does not work until presentation compiler stores more information in the AST")
+ def full_compound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Seq[TypeParam] with collection.IterableLike[TypeParam, TypeParam]
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: $T$[$TPARAM $] with collection.$TRAIT $[$TPARAM $, $TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type, "TRAIT" -> Trait))
+ }
+
+ @Test
+ def context_bound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[A : Ordering](a: A)
+ def ys[TypeParam](a: TypeParam)(implicit evidence$1: Ordering[TypeParam])
+ }
+ """, """
+ trait X {
+ def xs[A : $TYPE $](a: A)
+ def ys[$TPARAM $](a: $TPARAM $)(implicit evidence\$1: $TYPE $[$TPARAM $])
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "TYPE" -> Type))
+ }
+
+ @Test
+ def function_literal_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def f: Int => String
+ def g: Function1[Int, String]
+ }
+ """, """
+ trait X {
+ def f: $C$ => $TYPE$
+ def g: $TRAIT $[$C$, $TYPE$]
+ }
+ """,
+ Map("TYPE" -> Type, "C" -> Class, "TRAIT" -> Trait))
+ }
+
+ @Test
+ def tuple_literal_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def f: (Int, String)
+ def g: Tuple2[Int, String]
+ }
+ """, """
+ trait X {
+ def f: ($C$, $TYPE$)
+ def g: $CC $[$C$, $TYPE$]
+ }
+ """,
+ Map("TYPE" -> Type, "C" -> Class, "CC" -> CaseClass))
@dotta Owner
dotta added a note

"CC" -> CaseClass not needed.

@sschaef Collaborator
sschaef added a note

def g needs CC. Have you overseen it or do you mean that it is unnecessary to check the tuple class?

@dotta Owner
dotta added a note

Ooops. Sorry, my mistake.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ }
+
+ @Test
+ def partial_structural_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def f(s: { def foo[TypeParam](i: TypeParam): Int })
+ }
+ """, """
+ trait X {
+ def f(s: { def $M$[TypeParam](i: TypeParam): Int })
+ }
+ """,
+ Map("M" -> Method))
+ }
+
+ @Test
+ @Ignore("does not work until presentation compiler stores more information in the AST")
+ def full_structural_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def f(s: { def foo[TypeParam](i: TypeParam): Int })
+ }
+ """, """
+ trait X {
+ def f(s: { def $M$[$TPARAM $](i: $TPARAM $): $C$ })
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "C" -> Class, "M" -> Method))
+ }
+
+ @Test
+ def bounded_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ type Type[TypeParam] >: List[TypeParam] <: Iterable[TypeParam]
+ }
+ """, """
+ trait X {
+ type $T $[$TPARAM $] >: $T $[$TPARAM $] <: $T $[$TPARAM $]
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
+ }
+
+ @Test
+ def view_bound_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam <% Ordering[TypeParam]](a: TypeParam)
+ def ys[TypeParam](a: TypeParam)(implicit evidence$1: TypeParam => Ordering[TypeParam])
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $ <% $TYPE $[$TPARAM $]](a: $TPARAM $)
+ def ys[$TPARAM $](a: $TPARAM $)(implicit evidence\$1: $TPARAM $ => $TYPE $[$TPARAM $])
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "TYPE" -> Type))
+ }
+
+ @Test
+ def higher_kinded_type_param() {
+ checkSymbolClassification("""
+ trait M[A[_]]
+ trait H extends M[List]
+ """, """
+ trait M[A[_]]
+ trait H extends M[$HK$]
+ """,
+ Map("HK" -> Type))
+ }
+
+ @Test
+ def partial_existential_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Res[TypeParam] forSome { type Res[_] <: Seq[_] }
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: $T$[$TPARAM $] forSome { type $T$[_] <: Seq[_] }
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
+ }
+
+ @Test
+ @Ignore("does not work until presentation compiler stores more information in the AST")
+ def full_existential_type_param() {
+ checkSymbolClassification("""
+ trait X {
+ def xs[TypeParam]: Res[TypeParam] forSome { type Res[_] <: Seq[_] }
+ }
+ """, """
+ trait X {
+ def xs[$TPARAM $]: $T$[$TPARAM $] forSome { type $T$[_] <: $T$[_] }
+ }
+ """,
+ Map("TPARAM" -> TypeParameter, "T" -> Type))
+ }
}
View
51 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/semantichighlighting/classifier/TypeTest.scala
@@ -43,39 +43,36 @@ class TypeTest extends AbstractSymbolClassifierTest {
""",
Map("T" -> Type, "V" -> TemplateVal, "C" -> Class))
}
-
+
@Test
- @Ignore
- def classify_existential_type_1() {
- checkSymbolClassification(
- """
- object O {
- def m(x : t forSome {type t <: AnyRef}) = x
+ def path_dependent_type() {
+ checkSymbolClassification("""
+ trait MTrait { trait KTrait[A] }
+ trait X {
+ def xs(m: MTrait)(k: m.KTrait[Int])
}
- """",
- """
- object O {
- def m(x : t forSome {type t <: $ TPE$ }) = x
+ """, """
+ trait MTrait { trait KTrait[A] }
+ trait X {
+ def xs(m: $TT $)(k: m.$TT $[$C$])
}
- """",
- Map("TPE" -> Type))
+ """,
+ Map("C" -> Class, "TT" -> Trait))
}
-
+
@Test
- @Ignore
- def classify_existential_type_2() {
- checkSymbolClassification(
- """
- object O {
- def m(x : t forSome {type t <: List [_]}) = x
+ def type_projection() {
+ checkSymbolClassification("""
+ trait MTrait { trait KTrait[A] }
+ trait X {
+ def xs(m: MTrait#KTrait[Int])
}
- """",
- """
- object O {
- def m(x : t forSome {type t <: $TPE$[_]}) = x
+ """, """
+ trait MTrait { trait KTrait[A] }
+ trait X {
+ def xs(m: $TT $#$TT $[$C$])
}
- """",
- Map("TPE" -> Type))
+ """,
+ Map("C" -> Class, "TT" -> Trait))
}
-
}
View
65 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SafeSymbol.scala
@@ -40,7 +40,7 @@ trait SafeSymbol extends CompilerAccess with PimpedTrees {
* TransparentPositions come into play for trees that don't have a source-code
* correspondence but still have children that are visible in the source.
*/
- protected def isSourceTree(t: Tree): Boolean = t.pos.isRange && !t.pos.isTransparent
+ protected def isSourceTree(t: Tree): Boolean = hasSourceCodeRepresentation(t) && !t.pos.isTransparent
protected def safeSymbol(t: Tree): List[(Symbol, Position)] = t match {
@@ -52,7 +52,7 @@ trait SafeSymbol extends CompilerAccess with PimpedTrees {
// if the original tree did not find anything, we need to call
// symbol, which may trigger type checking of the underlying tree, so we
// wrap it in 'ask'
- if (originalSym.isEmpty) {
+ if (originalSym.isEmpty && hasSourceCodeRepresentation(tpeTree)) {
val tpeSym = global.askOption(() => Option(t.symbol)).flatten.toList
tpeSym.zip(List(tpeTree.namePosition))
} else originalSym
@@ -76,8 +76,33 @@ trait SafeSymbol extends CompilerAccess with PimpedTrees {
else List((sym1, pos))
}).flatten
+ case AppliedTypeTree(tpe, args) if isTupleOrFunctionLiteral(tpe) =>
+ args.flatMap(safeSymbol)
+
case AppliedTypeTree(tpe, args) =>
- tpe.symbol -> tpe.namePosition :: args.flatMap(safeSymbol)
+ (tpe :: args).flatMap(safeSymbol)
+
+ case tpe @ SelectFromTypeTree(qualifier, _) =>
+ global.askOption(() => tpe.symbol -> tpe.namePosition).toList ::: safeSymbol(qualifier)
+
+ case CompoundTypeTree(Template(parents, _, body)) =>
+ (if (isStructuralType(parents)) body else parents).flatMap(safeSymbol)
+
+ case TypeBoundsTree(lo, hi) =>
+ List(lo, hi).flatMap(safeSymbol)
+
+ case ValDef(_, name, tpt: TypeTree, _) if isProbableTypeBound(name) =>
+ tpt.original match {
+ case AppliedTypeTree(_, args) if isViewBound(args) =>
+ safeSymbol(args(1))
+ case AppliedTypeTree(tpe, args) if isContextBound(args) =>
+ global.askOption(() => tpe.symbol -> tpe.namePosition).toList
+ case tpt =>
+ safeSymbol(tpt)
+ }
+
+ case ExistentialTypeTree(tpt, whereClauses) =>
+ (tpt :: whereClauses).flatMap(safeSymbol)
case _ =>
// the local variable backing a lazy value is called 'originalName$lzy'. We swap it here for its
@@ -87,6 +112,38 @@ trait SafeSymbol extends CompilerAccess with PimpedTrees {
else sym
}.toList
- sym1.zip(List(t.namePosition))
+ if (!hasSourceCodeRepresentation(t)) Nil
+ else sym1.zip(List(t.namePosition))
}
+
+ private def isViewBound(args: List[Tree]): Boolean =
+ args.size == 2
+
+ private def isProbableTypeBound(name: Name): Boolean =
+ name.startsWith(nme.EVIDENCE_PARAM_PREFIX)
+
+ private def isStructuralType(ts: List[Tree]): Boolean =
+ ts.size == 1
+
+ private def isTupleOrFunctionLiteral(tpt: Tree): Boolean =
+ tpt.toString.matches(""".*scala\.(Tuple|Function).*""") && !hasSourceCodeRepresentation(tpt)
+
+ private def isContextBound(args: List[Tree]): Boolean =
+ args.size == 1 && !hasSourceCodeRepresentation(args.head)
+
+ /*
+ * Sometimes the compiler enrich the AST with some trees not having a source
+ * code representation. This is true for tuple or function literals, for
+ * view and context bounds and others.
+ *
+ * The problem is that such trees don't have a `Range` because these trees are
+ * generated by the compiler to have a representation for symbols not written
+ * directly into the source code (but written in form of there corresponding
+ * literals).
+ *
+ * Thus, calling the position of such a tree results in an exception. To avoid
+ * this exception one needs to call this method to be on the safe side.
+ */
+ private def hasSourceCodeRepresentation(tpt: Tree): Boolean =
+ tpt.pos.isRange
}
Something went wrong with that request. Please try again.