Skip to content
This repository was archived by the owner on Sep 3, 2020. It is now read-only.

Implement methods #199

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package scala.tools.refactoring.implementations

import scala.tools.refactoring.common.InteractiveScalaCompiler
import scala.tools.refactoring.transformation.TreeFactory
import scala.tools.refactoring.{MultiStageRefactoring, analysis}

abstract class ImplementMethods extends MultiStageRefactoring with analysis.Indexes with TreeFactory with InteractiveScalaCompiler {

import global._

case class PreparationResult(targetTemplate: Template, memberToImplement: Seq[MemberDef])

/* Helper class to box members so they can be
compared in terms of their signature.
*/
implicit class OverloadedMember(val member: MemberDef) {

private val key = member match {
case method: DefDef =>
import method.{name, tparams, vparamss}

val vparamsTypes = for {
paramList <- vparamss
param <- paramList
} yield param.tpt.tpe

(name, tparams, vparamsTypes)

case ValDef(_, name, _, _) => 'valdef -> name.toString.trim
case TypeDef(_, name, _, _) => 'typedef -> name.toString.trim
}

override def hashCode(): RunId =
key.hashCode()

override def equals(obj: scala.Any): Boolean = obj match {
case that: OverloadedMember => key == that.key
case _ => false
}
}

private def templateAncestry(template: Template): List[Template] =
template :: {
for {
parent <- template.parents
parentImp <- index.declaration(parent.symbol).toList collect {
case ClassDef(_, _, _, impl) => impl
}
ancestor <- templateAncestry(parentImp)
} yield ancestor
}

override def prepare(s: Selection): Either[PreparationError, PreparationResult] = {


// Expand the selection to the concrete type when a kind was initially selected.
val maybeSelectedTemplate = (s::s.expandToNextEnclosingTree.toList) flatMap { sel: Selection =>
index.declaration(sel.enclosingTree.symbol)
} collectFirst {
case templateDeclaration: ClassDef => templateDeclaration.impl
}

// Get a sequence of members (types, methods or values) found in the selected mixed trait
val membersToImplement = {

val rawList = for {
selectedTemplate <- maybeSelectedTemplate.toList
selectedDeclaration <- templateAncestry(selectedTemplate)
unimplementedMember <- selectedDeclaration.body collect {
case defOrValDef: ValOrDefDef if defOrValDef.rhs.isEmpty => defOrValDef
case typeDef: TypeDef if !typeDef.rhs.hasExistingSymbol =>
typeDef
}
} yield unimplementedMember

val (uniqueMembers, _) =
rawList.foldRight((List.empty[MemberDef], Set.empty[OverloadedMember])) {
case (member, (l, visited)) if !visited.contains(member) =>
(member::l, visited + member)
case (_, st) => st
}
uniqueMembers
}

// Use the selection to find the template where the members should be implemented.
val targetTemplate = s.expandToNextEnclosingTree.flatMap {
_.selectedSymbolTree collect {
case target: Template => target
}
}

targetTemplate map { t => // If the selection has indeed a target template...
if(membersToImplement.isEmpty) Left { //... as long as there are members in the mixed trait...
PreparationError("There are not members to implement")
} else Right { //... these are selected to be defined in the target template.
// If and only if they were not already defined there.
val implementedMembers: Set[OverloadedMember] = {
t.body collect {
case memberDef: MemberDef => new OverloadedMember(memberDef)
} toSet
}
PreparationResult(t, membersToImplement.filterNot(implementedMembers contains _))
}
} getOrElse Left {
PreparationError("No target class in selection")
}
}

override type RefactoringParameters = Unit

override def perform(selection: Selection, prepared: PreparationResult, params: RefactoringParameters) = {
import prepared._

val findTemplate = filter {
case t: Template => t == targetTemplate
}

val addMembers = transform {
case tpl @ Template(_, _, body) if tpl == prepared.targetTemplate =>
val unimplementedSentence = Ident("???")
val thisType = SingletonTypeTree(This(TypeName("")))

val membersWithRhs = memberToImplement collect {
case methodDef: DefDef =>
methodDef copy (rhs = Block(unimplementedSentence :: Nil, EmptyTree))
case valueDef: ValDef =>
valueDef copy (rhs = unimplementedSentence)
case typeDef: TypeDef =>
typeDef copy (rhs = thisType)
}

tpl.copy(body = body ++ membersWithRhs).replaces(tpl)
}

val transformation = topdown {
matchingChildren {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I think that it will be quite hard to modify trees. The way how you determine unimplemented methods/fields from tree is done OK, but it is safer to modify source code directly, not its tree. There is scala.tools.refactoring.util.Movement util to walk on source. Generally I have to look at this closer.

findTemplate &>
addMembers
}
}
Right(transformFile(selection.file, transformation))
}
}
Loading