[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() }
|
||||
}
|
||||
|
||||
testsJar()
|
||||
|
||||
projectTest(jUnitMode = JUnitMode.JUnit5) {
|
||||
workingDir = rootDir
|
||||
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