Fix Documentation Comments (#1199)

`TypeFunctions` and `TypeSignature` passes preserve documentation 
comments
This commit is contained in:
Dmitry Bushev 2020-10-07 20:42:40 +03:00 committed by GitHub
parent 42dcc1a003
commit a2d3b9fe01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 275 additions and 30 deletions

View File

@ -253,14 +253,20 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
* @return the list of type arguments
*/
private def buildTypeSignature(typeExpr: IR.Expression): Vector[TypeArg] = {
@scala.annotation.tailrec
def go(typeExpr: IR.Expression, args: Vector[TypeArg]): Vector[TypeArg] =
typeExpr match {
case IR.Application.Prefix(_, args, _, _, _, _) =>
args.toVector
.map(arg => go(arg.value, Vector()))
.map {
case Vector(targ) => targ
case targs => TypeArg.Function(targs)
}
case IR.Function.Lambda(List(targ), body, _, _, _, _) =>
val tdef = TypeArg(targ.name.name, targ.suspended)
val tdef = TypeArg.Value(targ.name.name, targ.suspended)
go(body, args :+ tdef)
case tname: IR.Name =>
args :+ TypeArg(tname.name, isSuspended = false)
args :+ TypeArg.Value(tname.name, isSuspended = false)
case _ =>
args
}
@ -364,12 +370,40 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
): Suggestion.Argument =
Suggestion.Argument(
name = varg.name.name,
reprType = targ.name,
isSuspended = targ.isSuspended,
reprType = buildTypeArgumentName(targ),
isSuspended = buildTypeArgumentSuspendedFlag(targ),
hasDefault = varg.defaultValue.isDefined,
defaultValue = varg.defaultValue.flatMap(buildDefaultValue)
)
/** Build the name of type argument.
*
* @param targ the type argument
* @return the name of type argument
*/
private def buildTypeArgumentName(targ: TypeArg): String = {
def go(targ: TypeArg, level: Int): String =
targ match {
case TypeArg.Value(name, _) => name
case TypeArg.Function(types) =>
val typeList = types.map(go(_, level + 1))
if (level > 0) typeList.mkString("(", " -> ", ")")
else typeList.mkString(" -> ")
}
go(targ, 0)
}
/** Build the suspended flag of the type argument.
*
* @param targ the type argument
* @return the suspended flag extracted from the type argument
*/
private def buildTypeArgumentSuspendedFlag(targ: TypeArg): Boolean =
targ match {
case TypeArg.Value(_, isSuspended) => isSuspended
case TypeArg.Function(_) => false
}
/** Build suggestion argument from an untyped definition.
*
* @param arg the value argument
@ -390,10 +424,7 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
* @return the type name
*/
private def buildReturnType(typeDef: Option[TypeArg]): String =
typeDef match {
case Some(TypeArg(name, _)) => name
case None => Any
}
typeDef.map(buildTypeArgumentName).getOrElse(Any)
/** Build argument default value from the expression.
*
@ -446,13 +477,24 @@ object SuggestionBuilder {
new Scope(mutable.Queue(items: _*), location)
}
/** Type of the argument.
*
* @param name the name of the type
* @param isSuspended is the argument lazy
*/
private case class TypeArg(name: String, isSuspended: Boolean)
/** The base trait for argument types. */
sealed private trait TypeArg
private object TypeArg {
/** Type with the name, like `A`.
*
* @param name the name of the type
* @param isSuspended is the argument lazy
*/
case class Value(name: String, isSuspended: Boolean) extends TypeArg
/** Function type, like `A -> A`.
*
* @param signature the list of types defining the function
*/
case class Function(signature: Vector[TypeArg]) extends TypeArg
}
private val Any: String = "Any"
}

View File

@ -3,6 +3,7 @@ package org.enso.compiler.pass.resolve
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Application
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.exception.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse._
@ -94,7 +95,12 @@ case object TypeFunctions extends IRPass {
*/
def resolveExpression(expr: IR.Expression): IR.Expression = {
expr.transformExpressions {
case app: IR.Application => resolveApplication(app)
case app: IR.Application =>
val result = resolveApplication(app)
app
.getMetadata(DocumentationComments)
.map(doc => result.updateMetadata(DocumentationComments -->> doc))
.getOrElse(result)
}
}

View File

@ -87,16 +87,27 @@ case object TypeSignatures extends IRPass {
val res = lastSignature match {
case Some(asc @ IR.Type.Ascription(typed, sig, _, _, _)) =>
val methodRef = meth.methodReference
val newMethodWithDoc = asc
.getMetadata(DocumentationComments)
.map(doc =>
newMethod.updateMetadata(DocumentationComments -->> doc)
)
.getOrElse(newMethod)
typed match {
case ref: IR.Name.MethodReference =>
if (ref isSameReferenceAs methodRef) {
Some(newMethod.updateMetadata(this -->> Signature(sig)))
Some(
newMethodWithDoc.updateMetadata(this -->> Signature(sig))
)
} else {
List(IR.Error.Unexpected.TypeSignature(asc), newMethod)
List(
IR.Error.Unexpected.TypeSignature(asc),
newMethodWithDoc
)
}
case _ =>
List(IR.Error.Unexpected.TypeSignature(asc), newMethod)
List(IR.Error.Unexpected.TypeSignature(asc), newMethodWithDoc)
}
case None => Some(newMethod)
}
@ -172,16 +183,30 @@ case object TypeSignatures extends IRPass {
val res = lastSignature match {
case Some(asc @ IR.Type.Ascription(typed, sig, _, _, _)) =>
val name = binding.name
val newBindingWithDoc = asc
.getMetadata(DocumentationComments)
.map(doc =>
newBinding.updateMetadata(DocumentationComments -->> doc)
)
.getOrElse(newBinding)
typed match {
case typedName: IR.Name =>
if (typedName.name == name.name) {
Some(newBinding.updateMetadata(this -->> Signature(sig)))
Some(
newBindingWithDoc.updateMetadata(this -->> Signature(sig))
)
} else {
List(IR.Error.Unexpected.TypeSignature(asc), newBinding)
List(
IR.Error.Unexpected.TypeSignature(asc),
newBindingWithDoc
)
}
case _ =>
List(IR.Error.Unexpected.TypeSignature(asc), newBinding)
List(
IR.Error.Unexpected.TypeSignature(asc),
newBindingWithDoc
)
}
case None => Some(newBinding)
}

View File

@ -64,6 +64,30 @@ class SuggestionBuilderTest extends CompilerTest {
)
}
"build method with type and documentation" in {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""## The foo
|foo : Number
|foo = 42""".stripMargin
val module = code.preprocessModule
build(code, module) should contain theSameElementsAs Seq(
Suggestion.Method(
externalId = None,
module = "Unnamed.Test",
name = "foo",
arguments = Seq(
Suggestion.Argument("this", "Test", false, false, None)
),
selfType = "Test",
returnType = "Number",
documentation = Some(" The foo")
)
)
}
"build method with arguments" in {
implicit val moduleContext: ModuleContext = freshModuleContext
@ -179,6 +203,7 @@ class SuggestionBuilderTest extends CompilerTest {
val code =
"""type MyAtom
|
|## My bar
|MyAtom.bar : Number -> Number -> Number
|MyAtom.bar a b = a + b
|""".stripMargin
@ -204,6 +229,41 @@ class SuggestionBuilderTest extends CompilerTest {
),
selfType = "MyAtom",
returnType = "Number",
documentation = Some(" My bar")
)
)
}
"build method with function type signature" in {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""type MyAtom
|
|MyAtom.apply : (Number -> Number) -> Number
|MyAtom.apply f = f this
|""".stripMargin
val module = code.preprocessModule
build(code, module) should contain theSameElementsAs Seq(
Suggestion.Atom(
externalId = None,
module = "Unnamed.Test",
name = "MyAtom",
arguments = Seq(),
returnType = "MyAtom",
documentation = None
),
Suggestion.Method(
externalId = None,
module = "Unnamed.Test",
name = "apply",
arguments = Seq(
Suggestion.Argument("this", "MyAtom", false, false, None),
Suggestion.Argument("f", "Number -> Number", false, false, None)
),
selfType = "MyAtom",
returnType = "Number",
documentation = None
)
)
@ -416,6 +476,62 @@ class SuggestionBuilderTest extends CompilerTest {
)
}
"build type with methods, type signatures and docs" in {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =
"""type List
| ## And more
| type Cons
| ## End
| type Nil
|
| ## a method
| empty : List
| empty = Nil
|""".stripMargin
val module = code.preprocessModule
build(code, module) should contain theSameElementsAs Seq(
Suggestion.Atom(
externalId = None,
module = "Unnamed.Test",
name = "Cons",
arguments = Seq(),
returnType = "Cons",
documentation = Some(" And more")
),
Suggestion.Atom(
externalId = None,
module = "Unnamed.Test",
name = "Nil",
arguments = Seq(),
returnType = "Nil",
documentation = Some(" End")
),
Suggestion.Method(
externalId = None,
module = "Unnamed.Test",
name = "empty",
arguments = Seq(
Suggestion.Argument("this", "Cons", false, false, None)
),
selfType = "Cons",
returnType = "List",
documentation = Some(" a method")
),
Suggestion.Method(
externalId = None,
module = "Unnamed.Test",
name = "empty",
arguments = Seq(
Suggestion.Argument("this", "Nil", false, false, None)
),
selfType = "Nil",
returnType = "List",
documentation = Some(" a method")
)
)
}
"build type with methods" in {
implicit val moduleContext: ModuleContext = freshModuleContext
val code =

View File

@ -145,6 +145,30 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
getDoc(body.expressions(0)) shouldEqual " Do thing"
getDoc(body.returnValue) shouldEqual " Do another thing"
}
"be associated with the type ascriptions" in {
implicit val moduleContext: ModuleContext = mkModuleContext
val ir =
"""
|method x =
| ## Id
| f : Any -> Any
| f x = x
|
| ## Return thing
| f 1
|""".stripMargin.preprocessModule.resolve
val body = ir
.bindings(0)
.asInstanceOf[IR.Module.Scope.Definition.Method.Binding]
.body
.asInstanceOf[IR.Expression.Block]
body.expressions.length shouldEqual 2
body.expressions(0) shouldBe an[IR.Application.Operator.Binary]
getDoc(body.expressions(0)) shouldEqual " Id"
getDoc(body.returnValue) shouldEqual " Return thing"
}
}
"Documentation in complex type definitions" should {
@ -190,8 +214,10 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
val module =
"""## The foo
|foo : Integer
|foo = 42""".stripMargin.preprocessModule
module.bindings.head.getMetadata(DocumentationComments) shouldBe defined
val foo = module.bindings.head
getDoc(foo) shouldEqual " The foo"
}
"be preserved after rewriting for all entities" in {
@ -209,6 +235,7 @@ class DocumentationCommentsTest extends CompilerTest with Inside {
| type Bar
|
| ## a method
| foo : Any -> Any
| foo x =
| ## a statement
| IO.println "foo"

View File

@ -4,7 +4,7 @@ import org.enso.compiler.Passes
import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext}
import org.enso.compiler.core.IR
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.pass.resolve.TypeSignatures
import org.enso.compiler.pass.resolve.{DocumentationComments, TypeSignatures}
import org.enso.compiler.test.CompilerTest
class TypeSignaturesTest extends CompilerTest {
@ -107,12 +107,25 @@ class TypeSignaturesTest extends CompilerTest {
ir.bindings(4) shouldBe an[IR.Error.Unexpected.TypeSignature]
}
"reattach documentation to method definitions" in {
val ir =
"""## My bar
|bar : Number -> Number -> Number
|bar a b = a + b
|""".stripMargin.preprocessModule.resolve
ir.bindings.length shouldEqual 1
ir.bindings.head.getMetadata(TypeSignatures) shouldBe defined
ir.bindings.head.getMetadata(DocumentationComments) shouldBe defined
}
"work inside type definition bodies" in {
val ir =
"""
|type MyType
| type MyAtom
|
| ## is atom
| is_atom : this -> Boolean
| is_atom = true
|
@ -123,6 +136,7 @@ class TypeSignaturesTest extends CompilerTest {
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Atom]
ir.bindings(1) shouldBe an[IR.Module.Scope.Definition.Method]
ir.bindings(1).getMetadata(TypeSignatures) shouldBe defined
ir.bindings(1).getMetadata(DocumentationComments) shouldBe defined
ir.bindings(2) shouldBe an[IR.Error.Unexpected.TypeSignature]
}
@ -130,9 +144,14 @@ class TypeSignaturesTest extends CompilerTest {
val ir =
"""
|main =
| f : a -> a
| ## An eff
| f : A -> A
| f a = a
|
| ## Local
| h : A
| h = 1
|
| f 1
|""".stripMargin.preprocessModule.resolve.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Method]
@ -142,8 +161,12 @@ class TypeSignaturesTest extends CompilerTest {
.body
.asInstanceOf[IR.Expression.Block]
block.expressions.length shouldEqual 1
block.expressions.length shouldEqual 2
block.expressions.head.getMetadata(TypeSignatures) shouldBe defined
block.expressions.head.getMetadata(DocumentationComments) shouldBe defined
block.expressions(1).getMetadata(TypeSignatures) shouldBe defined
block.expressions(1).getMetadata(DocumentationComments) shouldBe defined
}
}
@ -153,11 +176,13 @@ class TypeSignaturesTest extends CompilerTest {
val ir =
"""
|block =
| ## Doc f
| f : Int
| f = 0
|
| g : Int
| g =
| ## Doc inner f
| f : Double
| f = 0
| f 1
@ -169,8 +194,10 @@ class TypeSignaturesTest extends CompilerTest {
val block = ir.expression.asInstanceOf[IR.Expression.Block]
"associate signatures with bindings" in {
block.expressions.head shouldBe an[IR.Expression.Binding]
block.expressions.head.getMetadata(TypeSignatures) shouldBe defined
val head = block.expressions.head
head shouldBe an[IR.Expression.Binding]
head.getMetadata(TypeSignatures) shouldBe defined
head.getMetadata(DocumentationComments) shouldBe defined
}
"raise an error if a signature is divorced from its definition" in {
@ -184,8 +211,10 @@ class TypeSignaturesTest extends CompilerTest {
.expression
.asInstanceOf[IR.Expression.Block]
nested.expressions.head shouldBe an[IR.Expression.Binding]
nested.expressions.head.getMetadata(TypeSignatures) shouldBe defined
val head = nested.expressions.head
head shouldBe an[IR.Expression.Binding]
head.getMetadata(TypeSignatures) shouldBe defined
head.getMetadata(DocumentationComments) shouldBe defined
}
}