Skip to content

Instantly share code, notes, and snippets.

@mandubian
Last active October 17, 2017 10:54
Show Gist options
  • Save mandubian/03cec82b6ab4433d8989dc37e550d9a7 to your computer and use it in GitHub Desktop.
Save mandubian/03cec82b6ab4433d8989dc37e550d9a7 to your computer and use it in GitHub Desktop.
Scalameta AST diffing enriched by Semantic Database
// Create ScalaMeta Semantic DB from previous 2 files
val sdb = SemanticdbIndex.load(Sourcepath(BuildInfo.sourcepath), Classpath(BuildInfo.classpath))
// Print SDB info (always interesting)
println(sdb.database)
// Retrieve source document
val src = sdb.documents(0)
// Build source tree (for now, there is no API to do that more straightforward
val srcTree = src.input.text.parse[Source].get.children.head.children.tail.map(TreeNode(_))
println(s"srcTree:\n${srcTree}")
/*
srcTree:
List(TreeNode(object Test {
val a = 3
case class Foo(a: String)
def f(foo: Foo): String = foo.a
}))
*/
// Build target document
val target = sdb.documents(1)
// Build target tree (for now, there is no API to do that more straightforward
val targetTree = target.input.text.parse[Source].get.children.head.children.tail.map(TreeNode(_))
println(s"target:\n${targetTree}")
/*
target:
List(TreeNode(object Test {
val b = 2
case class Bar(a: Int)
def f(foo: Bar): Int = foo.a
}))
*/
// Compute Diff of both trees
val diff = Diff(srcTree, targetTree)
println(s"diff:\n${diff}")
/*
diff:
Cpy(ObjectType(Defn.Object),Cpy(TreeListFieldLabel(mods,0),Cpy(SimpleFieldLabel(name),Cpy(ValueLabel(TreeNode(Test)),Cpy(SimpleFieldLabel(templ),Cpy(ObjectType(Template),Cpy(TreeListFieldLabel(early,0),Cpy(TreeListFieldLabel(inits,0),Cpy(SimpleFieldLabel(self),Cpy(ValueLabel(TreeNode()),Cpy(TreeListFieldLabel(stats,3),Cpy(ObjectType(Defn.Val),Cpy(TreeListFieldLabel(mods,0),Cpy(TreeListFieldLabel(pats,1),Cpy(ObjectType(Pat.Var),Cpy(SimpleFieldLabel(name),Del(ValueLabel(TreeNode(a)),Ins(ValueLabel(TreeNode(b)),Cpy(TreeOptionFieldLabel(decltpe,false),Cpy(SimpleFieldLabel(rhs),Del(ValueLabel(TreeNode(3)),Ins(ValueLabel(TreeNode(2)),Cpy(ObjectType(Defn.Class),Cpy(TreeListFieldLabel(mods,1),Cpy(ValueLabel(TreeNode(case)),Cpy(SimpleFieldLabel(name),Del(ValueLabel(TreeNode(Foo)),Ins(ValueLabel(TreeNode(Bar)),Cpy(TreeListFieldLabel(tparams,0),Cpy(SimpleFieldLabel(ctor),Cpy(ObjectType(Ctor.Primary),Cpy(TreeListFieldLabel(mods,0),Cpy(SimpleFieldLabel(name),Cpy(ValueLabel(TreeNode()),Cpy(TreeListListFieldLabel(paramss,1),Cpy(TreeListFieldLabel(paramss,1),Cpy(ObjectType(Term.Param),Cpy(TreeListFieldLabel(mods,0),Cpy(SimpleFieldLabel(name),Cpy(ValueLabel(TreeNode(a)),Cpy(TreeOptionFieldLabel(decltpe,true),Del(ValueLabel(TreeNode(String)),Ins(ValueLabel(TreeNode(Int)),Cpy(TreeOptionFieldLabel(default,false),Cpy(SimpleFieldLabel(templ),Cpy(ValueLabel(TreeNode()),Cpy(ObjectType(Defn.Def),Cpy(TreeListFieldLabel(mods,0),Cpy(SimpleFieldLabel(name),Cpy(ValueLabel(TreeNode(f)),Cpy(TreeListFieldLabel(tparams,0),Cpy(TreeListListFieldLabel(paramss,1),Cpy(TreeListFieldLabel(paramss,1),Cpy(ObjectType(Term.Param),Cpy(TreeListFieldLabel(mods,0),Cpy(SimpleFieldLabel(name),Cpy(ValueLabel(TreeNode(foo)),Cpy(TreeOptionFieldLabel(decltpe,true),Del(ValueLabel(TreeNode(Foo)),Ins(ValueLabel(TreeNode(Bar)),Cpy(TreeOptionFieldLabel(default,false),Cpy(TreeOptionFieldLabel(decltpe,true),Del(ValueLabel(TreeNode(String)),Ins(ValueLabel(TreeNode(Int)),Cpy(SimpleFieldLabel(body),Cpy(ValueLabel(TreeNode(foo.a)),End))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
*/
// Test patching source using diff
val patched = Diff.patch(diff, srcTree)
println(s"patched:\n${patched}")
/*
patched:
Right(List(TreeNode(object Test {
val b = 2
case class Bar(a: Int)
def f(foo: Bar): Int = foo.a
})))
*/
// Map Semantic-Diff tree symbols into their semantic counterparts
val sem0 = toSemantic(sdb, src.input, target.input, diff, fullDetailsFrom = false, fullDetailsTo = false)
// Extracts Source from Semantic-Diff
val Right(List(TreeNode(resrc))) = Diff.extractSource(sem0, List())
println(s"resrc:\n${resrc}")
/*
resrc:
object Test {
val _root_.toto.Test.a = 3
case class _root_.toto.Test.Foo(a: _root_.scala.Predef.String)
def f(foo: _root_.toto.Test.Foo): _root_.scala.Predef.String = foo.a
}
*/
// Extracts Target from Semantic-Diff
val Right(List(TreeNode(retarget))) = Diff.extractTarget(sem0, List())
println(s"retarget:\n${retarget}")
/*
retarget:
object Test {
val _root_.tata.Test.b = 2
case class _root_.tata.Test.Bar(a: _root_.scala.Int)
def f(foo: _root_.tata.Test.Bar): _root_.scala.Int = foo.a
}
*/
// Map Semantic-Diff tree symbols into their semantic counterparts with all denotation details
val sem = toSemantic(sdb, src.input, target.input, diff, fullDetailsFrom = true, fullDetailsTo = false)
// Visualize a syntax of Semantic Diff using semantic denotation info
val diffSyntax = DiffSyntax(scala.meta.dialects.Scala212)(sem)
println(s"diffSyntax:\n${diffSyntax}")
/*
diffSyntax:
*/
object Test {
val /*FROM `val _root_.toto.Test.a: Int` TO*/ _root_.tata.Test.b = /*FROM 3 TO*/ 2
case class /*FROM `case class _root_.toto.Test.Foo` TO*/ _root_.tata.Test.Bar(a: /*FROM `type _root_.scala.Predef.String: String` TO*/ _root_.scala.Int)
def f(foo: /*FROM `case class _root_.toto.Test.Foo` TO*/ _root_.tata.Test.Bar): /*FROM `type _root_.scala.Predef.String: String` TO*/ _root_.scala.Int = foo.a
}
package toto
object Test {
val a = 3
case class Foo(a: String)
def f(foo: Foo): String = foo.a
}
package tata
object Test {
val b = 2
case class Bar(a: Int)
def f(foo: Bar): Int = foo.a
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment