Skip to content

Commit

Permalink
feat: add capabilities to InferMethodprovider
Browse files Browse the repository at this point in the history
It now handles adding methods to class and object, lambda-0 case,
map(nonExistent) with and without arguments
  • Loading branch information
ag91 committed Oct 20, 2024
1 parent 709ce61 commit c768220
Show file tree
Hide file tree
Showing 2 changed files with 493 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import scala.annotation.tailrec
* - apply inside method invocation `method(nonExistent(param))`
* - method in val definition `val value: DefinedType = nonExistent(param)` TODO
* - lambda expression `list.map(nonExistent)`
* - class method `someClass.nonExistent(param)`
*
* Limitations:
* - cannot work with an expression inside the parameter, since it's not typechecked
Expand Down Expand Up @@ -83,10 +84,12 @@ final class InferredMethodProvider(
def signature(
name: String,
paramsString: String,
retType: Option[String]
retType: Option[String],
postProcess: String => String,
position: Option[Position]
): List[TextEdit] = {

val lastApplyPos = insertPosition()
val lastApplyPos = position.getOrElse(insertPosition())
val indentString =
indentation(params.text(), lastApplyPos.start - 1)
val retTypeString = retType match {
Expand All @@ -101,11 +104,14 @@ final class InferredMethodProvider(
methodInsertPosition.setEnd(methodInsertPosition.getStart())
new TextEdit(
methodInsertPosition,
full
postProcess(full)
) :: additionalImports
}

typedTree match {
// case errorMethod if errorMethod.toString().contains("lol") =>
// pprint.log(lastVisitedParentTrees)
// Nil
case errorMethod: Ident if errorMethod.isErroneous =>
lastVisitedParentTrees match {
/**
Expand All @@ -117,7 +123,7 @@ final class InferredMethodProvider(
case _ :: (nonExistent: Apply) :: Apply(
Ident(containingMethod),
arguments
) :: _ =>
) :: _ if nonExistent.isErrorTyped =>
val argumentString = argumentsString(nonExistent.args)

val methodSymbol = context.lookupSymbol(containingMethod, _ => true)
Expand All @@ -128,16 +134,19 @@ final class InferredMethodProvider(
case MethodType(methodParams, _) if retIndex >= 0 =>
val ret = prettyType(methodParams(retIndex).tpe)
signature(
errorMethod.name.toString(),
paramsString,
Some(ret)
name = errorMethod.name.toString(),
paramsString = paramsString,
retType = Some(ret),
postProcess = identity,
position = None
)
case _ => Nil
case _ =>
Nil
}

case _ => Nil
case _ =>
Nil
}

// nonExistent(param1, param2)
// val a: Int = nonExistent(param1, param2) TODO
case (_: Ident) :: Apply(
Expand All @@ -146,20 +155,20 @@ final class InferredMethodProvider(
),
arguments
) :: _ if containing.isErrorTyped =>
val argumentString = argumentsString(arguments)
argumentString match {
case Some(paramsString) =>
signature(nonExistent.toString(), paramsString, None)
case None => Nil
}

signature(
name = nonExistent.toString(),
paramsString = argumentsString(arguments).getOrElse(""),
retType = None,
postProcess = identity,
position = None
)
// List(1, 2, 3).map(nonExistent)
// List((1, 2)).map{case (x,y) => otherMethod(x,y)}
case (_: Ident) :: Apply(
Ident(containingMethod), // TODO doesn't work with Select
Ident(containingMethod),
arguments
) :: _ =>
val methodSymbol = context.lookupSymbol(containingMethod, _ => true)

if (methodSymbol.isSuccess) {
val retIndex = arguments.indexWhere(_.pos.includes(pos))
methodSymbol.symbol.tpe match {
Expand Down Expand Up @@ -194,17 +203,139 @@ final class InferredMethodProvider(
) :: additionalImports

case _ =>
Nil
// def method1(s : => Int) = 123
// method1(<<otherMethod>>)
val ret = tpe.toString().replace("=>", "").trim()
val full =
s"def ${errorMethod.name}: $ret = ???\n$indentString"
val methodInsertPosition = lastApplyPos.toLsp
methodInsertPosition.setEnd(
methodInsertPosition.getStart()
)

new TextEdit(
methodInsertPosition,
full
) :: additionalImports
}

case _ => Nil
case _ =>
Nil
}
} else {
Nil
signature(
name = errorMethod.name.toString(),
paramsString = argumentsString(arguments).getOrElse(""),
retType = None,
postProcess = identity,
position = None
)
}
case other =>
// pprint.log(other)
Nil
// val list = List(1,2,3)
// list.map(nonExistent)
case (Ident(_)) :: Apply(
Select(Ident(argumentList), _),
_ :: Nil
) :: _ =>
// need to find the type of the value on which we are mapping
val listSymbol = context.lookupSymbol(argumentList, _ => true)
if (listSymbol.isSuccess) {
listSymbol.symbol.tpe match {
case TypeRef(_, _, TypeRef(_, inputType, _) :: Nil) =>
val lastApplyPos = insertPosition()
val indentString =
indentation(params.text(), lastApplyPos.start - 1)

val paramsString = s"arg0: ${prettyType(inputType.tpe)}"
val full =
s"def ${errorMethod.name}($paramsString) = ???\n$indentString"
val methodInsertPosition = lastApplyPos.toLsp
methodInsertPosition.setEnd(
methodInsertPosition.getStart()
)

new TextEdit(
methodInsertPosition,
full
) :: additionalImports
case _ => Nil
}
} else Nil
case _ => Nil
}
case errorMethod: Select if errorMethod.isErroneous =>
lastVisitedParentTrees match {
// class X{}
// val x: X = ???
// x.nonExistent(1,true,"string")
case Select(Ident(container), _) :: Apply(
_,
arguments
) :: _ =>
// pprint.log(lastVisitedParentTrees)
// we need to get the type of the container of our undefined method
val containerSymbol = context.lookupSymbol(container, _ => true)
if (containerSymbol.isSuccess) {
containerSymbol.symbol.tpe match {
case TypeRef(_, classSymbol, _) =>
// we get the position of the container
// class because we want to add the method
// definition there
//

val containerClass =
context.lookupSymbol(classSymbol.name, _ => true)
// this gives me the position of the class, but what about its body
typedTreeAt(containerClass.symbol.pos) match {
case ClassDef(
_,
_,
_,
template
) =>
val insertPos: Position =
inferEditPosition(params.text, template)

// val ret = prettyType(methodParams(retIndex).tpe)
signature(
name = errorMethod.name.toString(),
paramsString = argumentsString(arguments).getOrElse(""),
retType = None,
postProcess = method => {
if (hasBody(params.text, template).isDefined)
s"\n $method"
else s" {\n $method}"
},
position = Option(insertPos)
)
case ModuleDef(
_,
_,
template
) =>
val insertPos: Position =
inferEditPosition(params.text, template)

// val ret = prettyType(methodParams(retIndex).tpe)
signature(
name = errorMethod.name.toString(),
paramsString = argumentsString(arguments).getOrElse(""),
retType = None,
postProcess = method => {
if (hasBody(params.text, template).isDefined)
s"\n $method"
else s" {\n $method}"
},
position = Option(insertPos)
)
case _ =>
Nil
}
case _ => Nil
}
} else
Nil
case _ => Nil
}
case _ =>
Nil
Expand Down Expand Up @@ -235,4 +366,40 @@ final class InferredMethodProvider(
if (text(index) != '\n') countIndent(text, index - 1, acc + 1)
else acc
}

// TODO taken from OverrideCompletion: move into a utility?
/**
* Get the position to insert implements for the given Template.
* `class Foo extends Bar {}` => retuning position would be right after the opening brace.
* `class Foo extends Bar` => retuning position would be right after `Bar`.
*
* @param text the text of the original source code.
* @param t the enclosing template for the class/object/trait we are implementing.
*/
private def inferEditPosition(text: String, t: Template): Position = {
hasBody(text, t)
.map { offset => t.pos.withStart(offset + 1).withEnd(offset + 1) }
.getOrElse(
t.pos.withStart(t.pos.end)
)
}

/**
* Check if the given Template has body or not:
* `class Foo extends Bar {}` => Some(position of `{`)
* `class Foo extends Bar` => None
*
* @param text the text of the original source code.
* @param t the enclosing template for the class/object/trait we are implementing.
* @return if the given Template has body, returns the pos of opening brace, otherwise returns None
*/
private def hasBody(text: String, t: Template): Option[Int] = {
val start = t.pos.start
val offset =
if (t.self.tpt.isEmpty)
text.indexOf('{', start)
else text.indexOf("=>", start) + 1
if (offset > 0 && offset < t.pos.end) Some(offset)
else None
}
}
Loading

0 comments on commit c768220

Please sign in to comment.