fix: method clash error (#3210)

This commit is contained in:
Dmitry Bushev 2021-12-28 18:09:34 +03:00 committed by GitHub
parent 66c256a1f7
commit 2676aa50a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 251 additions and 9 deletions

View File

@ -464,7 +464,7 @@ class IrToTruffle(
"No binding analysis at the point of codegen."
)
bindingsMap.exportedSymbols.foreach {
case (name, List(resolution)) =>
case (name, resolution :: _) =>
if (resolution.module.unsafeAsModule() != moduleScope.getModule) {
resolution match {
case BindingsMap.ResolvedConstructor(definitionModule, cons) =>
@ -1100,6 +1100,11 @@ class IrToTruffle(
.error()
.compileError()
.newInstance(Text.create(err.message))
case err: Error.Redefined.MethodClashWithAtom =>
context.getBuiltins
.error()
.compileError()
.newInstance(Text.create(err.message))
case err: Error.Redefined.Conversion =>
context.getBuiltins
.error()

View File

@ -7476,6 +7476,126 @@ object IR {
s"(Redefined (Method $atomName.$methodName))"
}
/** An error representing the redefinition of a method in a given module,
* when the module defines a method with the same name as an atom.
* This is also known as a name clash.
*
* @param atomName the name of the atom that clashes with the method
* @param methodName the method name being redefined in the module
* @param location the location in the source to which this error
* corresponds
* @param passData the pass metadata for the error
* @param diagnostics any diagnostics associated with this error.
*/
sealed case class MethodClashWithAtom(
atomName: IR.Name,
methodName: IR.Name,
override val location: Option[IdentifiedLocation],
override val passData: MetadataStorage = MetadataStorage(),
override val diagnostics: DiagnosticStorage = DiagnosticStorage()
) extends Redefined
with Diagnostic.Kind.Interactive
with Module.Scope.Definition
with IRKind.Primitive {
override protected var id: Identifier = randomId
/** Creates a copy of `this`.
*
* @param atomName the name of the atom that clashes with the method
* @param methodName the method name being redefined in the module
* @param location the location in the source to which this error
* corresponds
* @param passData the pass metadata for the error
* @param diagnostics any diagnostics associated with this error.
* @param id the identifier for the node
* @return a copy of `this`, updated with the specified values
*/
def copy(
atomName: IR.Name = atomName,
methodName: IR.Name = methodName,
location: Option[IdentifiedLocation] = location,
passData: MetadataStorage = passData,
diagnostics: DiagnosticStorage = diagnostics,
id: Identifier = id
): MethodClashWithAtom = {
val res = MethodClashWithAtom(
atomName,
methodName,
location,
passData,
diagnostics
)
res.id = id
res
}
/** @inheritdoc */
override def duplicate(
keepLocations: Boolean = true,
keepMetadata: Boolean = true,
keepDiagnostics: Boolean = true,
keepIdentifiers: Boolean = false
): MethodClashWithAtom =
copy(
atomName = atomName.duplicate(
keepLocations,
keepMetadata,
keepDiagnostics,
keepIdentifiers
),
methodName = methodName
.duplicate(
keepLocations,
keepMetadata,
keepDiagnostics,
keepIdentifiers
),
location = if (keepLocations) location else None,
passData =
if (keepMetadata) passData.duplicate else MetadataStorage(),
diagnostics =
if (keepDiagnostics) diagnostics.copy else DiagnosticStorage(),
id = if (keepIdentifiers) id else randomId
)
/** @inheritdoc */
override def setLocation(
location: Option[IdentifiedLocation]
): MethodClashWithAtom =
copy(location = location)
/** @inheritdoc */
override def message: String =
s"Method definitions with the same name as atoms are not supported. " +
s"Method ${methodName.name} clashes with the atom ${atomName.name} in this module."
/** @inheritdoc */
override def mapExpressions(
fn: Expression => Expression
): MethodClashWithAtom =
this
/** @inheritdoc */
override def toString: String =
s"""
|IR.Error.Redefined.MethodClashWithAtom(
|atomName = $atomName,
|methodName = $methodName,
|location = $location,
|passData = ${this.showPassData},
|diagnostics = $diagnostics,
|id = $id
|)
|""".stripMargin
/** @inheritdoc */
override def children: List[IR] = List(atomName, methodName)
/** @inheritdoc */
override def showCode(indent: Int): String =
s"(Redefined (MethodClash $atomName $methodName))"
}
/** An error representing the redefinition of an atom in a given module.
*
* @param atomName the name of the atom being redefined

View File

@ -74,11 +74,21 @@ case object OverloadsResolution extends IRPass {
IR.Error.Redefined
.Method(method.typeName, method.methodName, method.location)
} else {
val currentMethods = seenMethods(method.typeName.name)
seenMethods = seenMethods + (method.typeName.name ->
(currentMethods + method.methodName.name))
atoms.find(_.name.name.equalsIgnoreCase(method.methodName.name)) match {
case Some(clashedAtom)
if method.typeName.isInstanceOf[IR.Name.Here] =>
IR.Error.Redefined.MethodClashWithAtom(
clashedAtom.name,
method.methodName,
method.location
)
case _ =>
val currentMethods = seenMethods(method.typeName.name)
seenMethods = seenMethods + (method.typeName.name ->
(currentMethods + method.methodName.name))
method
method
}
}
})

View File

@ -138,4 +138,33 @@ class OverloadsResolutionTest extends CompilerTest {
}
}
"Atom overloads method resolution" should {
implicit val ctx: ModuleContext = mkModuleContext
val atomName = "Foo"
val methodName = atomName.toLowerCase
val ir =
s"""|
|type $atomName
|$methodName = 0
|Unit.$methodName = 1
|""".stripMargin.preprocessModule.resolve
"detect overloads within a given module" in {
exactly(1, ir.bindings) shouldBe an[
IR.Error.Redefined.MethodClashWithAtom
]
}
"replace all overloads by an error node" in {
ir.bindings(1) shouldBe an[IR.Error.Redefined.MethodClashWithAtom]
ir.bindings(1)
.asInstanceOf[IR.Error.Redefined.MethodClashWithAtom]
.methodName
.name shouldEqual methodName
ir.bindings(2) shouldBe an[IR.Module.Scope.Definition.Method.Explicit]
}
}
}

View File

@ -2512,6 +2512,66 @@ class RuntimeServerTest
)
}
it should "return error when method name clashes with atom" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()
val moduleName = "Enso_Test.Test.Main"
val metadata = new Metadata
val code =
"""type Foo
|foo = 0
|main = 0
|""".stripMargin.linesIterator.mkString("\n")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)
// create context
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
context.receive shouldEqual Some(
Api.Response(requestId, Api.CreateContextResponse(contextId))
)
// Open the new file
context.send(
Api.Request(Api.OpenFileNotification(mainFile, contents))
)
context.receiveNone shouldEqual None
// push main
context.send(
Api.Request(
requestId,
Api.PushContextRequest(
contextId,
Api.StackItem.ExplicitCall(
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"),
None,
Vector()
)
)
)
)
context.receive(3) should contain theSameElementsAs Seq(
Api.Response(requestId, Api.PushContextResponse(contextId)),
Api.Response(
Api.ExecutionUpdate(
contextId,
Seq(
Api.ExecutionResult.Diagnostic.error(
"Method definitions with the same name as atoms are not supported. Method foo clashes with the atom Foo in this module.",
Some(mainFile),
Some(model.Range(model.Position(1, 0), model.Position(1, 7))),
None,
Vector()
)
)
)
),
context.executionComplete(contextId)
)
}
it should "return error with a stack trace" in {
val contextId = UUID.randomUUID()
val requestId = UUID.randomUUID()

View File

@ -160,7 +160,6 @@ class MethodsTest extends InterpreterTest {
|main =
| myList = Cons 1 (Cons 2 (Cons 3 Nil))
| myList.sum
|
|""".stripMargin
eval(code) shouldEqual 6

View File

@ -18,7 +18,7 @@ class OverloadsResolutionErrorTest extends InterpreterTest {
interpreterContext: InterpreterContext
): Unit = {
"result in an error at runtime for methods" in {
"result in an error at runtime for method overloads" in {
val code =
"""from Standard.Builtins import all
|
@ -38,7 +38,7 @@ class OverloadsResolutionErrorTest extends InterpreterTest {
)
}
"result in an error at runtime for atoms" in {
"result in an error at runtime for atom overloads" in {
val code =
"""
|type MyAtom
@ -56,6 +56,25 @@ class OverloadsResolutionErrorTest extends InterpreterTest {
"Test[3:1-3:11]: Redefining atoms is not supported: MyAtom is defined multiple times in this module."
)
}
}
"result in an error at runtime for methods overloading atoms" in {
val code =
"""
|type Foo
|foo = 0
|""".stripMargin.linesIterator.mkString("\n")
the[InterpreterException] thrownBy eval(code) should have message
"Compilation aborted due to errors."
val diagnostics = consumeOut
diagnostics
.filterNot(_.contains("Compiler encountered"))
.filterNot(_.contains("In module"))
.toSet shouldEqual Set(
"Test[3:1-3:7]: Method definitions with the same name as atoms are not supported. Method foo clashes with the atom Foo in this module."
)
}
}
}