Skip to content

Support cross-backend adjustment - #471

Open
tjpalmer wants to merge 8 commits into
mainfrom
adjust-backends
Open

Support cross-backend adjustment#471
tjpalmer wants to merge 8 commits into
mainfrom
adjust-backends

Conversation

@tjpalmer

@tjpalmer tjpalmer commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
  • Allow a backend that requires another also to conditionally intercept handling of connected calls
  • Report an error when adding an adjuster for a backend not mentioned as required
  • Register required backends separately from adjuster factories because required backend ids are in BackendMeta, which is a more limited context, and maybe that's ok
  • Process this per module, allowing the adjuster to add extra output to a module, for example
  • Type safety isn't forced here; adjust implementers have to be careful
  • Also implement adjustment specifically in be-java, and no others so far except unit test backends, for use from a separate repo

tjpalmer added 7 commits July 29, 2026 17:06
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>

override fun translate(finished: TmpL.ModuleSet) = buildList {
JavaTranslator(names, dependenciesBuilder).let { trans ->
JavaTranslator(names, dependenciesBuilder, adjusterFactory).let { trans ->

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented adjustment only in be-java for now. And in separate test backends also.


fun module(module: TmpL.Module): List<J.Program> {
this.module = module
adjuster = adjusterFactory?.makeAdjuster(module, this)

@tjpalmer tjpalmer Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each adjuster is per module, so I make an adjuster for each ModuleScope in JavaTranslator.

And the this will presumably cause a class cast exception in the adjuster factory if an unexpected type is given.

).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) }

@tjpalmer tjpalmer Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjuster only operate in limited contexts so far, and connected call adjustment is one of those places.

And note that a class cast exception would happen here if adjusters don't dynamically full the generic function signature.

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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my adjuster in a separate repo, I wanted my capabilities and conveniences, so I factored out more helpers and made them public. Then modified code here to use the new helpers.

Note the t.returnType.pos. More cases said t.pos (or equivalent) for resultType calls, so that's what I put in the newly factored helper. If someone has strong opinions to the contrary, let me know.

modFinal = final(m.assignOnce),
),
type = JavaType.fromTmpL(m.type, names).toTypeAst(m.pos),
type = varType(m),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So varType and resultType were my main new factorings.

),
),
errors,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one simple error is now two different kinds of errors.

organization.backendBuckets,
)
assertEquals(backends.keys - setOf(aloofBackend.backendId), organization.factoriesById.keys)
assertEquals(uselessAdjusterFactory, organization.adjusterFactories[needyFactory.backendId])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we also got the proper adjuster factory.

libraryRoot = dirPath("a"),
supportedBackendList = emptyList(),
classifyTemperSource = ::defaultClassifyTemperSource,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this down to be closer to the related tests instead of the unrelated ones I was shoving in front of them.

unknownPos,
listOf(backendId),
listOf(err.backendId),
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the error reporting for temper cli builds instead of funtests.

dependencyResolver = dependencyResolver,
config = harness.backendConfig,
adjusterFactory = backendOrganization.adjusterFactories[factory.backendId],
),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here's the temper cli build grabbing the adjuster factories. I'm not sure we test this anywhere yet, but at least it doesn't break things.

*/
fun <T : OutTree<*>> adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? {
return null
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the doc comment mentioning the need for careful type handling in adjusters to be sure to comply with the unenforceable generics signature. If people don't comply, there typically will be runtime cast exceptions, at least, rather than silent runtime errors.

class TestAdjusterA : BackendAdjuster {
override fun <T : OutTree<*>> adjustConnectedCall(decl: TmpL.FunctionDeclaration, call: T): T? {
// Abusively just treat ids as calls to simplify.
call is TmpL.Id || return null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handling is my recommended way to ensure type safety dynamically for backend adjusters.

@tjpalmer
tjpalmer marked this pull request as ready for review August 3, 2026 19:39
Signed-off-by: Tom <tom@temper.systems>
* JLS 15.26 TODO add array access
*/
LeftHandSide = NameExpr | FieldAccessExpr ;
LeftHandSide = NameExpr | FieldAccessExpr | StaticFieldAccessExpr ;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unrelated to backend adjustment itself, but it's handy for my needs in some adjuster code in a different repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant