From fa1900aa8e2aef281d054622d15fb613765f5aa3 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 29 Jul 2026 17:06:34 -0700 Subject: [PATCH 1/8] Supply backend adjusters Signed-off-by: Tom --- .../kotlin/lang/temper/be/java/JavaBackend.kt | 2 +- .../lang/temper/be/java/JavaTranslator.kt | 6 ++- .../lang/temper/be/FunctionalTestRunner.kt | 3 ++ .../lang/temper/be/GeneratedCodeHelper.kt | 3 ++ .../kotlin/lang/temper/be/Backend.kt | 50 +++++++++++++++++++ .../lang/temper/tooling/buildrun/Build.kt | 1 + 6 files changed, 63 insertions(+), 2 deletions(-) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt index b9fb0488..1cc8794c 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt @@ -136,7 +136,7 @@ class JavaBackend private constructor( private var rootMainClass: QualifiedName? = null override fun translate(finished: TmpL.ModuleSet) = buildList { - JavaTranslator(names, dependenciesBuilder).let { trans -> + JavaTranslator(names, dependenciesBuilder, adjuster).let { trans -> names.scanNames(finished) finished.modules.flatMap { tmpLModule -> trans.translate(tmpLModule) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt index 0b16946c..be77aa8c 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt @@ -2,6 +2,7 @@ package lang.temper.be.java import lang.temper.ast.deepSlice import lang.temper.ast.toLispy +import lang.temper.be.BackendAdjuster import lang.temper.be.Dependencies import lang.temper.be.java.JavaOperator.Assign import lang.temper.be.tmpl.FnAutodoc @@ -66,6 +67,7 @@ class JavaTranslator( /** a shared instance for consistent name mapping */ private val topNames: JavaNames, private val dependenciesBuilder: Dependencies.Builder? = null, + private val adjuster: BackendAdjuster? = null, ) { /** Spin off an instance for a given module */ private fun forModule(module: ModuleName, programMeta: J.ProgramMeta = J.ProgramMeta(unknownPos)): ModuleScope = @@ -481,7 +483,9 @@ class JavaTranslator( }.also { add(it.asNameExpr().asArgument()) } } }, - ).exprOrReturnStatement(shouldReturn = result !is J.VoidType).also { add(it) } + ).let { call -> + adjuster?.adjustConnectedCall(fn, call as J.ExpressionStatementExpr) ?: call + }.exprOrReturnStatement(shouldReturn = result !is J.VoidType).also { add(it) } }.let { J.BlockStatement(pos, it) } } diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt index dee5ace3..4c3f3d62 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt @@ -160,6 +160,7 @@ abstract class FunctionalTestRunner>( outputRoot = outputRoot, preparedModules = preparedModules, test = test, + adjuster = backendOrganization.adjusters[neededBackendId], ) } @@ -204,6 +205,7 @@ abstract class FunctionalTestRunner>( outputRoot: OutputRoot, preparedModules: PreparedFunctionalTest, test: FunctionalTestBase, + adjuster: BackendAdjuster?, ) = run { val supportedBackendList = listOf(backendId) val functionalTestLibraryConfiguration = LibraryConfiguration( @@ -313,6 +315,7 @@ abstract class FunctionalTestRunner>( config = config, dependenciesBuilder = dependenciesBuilder, rawBackendFiles = rawBackendFiles, + adjuster = adjuster, ), ) } diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt index 9cba9755..dd4f88bd 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt @@ -103,6 +103,7 @@ fun > generateCode( moduleResultNeeded = moduleResultNeeded, logSink = logSink, outputRoot = outputRoot, + adjuster = factory.adjusters()[backendId], ) } } @@ -117,6 +118,7 @@ fun > generateCode( moduleResultNeeded: Boolean, logSink: LogSink, outputRoot: OutputRoot, + adjuster: BackendAdjuster?, activeFactories: Iterable> = listOf(factory), ) { val backendId = factory.backendId @@ -197,6 +199,7 @@ fun > generateCode( logSink, NullDependencyResolver, backendConfig, + adjuster = adjuster, ), ) } diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index 936e6ce1..5009b014 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -109,6 +109,7 @@ abstract class Backend>( */ val dependenciesBuilder: Dependencies.Builder, val rawBackendFiles: Map = mapOf(), + val adjuster: BackendAdjuster? = null, ) { constructor(backendId: BackendId, setup: BackendSetup) : this( backendId = backendId, @@ -121,6 +122,7 @@ abstract class Backend>( config = setup.config, dependenciesBuilder = setup.dependenciesBuilder, rawBackendFiles = setup.rawBackendFiles, + adjuster = setup.adjuster, ) val libraryConfigurations = dependenciesBuilder.libraryConfigurations @@ -830,6 +832,12 @@ abstract class Backend>( * [module list][BackendSetup.modules]. */ fun make(setup: BackendSetup): Backend + + /** + * Adjuster by backend as wanted. Should only be given for backend ids + * in [BackendMeta.requiredBackendIds] for this backend. + */ + fun adjusters(): Map = mapOf() } @Retention(AnnotationRetention.RUNTIME) @@ -898,8 +906,36 @@ data class BackendSetup>( val config: Backend.Config, /** Files matching backend extensions from the library source tree. */ val rawBackendFiles: Map = mapOf(), + val adjuster: BackendAdjuster? = null, ) +/** + * Adjusts, customized, or transforms backend behavior. Might be installed by + * one backend on another in some custom Temper configuration. + */ +interface BackendAdjuster { + /** + * Optionally customizes an already translated connected call. On null + * return value, use the original. + */ + fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + return null + } + + // TODO adjustModuleTree to be called on the final product??? + + // TODO addTranslations or addFiles to add more files. +} + +/** Combines two adjusters with [this] as priority. */ +fun BackendAdjuster.orElse(other: BackendAdjuster): BackendAdjuster { + return object : BackendAdjuster { + override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + return adjustConnectedCall(decl, call) ?: other.adjustConnectedCall(decl, call) + } + } +} + private fun sourceMapFile(outputSourceFile: FilePath): FilePath { check(outputSourceFile.isFile) val dir = outputSourceFile.dirName() @@ -937,6 +973,9 @@ data class BackendOrganization( /** The factory for each backend. */ val factoriesById: Map>, + + /** Priority order rather than chain. */ + val adjusters: Map = mapOf(), ) /** Calculate transitive backend organization as needed by the given initially requested [backendIds]. */ @@ -1009,9 +1048,20 @@ fun organizeBackends( } } val backendRequirements = transitiveClosure(backendOrderingFounds).mapValues { setOf(it.key) + it.value } + val adjusters = buildMap { + for (factory in factoriesById.values) { + val adjusters = factory.adjusters() + for ((backendId, adjuster) in adjusters) { + compute(backendId) { _, previous -> + previous?.let { previous.orElse(adjuster) } ?: adjuster + } + } + } + } return BackendOrganization( backendBuckets = backendBuckets, backendRequirements = backendRequirements, factoriesById = factoriesById, + adjusters = adjusters, ) } diff --git a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt index c5cd9138..5012abc6 100644 --- a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt +++ b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt @@ -428,6 +428,7 @@ fun doOneBuild(build: Build): BuildResult { logSink = projectLogSink, dependencyResolver = dependencyResolver, config = harness.backendConfig, + adjuster = backendOrganization.adjusters[factory.backendId], ), ) } From 2f14fee15e882ac6a0f039027953f28ee2294e66 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 30 Jul 2026 11:59:25 -0700 Subject: [PATCH 2/8] Test adjusters; report adjuster errors Signed-off-by: Tom --- .../lang/temper/be/FunctionalTestRunner.kt | 2 +- .../lang/temper/be/GeneratedCodeHelper.kt | 2 +- .../kotlin/lang/temper/be/Backend.kt | 37 +++++++++++++++++- .../kotlin/lang/temper/be/BackendTest.kt | 39 ++++++++++++++++--- .../lang/temper/tooling/buildrun/Build.kt | 8 ++-- 5 files changed, 75 insertions(+), 13 deletions(-) diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt index 4c3f3d62..0d0c4e2c 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt @@ -131,7 +131,7 @@ abstract class FunctionalTestRunner>( val backendOrganization = organizeBackends( listOf(backendId), lookupFactory = ::lookupFactory, - onMissingFactory = { error(it) }, + onError = { error(it) }, ) // TODO Actually build by buckets? val outputRoot = OutputRoot(MemoryFileSystem()) diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt index dd4f88bd..f4117322 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt @@ -90,7 +90,7 @@ fun > generateCode( val backendOrganization = organizeBackends( listOf(factory.backendId), lookupFactory = lookupFactory, - onMissingFactory = { error(it) }, + onError = { error(it) }, ) for (bucket in backendOrganization.backendBuckets) { for (backendId in bucket) { diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index 5009b014..452f58d2 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -978,11 +978,33 @@ data class BackendOrganization( val adjusters: Map = mapOf(), ) +data class BackendOrganizationError( + val kind: BackendOrganizationErrorKind, + val backendId: BackendId, + val sourceBackendId: BackendId? = null, +) + +enum class BackendOrganizationErrorKind { + /** + * A factory was missing for some requested [BackendId], whether in the + * initial list or as some listed, transitive requirement of one. + */ + FactoryNotFound, + + /** + * Required backends currently are in [BackendMeta.requiredBackendIds], + * which really only can express basic metadata, not things like adjusters. + * So adjusters are defined on the factory, but they are expected to be + * specified only for directly required backends. + */ + AdjusterForUnrequiredBackend, +} + /** Calculate transitive backend organization as needed by the given initially requested [backendIds]. */ fun organizeBackends( backendIds: Iterable, lookupFactory: (BackendId) -> Backend.Factory<*>?, - onMissingFactory: (BackendId) -> Unit, + onError: (BackendOrganizationError) -> Unit, ): BackendOrganization { // We need to order how we drive backends. // A backend for one target language might require translations for another target language. @@ -998,7 +1020,10 @@ fun organizeBackends( fun factoryFor(backendId: BackendId) = factories.getOrPut(backendId) { lookupFactory(backendId).also { factory -> if (factory == null) { - onMissingFactory(backendId) + BackendOrganizationError( + kind = BackendOrganizationErrorKind.FactoryNotFound, + backendId = backendId, + ).also { onError(it) } } else { backendOrdering[backendId] = factory.backendMeta.requiredBackendIds.toSet() } @@ -1051,7 +1076,15 @@ fun organizeBackends( val adjusters = buildMap { for (factory in factoriesById.values) { val adjusters = factory.adjusters() + val requiredBackendIds = factory.backendMeta.requiredBackendIds for ((backendId, adjuster) in adjusters) { + if (backendId !in requiredBackendIds) { + BackendOrganizationError( + kind = BackendOrganizationErrorKind.AdjusterForUnrequiredBackend, + backendId = backendId, + sourceBackendId = factory.backendId, + ).also { onError(it) } + } compute(backendId) { _, previous -> previous?.let { previous.orElse(adjuster) } ?: adjuster } diff --git a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt index 017e2893..f322fdc4 100644 --- a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt +++ b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt @@ -21,19 +21,32 @@ class BackendTest { @Test fun backendOrganization() { - class NeedyBackendFactory(backendId: String, requiredBackendIds: List) : TestBackend.TestFactory() { + class NeedyBackendFactory( + backendId: String, + requiredBackendIds: List, + val adjusters: Map = mapOf(), + ) : TestBackend.TestFactory() { override val backendId: BackendId = BackendId(backendId) override val backendMeta = super.backendMeta.copy( backendId = this.backendId, requiredBackendIds = requiredBackendIds, ) + override fun adjusters(): Map { + return adjusters + } } val missingBackendId = BackendId("missing") val needyFactory = NeedyBackendFactory("needy", listOf(TestBackend.backendId, missingBackendId)) val needlessFactory = NeedyBackendFactory("needless", listOf()) + val uselessAdjuster = object : BackendAdjuster {} val needierFactory = NeedyBackendFactory( - "needier", - listOf(needyFactory.backendId, needlessFactory.backendId), + backendId = "needier", + requiredBackendIds = listOf(needyFactory.backendId, needlessFactory.backendId), + adjusters = mapOf( + needyFactory.backendId to uselessAdjuster, + // We don't directly require this, so we shouldn't be adjusting it. + TestBackend.backendId to uselessAdjuster, + ), ) val aloofBackend = NeedyBackendFactory("aloof", listOf()) val requestedAloofFactory = NeedyBackendFactory("requested-aloof", listOf()) @@ -45,14 +58,27 @@ class BackendTest { aloofBackend, requestedAloofFactory, ).associateBy { it.backendId } - val missingBackendIds = mutableSetOf() + val errors = mutableSetOf() val organization = organizeBackends( backendIds = listOf(needierFactory.backendMeta.backendId, requestedAloofFactory.backendId), lookupFactory = { backends[it] }, - onMissingFactory = { missingBackendIds.add(it) }, + onError = { err -> errors.add(err) }, ) - assertEquals(setOf(missingBackendId), missingBackendIds) // Collections maintain order by default, so these should be reliable. + assertEquals( + setOf( + BackendOrganizationError( + kind = BackendOrganizationErrorKind.FactoryNotFound, + backendId = missingBackendId, + ), + BackendOrganizationError( + kind = BackendOrganizationErrorKind.AdjusterForUnrequiredBackend, + backendId = TestBackend.backendId, + sourceBackendId = needierFactory.backendId, + ), + ), + errors, + ) assertEquals( mapOf( needierFactory.backendId to setOf( @@ -77,6 +103,7 @@ class BackendTest { organization.backendBuckets, ) assertEquals(backends.keys - setOf(aloofBackend.backendId), organization.factoriesById.keys) + assertEquals(uselessAdjuster, organization.adjusters[needyFactory.backendId]) } @Test diff --git a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt index 5012abc6..83a5e29c 100644 --- a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt +++ b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt @@ -384,13 +384,15 @@ fun doOneBuild(build: Build): BuildResult { val backendOrganization = organizeBackends( backendIds = libraries.flatMap { it.first.supportedBackendList }.toSet(), lookupFactory = ::lookupFactory, - onMissingFactory = { backendId -> - if (backendId != interpBackendId) { + onError = { err -> + if (err.backendId != interpBackendId) { + // Ignore error kind for now, but that might be nice to add in the future. + // These errors should be rare. projectLogSink.log( Log.Error, MessageTemplate.BadBackend, unknownPos, - listOf(backendId), + listOf(err.backendId), ) } }, From 05b488388d8530bf143e4933020ce2e4538838c3 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 30 Jul 2026 17:13:40 -0700 Subject: [PATCH 3/8] Use adjuster factories and adjusters per module Signed-off-by: Tom --- .../kotlin/lang/temper/be/java/JavaBackend.kt | 2 +- .../lang/temper/be/java/JavaTranslator.kt | 6 ++- .../lang/temper/be/FunctionalTestRunner.kt | 6 +-- .../lang/temper/be/GeneratedCodeHelper.kt | 6 +-- .../kotlin/lang/temper/be/Backend.kt | 50 +++++++++++++------ .../kotlin/lang/temper/be/BackendTest.kt | 18 ++++--- .../lang/temper/tooling/buildrun/Build.kt | 2 +- 7 files changed, 58 insertions(+), 32 deletions(-) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt index 1cc8794c..15c4058b 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaBackend.kt @@ -136,7 +136,7 @@ class JavaBackend private constructor( private var rootMainClass: QualifiedName? = null override fun translate(finished: TmpL.ModuleSet) = buildList { - JavaTranslator(names, dependenciesBuilder, adjuster).let { trans -> + JavaTranslator(names, dependenciesBuilder, adjusterFactory).let { trans -> names.scanNames(finished) finished.modules.flatMap { tmpLModule -> trans.translate(tmpLModule) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt index be77aa8c..7edfd025 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt @@ -3,6 +3,7 @@ package lang.temper.be.java import lang.temper.ast.deepSlice import lang.temper.ast.toLispy import lang.temper.be.BackendAdjuster +import lang.temper.be.BackendAdjusterFactory import lang.temper.be.Dependencies import lang.temper.be.java.JavaOperator.Assign import lang.temper.be.tmpl.FnAutodoc @@ -67,7 +68,7 @@ class JavaTranslator( /** a shared instance for consistent name mapping */ private val topNames: JavaNames, private val dependenciesBuilder: Dependencies.Builder? = null, - private val adjuster: BackendAdjuster? = null, + private val adjusterFactory: BackendAdjusterFactory? = null, ) { /** Spin off an instance for a given module */ private fun forModule(module: ModuleName, programMeta: J.ProgramMeta = J.ProgramMeta(unknownPos)): ModuleScope = @@ -134,6 +135,7 @@ class JavaTranslator( /** Might even stay null for snippets. */ private var module: TmpL.Module? = null + private var adjuster: BackendAdjuster? = null private fun activeDecls(decls: MutableList) = when { processingTestCode -> moduleTestDecls @@ -159,6 +161,7 @@ class JavaTranslator( fun module(module: TmpL.Module): List { this.module = module + adjuster = adjusterFactory?.makeAdjuster(module) val result = module.result topLevels@ for (tl in module.topLevels) { try { @@ -238,6 +241,7 @@ class JavaTranslator( addAll(moduleTestDecls) } } + adjuster?.also { programs.addAll(it.additionalFilesAfterTranslation()) } return programs } diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt index 0d0c4e2c..bb14a18a 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/FunctionalTestRunner.kt @@ -160,7 +160,7 @@ abstract class FunctionalTestRunner>( outputRoot = outputRoot, preparedModules = preparedModules, test = test, - adjuster = backendOrganization.adjusters[neededBackendId], + adjusterFactory = backendOrganization.adjusterFactories[neededBackendId], ) } @@ -205,7 +205,7 @@ abstract class FunctionalTestRunner>( outputRoot: OutputRoot, preparedModules: PreparedFunctionalTest, test: FunctionalTestBase, - adjuster: BackendAdjuster?, + adjusterFactory: BackendAdjusterFactory?, ) = run { val supportedBackendList = listOf(backendId) val functionalTestLibraryConfiguration = LibraryConfiguration( @@ -315,7 +315,7 @@ abstract class FunctionalTestRunner>( config = config, dependenciesBuilder = dependenciesBuilder, rawBackendFiles = rawBackendFiles, - adjuster = adjuster, + adjusterFactory = adjusterFactory, ), ) } diff --git a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt index f4117322..764caf85 100644 --- a/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt +++ b/be-test-helpers/src/commonMain/kotlin/lang/temper/be/GeneratedCodeHelper.kt @@ -103,7 +103,7 @@ fun > generateCode( moduleResultNeeded = moduleResultNeeded, logSink = logSink, outputRoot = outputRoot, - adjuster = factory.adjusters()[backendId], + adjusterFactory = factory.adjusterFactories()[backendId], ) } } @@ -118,7 +118,7 @@ fun > generateCode( moduleResultNeeded: Boolean, logSink: LogSink, outputRoot: OutputRoot, - adjuster: BackendAdjuster?, + adjusterFactory: BackendAdjusterFactory?, activeFactories: Iterable> = listOf(factory), ) { val backendId = factory.backendId @@ -199,7 +199,7 @@ fun > generateCode( logSink, NullDependencyResolver, backendConfig, - adjuster = adjuster, + adjusterFactory = adjusterFactory, ), ) } diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index 452f58d2..79083cac 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -109,7 +109,7 @@ abstract class Backend>( */ val dependenciesBuilder: Dependencies.Builder, val rawBackendFiles: Map = mapOf(), - val adjuster: BackendAdjuster? = null, + val adjusterFactory: BackendAdjusterFactory? = null, ) { constructor(backendId: BackendId, setup: BackendSetup) : this( backendId = backendId, @@ -122,7 +122,7 @@ abstract class Backend>( config = setup.config, dependenciesBuilder = setup.dependenciesBuilder, rawBackendFiles = setup.rawBackendFiles, - adjuster = setup.adjuster, + adjusterFactory = setup.adjusterFactory, ) val libraryConfigurations = dependenciesBuilder.libraryConfigurations @@ -837,7 +837,7 @@ abstract class Backend>( * Adjuster by backend as wanted. Should only be given for backend ids * in [BackendMeta.requiredBackendIds] for this backend. */ - fun adjusters(): Map = mapOf() + fun adjusterFactories(): Map = mapOf() } @Retention(AnnotationRetention.RUNTIME) @@ -906,9 +906,14 @@ data class BackendSetup>( val config: Backend.Config, /** Files matching backend extensions from the library source tree. */ val rawBackendFiles: Map = mapOf(), - val adjuster: BackendAdjuster? = null, + val adjusterFactory: BackendAdjusterFactory? = null, ) +/** Enables module-based lifetimes on individual [BackendAdjuster] instances. */ +interface BackendAdjusterFactory { + fun makeAdjuster(module: TmpL.Module): BackendAdjuster +} + /** * Adjusts, customized, or transforms backend behavior. Might be installed by * one backend on another in some custom Temper configuration. @@ -917,21 +922,34 @@ interface BackendAdjuster { /** * Optionally customizes an already translated connected call. On null * return value, use the original. + * + * Adjusters should return null for any [T] they don't know how to handle + * although typically this is well-defined for a particular backend. */ fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { return null } - // TODO adjustModuleTree to be called on the final product??? - - // TODO addTranslations or addFiles to add more files. + /** + * Return any extra files that should be added to the output translation, + * where [T] depends on the backend being adjusted. + */ + fun > additionalFilesAfterTranslation(): Collection { + return listOf() + } } -/** Combines two adjusters with [this] as priority. */ -fun BackendAdjuster.orElse(other: BackendAdjuster): BackendAdjuster { - return object : BackendAdjuster { - override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { - return adjustConnectedCall(decl, call) ?: other.adjustConnectedCall(decl, call) +/** Combines two adjuster factories with [this] as priority. */ +fun BackendAdjusterFactory.orElse(other: BackendAdjusterFactory): BackendAdjusterFactory { + return object : BackendAdjusterFactory { + override fun makeAdjuster(module: TmpL.Module): BackendAdjuster { + val a = this@orElse.makeAdjuster(module) + val b = other.makeAdjuster(module) + return object : BackendAdjuster { + override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + return a.adjustConnectedCall(decl, call) ?: b.adjustConnectedCall(decl, call) + } + } } } } @@ -975,7 +993,7 @@ data class BackendOrganization( val factoriesById: Map>, /** Priority order rather than chain. */ - val adjusters: Map = mapOf(), + val adjusterFactories: Map = mapOf(), ) data class BackendOrganizationError( @@ -1073,9 +1091,9 @@ fun organizeBackends( } } val backendRequirements = transitiveClosure(backendOrderingFounds).mapValues { setOf(it.key) + it.value } - val adjusters = buildMap { + val adjusterFactories = buildMap { for (factory in factoriesById.values) { - val adjusters = factory.adjusters() + val adjusters = factory.adjusterFactories() val requiredBackendIds = factory.backendMeta.requiredBackendIds for ((backendId, adjuster) in adjusters) { if (backendId !in requiredBackendIds) { @@ -1095,6 +1113,6 @@ fun organizeBackends( backendBuckets = backendBuckets, backendRequirements = backendRequirements, factoriesById = factoriesById, - adjusters = adjusters, + adjusterFactories = adjusterFactories, ) } diff --git a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt index f322fdc4..beafdfc7 100644 --- a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt +++ b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt @@ -1,6 +1,7 @@ package lang.temper.be import lang.temper.be.tmpl.TestBackend +import lang.temper.be.tmpl.TmpL import lang.temper.lexer.defaultClassifyTemperSource import lang.temper.library.LibraryConfiguration import lang.temper.log.dirPath @@ -24,28 +25,31 @@ class BackendTest { class NeedyBackendFactory( backendId: String, requiredBackendIds: List, - val adjusters: Map = mapOf(), + val adjusterFactories: Map = mapOf(), ) : TestBackend.TestFactory() { override val backendId: BackendId = BackendId(backendId) override val backendMeta = super.backendMeta.copy( backendId = this.backendId, requiredBackendIds = requiredBackendIds, ) - override fun adjusters(): Map { - return adjusters + override fun adjusterFactories(): Map { + return adjusterFactories } } val missingBackendId = BackendId("missing") val needyFactory = NeedyBackendFactory("needy", listOf(TestBackend.backendId, missingBackendId)) val needlessFactory = NeedyBackendFactory("needless", listOf()) val uselessAdjuster = object : BackendAdjuster {} + val uselessAdjusterFactory = object : BackendAdjusterFactory { + override fun makeAdjuster(module: TmpL.Module): BackendAdjuster = uselessAdjuster + } val needierFactory = NeedyBackendFactory( backendId = "needier", requiredBackendIds = listOf(needyFactory.backendId, needlessFactory.backendId), - adjusters = mapOf( - needyFactory.backendId to uselessAdjuster, + adjusterFactories = mapOf( + needyFactory.backendId to uselessAdjusterFactory, // We don't directly require this, so we shouldn't be adjusting it. - TestBackend.backendId to uselessAdjuster, + TestBackend.backendId to uselessAdjusterFactory, ), ) val aloofBackend = NeedyBackendFactory("aloof", listOf()) @@ -103,7 +107,7 @@ class BackendTest { organization.backendBuckets, ) assertEquals(backends.keys - setOf(aloofBackend.backendId), organization.factoriesById.keys) - assertEquals(uselessAdjuster, organization.adjusters[needyFactory.backendId]) + assertEquals(uselessAdjusterFactory, organization.adjusterFactories[needyFactory.backendId]) } @Test diff --git a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt index 83a5e29c..21912713 100644 --- a/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt +++ b/tooling/src/commonMain/kotlin/lang/temper/tooling/buildrun/Build.kt @@ -430,7 +430,7 @@ fun doOneBuild(build: Build): BuildResult { logSink = projectLogSink, dependencyResolver = dependencyResolver, config = harness.backendConfig, - adjuster = backendOrganization.adjusters[factory.backendId], + adjusterFactory = backendOrganization.adjusterFactories[factory.backendId], ), ) } From 4f8a2ffb0622c21d14e21142de397260f161f1a9 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 31 Jul 2026 07:15:02 -0700 Subject: [PATCH 4/8] Adjust mutable file list Signed-off-by: Tom --- .../kotlin/lang/temper/be/java/JavaTranslator.kt | 2 +- be/src/commonMain/kotlin/lang/temper/be/Backend.kt | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt index 7edfd025..7aa3a6fb 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt @@ -241,7 +241,7 @@ class JavaTranslator( addAll(moduleTestDecls) } } - adjuster?.also { programs.addAll(it.additionalFilesAfterTranslation()) } + adjuster?.adjustFilesAfterTranslation(programs) return programs } diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index 79083cac..86dde469 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -931,11 +931,12 @@ interface BackendAdjuster { } /** - * Return any extra files that should be added to the output translation, - * where [T] depends on the backend being adjusted. + * Pass any already built files. Most common behavior would be to add more. + * The adjust may want to reference existing files and could technically + * modify them. */ - fun > additionalFilesAfterTranslation(): Collection { - return listOf() + fun > adjustFilesAfterTranslation(files: MutableList) { + // Do nothing by default. } } From d72a62ac4a3210172cf9124c48d76aed58a2bf96 Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 31 Jul 2026 11:31:18 -0700 Subject: [PATCH 5/8] Adjust files in chain Signed-off-by: Tom --- .../kotlin/lang/temper/be/Backend.kt | 7 ++ .../kotlin/lang/temper/be/BackendTest.kt | 70 +++++++++++++++++-- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index 86dde469..e4b96f25 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -947,9 +947,16 @@ fun BackendAdjusterFactory.orElse(other: BackendAdjusterFactory): BackendAdjuste val a = this@orElse.makeAdjuster(module) val b = other.makeAdjuster(module) return object : BackendAdjuster { + /** Only lets the secondary adjuster apply if the first returns null. */ override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { return a.adjustConnectedCall(decl, call) ?: b.adjustConnectedCall(decl, call) } + + /** Adjusts in reverse order, so the priority adjuster has the final say. */ + override fun > adjustFilesAfterTranslation(files: MutableList) { + b.adjustFilesAfterTranslation(files) + a.adjustFilesAfterTranslation(files) + } } } } diff --git a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt index beafdfc7..232c2ed9 100644 --- a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt +++ b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt @@ -1,5 +1,6 @@ package lang.temper.be +import lang.temper.ast.OutTree import lang.temper.be.tmpl.TestBackend import lang.temper.be.tmpl.TmpL import lang.temper.lexer.defaultClassifyTemperSource @@ -7,18 +8,68 @@ import lang.temper.library.LibraryConfiguration import lang.temper.log.dirPath import lang.temper.log.filePath import lang.temper.name.BackendId +import lang.temper.name.BuiltinName import lang.temper.name.DashedIdentifier import lang.temper.name.ModuleName import kotlin.test.Test +import kotlin.test.assertContentEquals import kotlin.test.assertEquals class BackendTest { - private val helloWorldLibraryConfig = LibraryConfiguration( - libraryName = DashedIdentifier.from("hello-world")!!, - libraryRoot = dirPath("a"), - supportedBackendList = emptyList(), - classifyTemperSource = ::defaultClassifyTemperSource, - ) + @Test + fun backendAdjusters() { + val tmpl = TmplGenerator(".test") + // Define some adjusters and factories. + class TestAdjusterA : BackendAdjuster { + override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + // Abusively just treat ids as calls to simplify. + call is TmpL.Id || return null + return when (call.name.displayName) { + "_" -> { + @Suppress("UNCHECKED_CAST") + return tmpl.makeId(BuiltinName("there")) as T + } + else -> null + } + } + override fun > adjustFilesAfterTranslation(files: MutableList) { + @Suppress("UNCHECKED_CAST") + files.add(tmpl.makeId(BuiltinName("a")) as T) + } + } + class TestAdjusterB : BackendAdjuster { + override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + return call + } + override fun > adjustFilesAfterTranslation(files: MutableList) { + @Suppress("UNCHECKED_CAST") + files.add(tmpl.makeId(BuiltinName("b")) as T) + } + } + class TestAdjusterFactory(val adjuster: BackendAdjuster) : BackendAdjusterFactory { + override fun makeAdjuster(module: TmpL.Module): BackendAdjuster = adjuster + } + val comboFactory = TestAdjusterFactory(TestAdjusterA()) + .orElse(TestAdjusterFactory(TestAdjusterB())) + // Work on some tmpl. + val module = tmpl.module { + moduleFunction(BuiltinName("hi")) {} + } + val adjuster = comboFactory.makeAdjuster(module) + val function = module.topLevels.first() as TmpL.FunctionDeclaration + fun adjustCall(name: String): String { + val id = adjuster.adjustConnectedCall(function, tmpl.makeId(BuiltinName(name))) + return id!!.name.displayName + } + // The first adjuster only replaces name "_". The second always returns what it's given. + assertEquals("there", adjustCall("_")) + assertEquals("yall", adjustCall("yall")) + // Now see how we finalize, expecting reverse order. + assertContentEquals( + listOf("b", "a"), + buildList { adjuster.adjustFilesAfterTranslation(this) }.map { it.name.displayName }, + ) + } @Test fun backendOrganization() { @@ -163,4 +214,11 @@ class BackendTest { Backend.defaultFilePathForSource(helloWorldLibraryConfig, moduleName, ".out"), ) } + + private val helloWorldLibraryConfig = LibraryConfiguration( + libraryName = DashedIdentifier.from("hello-world")!!, + libraryRoot = dirPath("a"), + supportedBackendList = emptyList(), + classifyTemperSource = ::defaultClassifyTemperSource, + ) } From 076b85f10dad5d3e79a0fe8063c3754dace251bc Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 31 Jul 2026 16:59:26 -0700 Subject: [PATCH 6/8] Expose translator; expose java helpers Signed-off-by: Tom --- .../lang/temper/be/java/JavaTranslator.kt | 57 +++++++++++++------ .../kotlin/lang/temper/be/java/NameHelpers.kt | 2 +- .../kotlin/lang/temper/be/Backend.kt | 8 +-- .../kotlin/lang/temper/be/BackendTest.kt | 6 +- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt index 7aa3a6fb..eb253a43 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt @@ -161,7 +161,7 @@ class JavaTranslator( fun module(module: TmpL.Module): List { this.module = module - adjuster = adjusterFactory?.makeAdjuster(module) + adjuster = adjusterFactory?.makeAdjuster(module, this) val result = module.result topLevels@ for (tl in module.topLevels) { try { @@ -525,7 +525,7 @@ class JavaTranslator( private fun moduleFunction(t: TmpL.FunctionDeclaration) { val autodoc = autodocFor(t) val name = names.moduleFunction(t.name).second.toIdentifier(t.name.pos) - val result = resultType(names, t.returnType, pos = t.returnType.pos) + val result = resultType(t) val body = when { t.metadata.any { it.key.symbol == connectedSymbol } && module?.isStdLib != true -> connectedBody(t, result) @@ -906,7 +906,7 @@ class JavaTranslator( } } is TmpL.Getter -> { - val result = resultType(names, m.returnType, m.pos) + val result = resultType(m) val name = names.getterName(m.dotName, JavaType.toFrontend(m.returnType.ot)) val parameters = parameters(m.parameters) val prop = t.members.firstNotNullOfOrNull { @@ -932,7 +932,7 @@ class JavaTranslator( ) } is TmpL.Setter -> { - val result = resultType(names, m.returnType, m.pos) + val result = resultType(m) val name = names.setterName(m.dotName) val parameters = parameters(m.parameters) add( @@ -966,7 +966,7 @@ class JavaTranslator( J.ModStatic.Dynamic }, ) - val tentativeResult = resultType(names, m.returnType, m.pos) + val tentativeResult = resultType(m) val boxedTypeAdjustments = (m as? TmpL.NormalMethod)?.let { findJavaParametersThatNeedAdjustmentToBoxedType( @@ -1074,7 +1074,7 @@ class JavaTranslator( modAccess = access(m), modFinal = final(m.assignOnce), ), - type = JavaType.fromTmpL(m.type, names).toTypeAst(m.pos), + type = varType(m), variable = names.field(m.name), initializer = null, ), @@ -1090,7 +1090,7 @@ class JavaTranslator( modFinal = final(m.assignOnce), modStatic = J.ModStatic.Static, ), - type = JavaType.fromTmpL(m.type, names).toTypeAst(m.pos), + type = varType(m), variable = names.staticField(m.dotName), initializer = expr(m.expression), ), @@ -1172,7 +1172,7 @@ class JavaTranslator( pos = m.pos, autodoc = autodocFor(m), body = m.body, - result = resultType(names, m.returnType, m.pos), + result = resultType(m), name = names.getterName(m.dotName, JavaType.toFrontend(m.returnType.ot)), params = parameters(m.parameters), ), @@ -1182,7 +1182,7 @@ class JavaTranslator( pos = m.pos, autodoc = autodocFor(m), body = m.body, - result = resultType(names, m.returnType, m.pos), + result = resultType(m), name = names.setterName(m.dotName), params = parameters(m.parameters), ), @@ -1192,7 +1192,7 @@ class JavaTranslator( pos = m.pos, autodoc = autodocFor(m), body = m.body, - result = resultType(names, m.returnType, m.pos), + result = resultType(m), name = names.method(m.dotName).toIdentifier(m.dotName.pos), params = parameters(m.parameters), typeParams = typeFormals(m.typeParameters), @@ -1204,7 +1204,7 @@ class JavaTranslator( autodoc = autodocFor(m), body = m.body, name = names.method(m.dotName).toIdentifier(m.dotName.pos), - result = resultType(names, m.returnType, m.pos), + result = resultType(m), params = parameters(m.parameters), typeParams = typeFormals(m.typeParameters), isStatic = true, @@ -1219,7 +1219,7 @@ class JavaTranslator( J.InterfaceFieldDeclaration( pos = m.pos, javadoc = javadoc(autodocFor(m.pos, m.metadata)), - type = JavaType.fromTmpL(m.type, names).toTypeAst(m.type.pos), + type = varType(m), variables = listOf( J.VariableDeclarator( m.pos, @@ -1251,6 +1251,28 @@ class JavaTranslator( ) } + fun resultType(fn: TmpL.FunctionDeclarationOrMethod): J.ResultType { + // TODO Some manual calls chose the return type pos but most chose the function pos. + // TODO Maybe makes sense to standardize here, but which is best? + return resultType(names, fn.returnType, fn.pos) + } + + fun resultType(type: TmpL.AType, pos: Position? = null): J.ResultType { + return resultType(names, type, pos ?: type.pos) + } + + fun varType(property: TmpL.Property): J.Type { + return varType(property.type, property.pos) + } + + fun varType(v: TmpL.VarLike): J.Type { + return JavaType.fromFrontend(v.descriptor, names).toTypeAst(v.pos) + } + + fun varType(type: TmpL.AType, pos: Position? = null): J.Type { + return JavaType.fromTmpL(type, names).toTypeAst(pos ?: type.pos) + } + /** Create an object containing the method parameters and necessary preamble statements. */ private fun parameters(px: TmpL.Parameters): ParamsPreamble { val rest = px.restParameter @@ -1413,8 +1435,7 @@ class JavaTranslator( scope.addDecl( J.FieldDeclaration( pos, - type = JavaType.fromTmpL(param.type, names) - .toTypeAst(pos), + type = varType(param), variable = localName.outName.toIdentifier(varId.pos), initializer = newNames[oldName.outName]!!.toNameExpr(varId.pos), ), @@ -1460,7 +1481,7 @@ class JavaTranslator( scope.addDecl( J.FieldDeclaration( stmt.pos, - type = JavaType.fromTmpL(stmt.type, names).toTypeAst(stmt.pos), + type = varType(stmt), variable = localName.outName.toIdentifier(varId.pos), initializer = stmt.init?.let(::expr), ), @@ -1501,7 +1522,7 @@ class JavaTranslator( stmts.add( J.LocalVariableDeclaration( pos, - type = JavaType.fromTmpL(param.type, names).toTypeAst(pos), + type = varType(param), name = newName.toIdentifier(varId.pos), expr = oldName.toIdentifier(varId.pos).asNameExpr(), ), @@ -1577,7 +1598,7 @@ class JavaTranslator( val javaType: J.Type get() = JavaType.fromSig(funcType, names).toTypeAst(pos) /** the result type of the function */ - val javaResultType: J.ResultType get() = resultType(names, tmplFunc.returnType, tmplFunc.returnType.pos) + val javaResultType: J.ResultType get() = resultType(tmplFunc) /** a lambda expression can be used in a local variable declaration, or also in a forward declared form. */ fun toLambdaExpr() = @@ -1667,7 +1688,7 @@ class JavaTranslator( private fun localVar(t: TmpL.LocalDeclaration) = J.LocalVariableDeclaration( t.pos, - type = JavaType.fromTmpL(t.type, names).toTypeAst(t.pos), + type = varType(t), name = names.lookupRegularLocalNameObj(t.name).outName.toIdentifier(t.name.pos), expr = t.init?.let(::expr), ) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/NameHelpers.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/NameHelpers.kt index ac3edbbe..630d4e4d 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/NameHelpers.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/NameHelpers.kt @@ -46,4 +46,4 @@ internal fun ResolvedName.simpleText(): String = when (this) { internal fun ResolvedName.distinctSafeText() = distinctText().safeIdentifier() /** Apply a simple set of rules to extract a name's text and ensure the identifier is safe for Java. */ -internal fun ResolvedName.simpleSafeText() = simpleText().safeIdentifier() +fun ResolvedName.simpleSafeText() = simpleText().safeIdentifier() diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index e4b96f25..ae8a6fdf 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -911,7 +911,7 @@ data class BackendSetup>( /** Enables module-based lifetimes on individual [BackendAdjuster] instances. */ interface BackendAdjusterFactory { - fun makeAdjuster(module: TmpL.Module): BackendAdjuster + fun makeAdjuster(module: TmpL.Module, translator: Any): BackendAdjuster } /** @@ -943,9 +943,9 @@ interface BackendAdjuster { /** Combines two adjuster factories with [this] as priority. */ fun BackendAdjusterFactory.orElse(other: BackendAdjusterFactory): BackendAdjusterFactory { return object : BackendAdjusterFactory { - override fun makeAdjuster(module: TmpL.Module): BackendAdjuster { - val a = this@orElse.makeAdjuster(module) - val b = other.makeAdjuster(module) + override fun makeAdjuster(module: TmpL.Module, translator: Any): BackendAdjuster { + val a = this@orElse.makeAdjuster(module, translator) + val b = other.makeAdjuster(module, translator) return object : BackendAdjuster { /** Only lets the secondary adjuster apply if the first returns null. */ override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { diff --git a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt index 232c2ed9..3e9ec946 100644 --- a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt +++ b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt @@ -47,7 +47,7 @@ class BackendTest { } } class TestAdjusterFactory(val adjuster: BackendAdjuster) : BackendAdjusterFactory { - override fun makeAdjuster(module: TmpL.Module): BackendAdjuster = adjuster + override fun makeAdjuster(module: TmpL.Module, translator: Any): BackendAdjuster = adjuster } val comboFactory = TestAdjusterFactory(TestAdjusterA()) .orElse(TestAdjusterFactory(TestAdjusterB())) @@ -55,7 +55,7 @@ class BackendTest { val module = tmpl.module { moduleFunction(BuiltinName("hi")) {} } - val adjuster = comboFactory.makeAdjuster(module) + val adjuster = comboFactory.makeAdjuster(module, Unit) val function = module.topLevels.first() as TmpL.FunctionDeclaration fun adjustCall(name: String): String { val id = adjuster.adjustConnectedCall(function, tmpl.makeId(BuiltinName(name))) @@ -92,7 +92,7 @@ class BackendTest { val needlessFactory = NeedyBackendFactory("needless", listOf()) val uselessAdjuster = object : BackendAdjuster {} val uselessAdjusterFactory = object : BackendAdjusterFactory { - override fun makeAdjuster(module: TmpL.Module): BackendAdjuster = uselessAdjuster + override fun makeAdjuster(module: TmpL.Module, translator: Any): BackendAdjuster = uselessAdjuster } val needierFactory = NeedyBackendFactory( backendId = "needier", From 2fce2009926d0268482c2beab4b72c486bd375a7 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 3 Aug 2026 08:15:45 -0700 Subject: [PATCH 7/8] Fix lint Signed-off-by: Tom --- be/src/commonMain/kotlin/lang/temper/be/Backend.kt | 4 ++-- be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt index ae8a6fdf..752e1168 100644 --- a/be/src/commonMain/kotlin/lang/temper/be/Backend.kt +++ b/be/src/commonMain/kotlin/lang/temper/be/Backend.kt @@ -926,7 +926,7 @@ interface BackendAdjuster { * Adjusters should return null for any [T] they don't know how to handle * although typically this is well-defined for a particular backend. */ - fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { + fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { return null } @@ -935,7 +935,7 @@ interface BackendAdjuster { * The adjust may want to reference existing files and could technically * modify them. */ - fun > adjustFilesAfterTranslation(files: MutableList) { + fun > adjustFilesAfterTranslation(files: MutableList) { // Do nothing by default. } } diff --git a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt index 3e9ec946..0ce934c1 100644 --- a/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt +++ b/be/src/commonTest/kotlin/lang/temper/be/BackendTest.kt @@ -19,6 +19,7 @@ class BackendTest { @Test fun backendAdjusters() { val tmpl = TmplGenerator(".test") + // Define some adjusters and factories. class TestAdjusterA : BackendAdjuster { override fun > adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? { From 6f3375c47fef49b62f294a5b542f1a68df7fde25 Mon Sep 17 00:00:00 2001 From: Tom Date: Mon, 3 Aug 2026 17:14:33 -0700 Subject: [PATCH 8/8] Allow assignment to static fields in java Signed-off-by: Tom --- be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt | 2 +- .../src/commonMain/kotlin/lang/temper/be/java/SimplifyNames.kt | 1 + .../src/commonMain/kotlin/lang/temper/be/java/java.out-grammar | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt index afa3e866..45779e51 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt @@ -5509,7 +5509,7 @@ object Java { pos: Position, type: QualIdentifier, field: Identifier, - ) : BaseTree(pos), Expression { + ) : BaseTree(pos), Expression, LeftHandSide { override val operatorDefinition get() = JavaOperatorDefinition.Atom override val codeFormattingTemplate: CodeFormattingTemplate diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/SimplifyNames.kt b/be-java/src/commonMain/kotlin/lang/temper/be/java/SimplifyNames.kt index 5dea0c31..cbc85213 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/SimplifyNames.kt +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/SimplifyNames.kt @@ -446,6 +446,7 @@ class SimplifyNames(private val top: J.TopLevelClassDeclaration) { private fun Scope.scanLhs(e: J.LeftHandSide) = when (e) { is J.FieldAccessExpr -> scanExpr(e.expr) is J.NameExpr -> scanName(e) + is J.StaticFieldAccessExpr -> importType(e.type) } private fun Scope.scanArgs(ax: Iterable) = ax.forEach { scanExpr(it.expr) } diff --git a/be-java/src/commonMain/kotlin/lang/temper/be/java/java.out-grammar b/be-java/src/commonMain/kotlin/lang/temper/be/java/java.out-grammar index 7f6ba780..1b6b08ab 100644 --- a/be-java/src/commonMain/kotlin/lang/temper/be/java/java.out-grammar +++ b/be-java/src/commonMain/kotlin/lang/temper/be/java/java.out-grammar @@ -1266,7 +1266,7 @@ AssignmentExpr requires `operator.operator.isAssignment()` ; * The target of an assignment may be a name, or an accessor. * JLS 15.26 TODO add array access */ -LeftHandSide = NameExpr | FieldAccessExpr ; +LeftHandSide = NameExpr | FieldAccessExpr | StaticFieldAccessExpr ; /** * A lambda expression. JLS 15.27