[Swift Export] Create infrastructure for SIR validation
This commit is contained in:
committed by
Space Team
parent
fc1d7df0bc
commit
9fde7e0d35
@@ -27,6 +27,8 @@ sourceSets {
|
|||||||
"test" { projectDefault() }
|
"test" { projectDefault() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testsJar()
|
||||||
|
|
||||||
projectTest(jUnitMode = JUnitMode.JUnit5) {
|
projectTest(jUnitMode = JUnitMode.JUnit5) {
|
||||||
workingDir = rootDir
|
workingDir = rootDir
|
||||||
useJUnitPlatform { }
|
useJUnitPlatform { }
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.sir.passes.utility
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.sir.SirDeclaration
|
||||||
|
import org.jetbrains.kotlin.sir.SirDeclarationParent
|
||||||
|
import org.jetbrains.kotlin.sir.SirElement
|
||||||
|
import org.jetbrains.kotlin.sir.SirModule
|
||||||
|
import org.jetbrains.kotlin.sir.visitors.SirVisitor
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
// Copy of DeclarationParentVisitor from Kotlin Backend IR.
|
||||||
|
internal abstract class DeclarationParentVisitor : SirVisitor<Unit, Nothing?>() {
|
||||||
|
|
||||||
|
protected val declarationParentStack = ArrayDeque<SirDeclarationParent>()
|
||||||
|
|
||||||
|
protected abstract fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent)
|
||||||
|
|
||||||
|
override fun visitElement(element: SirElement, data: Nothing?) {
|
||||||
|
element.acceptChildren(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitModule(module: SirModule, data: Nothing?) {
|
||||||
|
declarationParentStack.push(module)
|
||||||
|
super.visitModule(module, data)
|
||||||
|
declarationParentStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDeclaration(declaration: SirDeclaration, data: Nothing?) {
|
||||||
|
handleParent(declaration, declarationParentStack.peekFirst())
|
||||||
|
|
||||||
|
if (declaration is SirDeclarationParent) {
|
||||||
|
declarationParentStack.push(declaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.visitDeclaration(declaration, data)
|
||||||
|
|
||||||
|
if (declaration is SirDeclarationParent) {
|
||||||
|
declarationParentStack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes parents of declarations.
|
||||||
|
*
|
||||||
|
* Copy of PatchDeclarationParentVisitor from Backend IR.
|
||||||
|
*/
|
||||||
|
internal class PatchDeclarationParentVisitor() : DeclarationParentVisitor() {
|
||||||
|
constructor(containingDeclaration: SirDeclarationParent) : this() {
|
||||||
|
declarationParentStack.push(containingDeclaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent) {
|
||||||
|
declaration.parent = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.sir.passes.utility
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.sir.*
|
||||||
|
import org.jetbrains.kotlin.sir.visitors.SirVisitor
|
||||||
|
import org.jetbrains.sir.passes.SirPass
|
||||||
|
import org.jetbrains.sir.passes.utility.ValidationError.WrongForeignDeclarationOrigin
|
||||||
|
import org.jetbrains.sir.passes.utility.ValidationError.WrongParent
|
||||||
|
import kotlin.collections.plusAssign
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of errors that might arise during SIR validation.
|
||||||
|
*/
|
||||||
|
public sealed interface ValidationError {
|
||||||
|
public class WrongParent(public val declaration: SirDeclaration, public val expectedParent: SirDeclarationParent) : ValidationError
|
||||||
|
public class WrongForeignDeclarationOrigin(public val declaration: SirForeignDeclaration) : ValidationError
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SirValidatorConfig(
|
||||||
|
public val checkParents: Boolean = true,
|
||||||
|
public val checkForeignDeclarations: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate various invariants about Swift IR.
|
||||||
|
*/
|
||||||
|
public fun validate(sirElement: SirElement, config: SirValidatorConfig): List<ValidationError> {
|
||||||
|
return SirValidator(config).run(sirElement, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run full set of SIR validators, and fail if any error encountered.
|
||||||
|
*/
|
||||||
|
public fun SirElement.assertValid() {
|
||||||
|
val errors = validate(this, SirValidatorConfig())
|
||||||
|
if (errors.isNotEmpty()) {
|
||||||
|
val messages = errors.map {
|
||||||
|
when (it) {
|
||||||
|
// TODO: better rendering of SIR elements.
|
||||||
|
is WrongForeignDeclarationOrigin -> "Wrong foreign declaration origin: ${it.declaration}"
|
||||||
|
is WrongParent -> "Wrong declaration parent of ${it.declaration}. Expected: ${it.expectedParent}. Got: ${it.declaration.parent}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("SIR is invalid:\n" + messages.joinToString(separator = "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun interface ErrorHandler {
|
||||||
|
fun handle(error: ValidationError)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class SirValidator(private val config: SirValidatorConfig) : SirPass<SirElement, Nothing?, List<ValidationError>> {
|
||||||
|
override fun run(element: SirElement, data: Nothing?): List<ValidationError> {
|
||||||
|
val errors = mutableListOf<ValidationError>()
|
||||||
|
|
||||||
|
val handler = ErrorHandler {
|
||||||
|
errors += it
|
||||||
|
}
|
||||||
|
if (config.checkParents) {
|
||||||
|
element.accept(ParentValidator(handler), data)
|
||||||
|
}
|
||||||
|
if (config.checkForeignDeclarations) {
|
||||||
|
element.accept(ForeignDeclarationValidator(handler), data)
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ParentValidator(private val errorHandler: ErrorHandler) : DeclarationParentVisitor() {
|
||||||
|
override fun handleParent(declaration: SirDeclaration, parent: SirDeclarationParent) {
|
||||||
|
if (declaration.parent != parent) {
|
||||||
|
errorHandler.handle(WrongParent(declaration, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ForeignDeclarationValidator(private val errorHandler: ErrorHandler) : SirVisitor<Unit, Nothing?>() {
|
||||||
|
override fun visitElement(element: SirElement, data: Nothing?) {
|
||||||
|
element.acceptChildren(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitForeignFunction(foreignFunction: SirForeignFunction, data: Nothing?) {
|
||||||
|
super.visitForeignFunction(foreignFunction, data)
|
||||||
|
if (foreignFunction.origin !is SirOrigin.Foreign) {
|
||||||
|
errorHandler.handle(WrongForeignDeclarationOrigin(foreignFunction))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.sir.passes
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.sir.SirNominalType
|
||||||
|
import org.jetbrains.kotlin.sir.builder.buildEnum
|
||||||
|
import org.jetbrains.kotlin.sir.builder.buildFunction
|
||||||
|
import org.jetbrains.kotlin.sir.builder.buildModule
|
||||||
|
import org.jetbrains.kotlin.sir.util.SirSwiftModule
|
||||||
|
import org.jetbrains.sir.passes.utility.PatchDeclarationParentVisitor
|
||||||
|
import org.jetbrains.sir.passes.utility.SirValidatorConfig
|
||||||
|
import org.jetbrains.sir.passes.utility.ValidationError
|
||||||
|
import org.jetbrains.sir.passes.utility.validate
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SirParentPatcherTests {
|
||||||
|
@Test
|
||||||
|
fun `parent patcher should patch parents`() {
|
||||||
|
val wrongEnum = buildEnum { name = "wrongEnum" }
|
||||||
|
val function = buildFunction {
|
||||||
|
name = "foo"
|
||||||
|
returnType = SirNominalType(SirSwiftModule.bool)
|
||||||
|
}
|
||||||
|
function.parent = wrongEnum
|
||||||
|
val wrongModule = buildModule { name = "wrongModule" }
|
||||||
|
val enum = buildEnum {
|
||||||
|
name = "e"
|
||||||
|
declarations += function
|
||||||
|
}
|
||||||
|
enum.parent = wrongModule
|
||||||
|
val module = buildModule {
|
||||||
|
name = "MyModule"
|
||||||
|
declarations += enum
|
||||||
|
}
|
||||||
|
val validationErrorsBefore = validate(module, SirValidatorConfig())
|
||||||
|
assertTrue(validationErrorsBefore.filterIsInstance<ValidationError.WrongParent>().count() == 2)
|
||||||
|
module.accept(PatchDeclarationParentVisitor(), null)
|
||||||
|
val validationErrorsAfter = validate(module, SirValidatorConfig())
|
||||||
|
assertTrue(validationErrorsAfter.filterIsInstance<ValidationError.WrongParent>().isEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.sir.passes
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.sir.SirOrigin
|
||||||
|
import org.jetbrains.kotlin.sir.builder.buildForeignFunction
|
||||||
|
import org.jetbrains.kotlin.sir.builder.buildModule
|
||||||
|
import org.jetbrains.kotlin.sir.mock.MockFunction
|
||||||
|
import org.jetbrains.kotlin.sir.mock.MockKotlinType
|
||||||
|
import org.jetbrains.sir.passes.utility.PatchDeclarationParentVisitor
|
||||||
|
import org.jetbrains.sir.passes.utility.SirValidatorConfig
|
||||||
|
import org.jetbrains.sir.passes.utility.ValidationError
|
||||||
|
import org.jetbrains.sir.passes.utility.validate
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class SirValidatorTests {
|
||||||
|
@Test
|
||||||
|
fun `validator should check foreign origins`() {
|
||||||
|
val module = buildModule {
|
||||||
|
name = "MyModule"
|
||||||
|
declarations += buildForeignFunction {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = SirValidatorConfig(checkParents = false)
|
||||||
|
val error = validate(module, config).first()
|
||||||
|
assertTrue(error is ValidationError.WrongForeignDeclarationOrigin)
|
||||||
|
assertEquals(module.declarations.first(), error.declaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `validatior should check declaration parents`() {
|
||||||
|
val wrongModule = buildModule {
|
||||||
|
name = "WrongModule"
|
||||||
|
}
|
||||||
|
val foreignFunction = buildForeignFunction {
|
||||||
|
val kotlinEntity = MockFunction(
|
||||||
|
fqName = listOf("foo"),
|
||||||
|
parameters = emptyList(),
|
||||||
|
returnType = MockKotlinType("kotlin/Byte")
|
||||||
|
)
|
||||||
|
origin = kotlinEntity
|
||||||
|
}
|
||||||
|
foreignFunction.parent = wrongModule
|
||||||
|
val module = buildModule {
|
||||||
|
name = "MyModule"
|
||||||
|
declarations += foreignFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = SirValidatorConfig(
|
||||||
|
checkForeignDeclarations = false
|
||||||
|
)
|
||||||
|
val error = validate(module, config).first()
|
||||||
|
assertTrue(error is ValidationError.WrongParent)
|
||||||
|
assertEquals(foreignFunction, error.declaration)
|
||||||
|
assertEquals(module, error.expectedParent)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user