Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion be-java/src/commonMain/kotlin/lang/temper/be/java/Java.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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.

names.scanNames(finished)
finished.modules.flatMap { tmpLModule ->
trans.translate(tmpLModule)
Expand Down
65 changes: 47 additions & 18 deletions be-java/src/commonMain/kotlin/lang/temper/be/java/JavaTranslator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ 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
Expand Down Expand Up @@ -66,6 +68,7 @@ class JavaTranslator(
/** a shared instance for consistent name mapping */
private val topNames: JavaNames,
private val dependenciesBuilder: Dependencies.Builder<JavaBackend>? = 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 =
Expand Down Expand Up @@ -132,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<J.ClassBodyDeclaration>) = when {
processingTestCode -> moduleTestDecls
Expand All @@ -157,6 +161,7 @@ class JavaTranslator(

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.

val result = module.result
topLevels@ for (tl in module.topLevels) {
try {
Expand Down Expand Up @@ -236,6 +241,7 @@ class JavaTranslator(
addAll(moduleTestDecls)
}
}
adjuster?.adjustFilesAfterTranslation(programs)
return programs
}

Expand Down Expand Up @@ -481,7 +487,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) }

@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.

}.let { J.BlockStatement(pos, it) }
}

Expand Down Expand Up @@ -517,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)

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.

val body = when {
t.metadata.any { it.key.symbol == connectedSymbol } && module?.isStdLib != true ->
connectedBody(t, result)
Expand Down Expand Up @@ -898,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 {
Expand All @@ -924,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(
Expand Down Expand Up @@ -958,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(
Expand Down Expand Up @@ -1066,7 +1074,7 @@ class JavaTranslator(
modAccess = access(m),
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.

variable = names.field(m.name),
initializer = null,
),
Expand All @@ -1082,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),
),
Expand Down Expand Up @@ -1164,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),
),
Expand All @@ -1174,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),
),
Expand All @@ -1184,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),
Expand All @@ -1196,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,
Expand All @@ -1211,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,
Expand Down Expand Up @@ -1243,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)
}

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.

Part of the line count increase is just these helpers.


/** Create an object containing the method parameters and necessary preamble statements. */
private fun parameters(px: TmpL.Parameters): ParamsPreamble {
val rest = px.restParameter
Expand Down Expand Up @@ -1405,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),
),
Expand Down Expand Up @@ -1452,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),
),
Expand Down Expand Up @@ -1493,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(),
),
Expand Down Expand Up @@ -1569,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() =
Expand Down Expand Up @@ -1659,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),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

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 wanted access to this. Now I don't remember if I used it in the end or not. If so, that's in a separate repo.

Original file line number Diff line number Diff line change
Expand Up @@ -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<J.Argument>) = ax.forEach { scanExpr(it.expr) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ;

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.


/**
* A lambda expression. JLS 15.27
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ abstract class FunctionalTestRunner<BACKEND : Backend<BACKEND>>(
val backendOrganization = organizeBackends(
listOf(backendId),
lookupFactory = ::lookupFactory,
onMissingFactory = { error(it) },
onError = { error(it) },

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 generalized this for more error kinds now.

)
// TODO Actually build by buckets?
val outputRoot = OutputRoot(MemoryFileSystem())
Expand Down Expand Up @@ -160,6 +160,7 @@ abstract class FunctionalTestRunner<BACKEND : Backend<BACKEND>>(
outputRoot = outputRoot,
preparedModules = preparedModules,
test = test,
adjusterFactory = backendOrganization.adjusterFactories[neededBackendId],

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.

These come originally from the BackendFactory.

)
}

Expand Down Expand Up @@ -204,6 +205,7 @@ abstract class FunctionalTestRunner<BACKEND : Backend<BACKEND>>(
outputRoot: OutputRoot,
preparedModules: PreparedFunctionalTest,
test: FunctionalTestBase,
adjusterFactory: BackendAdjusterFactory?,
) = run {
val supportedBackendList = listOf(backendId)
val functionalTestLibraryConfiguration = LibraryConfiguration(
Expand Down Expand Up @@ -313,6 +315,7 @@ abstract class FunctionalTestRunner<BACKEND : Backend<BACKEND>>(
config = config,
dependenciesBuilder = dependenciesBuilder,
rawBackendFiles = rawBackendFiles,
adjusterFactory = adjusterFactory,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fun <BACKEND : Backend<BACKEND>> generateCode(
val backendOrganization = organizeBackends(
listOf(factory.backendId),
lookupFactory = lookupFactory,
onMissingFactory = { error(it) },
onError = { error(it) },
)
for (bucket in backendOrganization.backendBuckets) {
for (backendId in bucket) {
Expand All @@ -103,6 +103,7 @@ fun <BACKEND : Backend<BACKEND>> generateCode(
moduleResultNeeded = moduleResultNeeded,
logSink = logSink,
outputRoot = outputRoot,
adjusterFactory = factory.adjusterFactories()[backendId],
)
}
}
Expand All @@ -117,6 +118,7 @@ fun <BACKEND : Backend<BACKEND>> generateCode(
moduleResultNeeded: Boolean,
logSink: LogSink,
outputRoot: OutputRoot,
adjusterFactory: BackendAdjusterFactory?,
activeFactories: Iterable<Backend.Factory<*>> = listOf(factory),
) {
val backendId = factory.backendId
Expand Down Expand Up @@ -197,6 +199,7 @@ fun <BACKEND : Backend<BACKEND>> generateCode(
logSink,
NullDependencyResolver,
backendConfig,
adjusterFactory = adjusterFactory,
),
)
}
Expand Down
Loading
Loading