mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 21:27:15 +03:00
Fix Documentation Comments (#1199)
`TypeFunctions` and `TypeSignature` passes preserve documentation comments
This commit is contained in:
parent
42dcc1a003
commit
a2d3b9fe01
@ -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"
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user