Skip to content

Support typed config objects - #479

Merged
tjpalmer merged 18 commits into
mainfrom
typed-config
Sep 24, 2026
Merged

tjpalmer merged 18 commits into
mainfrom
typed-config

Conversation

@tjpalmer

@tjpalmer tjpalmer commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor
  • Specifically support typed config for be-java
  • Inject bindings during import stage from all registered backends
  • Also inject std config.temper.md source from backends so it's not present when a backend isn't active and the config bindings are also missing
  • Also evaluate new calls during partial interp for imu types
  • Eval function defs in export stage so we get export values
  • Still support old top-level be-java fields so unchanged libraries still work (and also still has <groupId>dev.temperlang</groupId> and package temper.regex_parser; in generated files):
temper-regex-parser> temper test -b java
Tests passed: 22 of 22

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>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
* Work toward better std config injection
* Move java config def to frontend

Signed-off-by: Tom <tom@temper.systems>
val javaPackageConfigKey = Symbol("javaPackage")

/** Config key to specify Maven dependencies */
val javaDependenciesKey = Symbol("javaDependencies")

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 to config description in frontend to be with the new config type for use in std.

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 to config description in frontend to be with the new config type for use in std.

Now in a different file in be-java.

val pomPath = filePath("pom.xml")
val pomMime = MimeType("text", "xml")

override val configBindingsInjector: BindingsInjector = JavaConfigInjector

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.

We still want these configured on each backend so that plugged backends also can register them, even if we don't yet have a way for them to do custom config for std.

(value.stateVector as InstancePropertyRecord).properties.map { instance ->
(instance.key as SourceName).baseName.nameText to instance.value
}.toMap()
}

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.

Even if the definitions are in the frontend, we use them from the java backend.

And while sloppy checks here for config type are better than nothing, somehow sneaking this properly into frontend type checking might be nice.

What does get checked in frontend now, though, is the the field types themselves for the JavaConfig instance, and that's likely to be helpful.

private val libraryArtifact: String get() = cfg(JavaBackend.javaLibraryArtifactConfigKey) ?: libraryName

private val libraryArtifact: String
get() = cfg(JavaConfigKeys.ARTIFACT, JavaConfigKeys.libraryArtifactGlobal) ?: libraryName

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.

To support fallback for existing temper libs, check both the typed config and if missing then the global config var.

| class: JavaConfig,
| package: "com.example",
| };
| ```,

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.

If we get target-typed prop bags, we could say this instead:

export let java: JavaConfig = {
  package: "com.example",
};

I'd rather not rely on unique keys, even though package is unique right now, because I suspect different backend configs could conflict easily in the general sense.

JavaConfigInjector,
)

private val sharedStdModules = lazy {

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.

With sharedStdModules themselves already global here, I didn't feel too bad about making the injectors also global. Note that both are private to this file.

*/
fun addBindingsInjector(injector: BindingsInjector) {
mutableBindingsInjectors.add(injector)
}

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.

Here's comments about me musing on import resolvers.

dependencies: [
"org.junit.jupiter:junit-jupiter:5.9.2",
],
};

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.

Converted std to new style here, which is what needed the schema code being in frontend.

// Produce exports
stage == Stage.Export -> true
stage == Stage.GenerateCode -> true
else -> false

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 discussed this some on discord, but I can't get values into exports unless either:

  • Export stage actually provides function values or
  • We wait until generate code stage to make exports.

Note that the already existing comment above suggests someone was thinking about exports coming from generate code stage, even though that's not what happens elsewhere.


private fun TypeShape.isImu(): Boolean {
return (stayLeaf?.incoming?.source as? DeclTree)?.parts?.let { imuSymbol in it.metadataSymbolMap } == true
}

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.

Not much code needed to produce new values in partial interp for @imu. I don't know what all other functions run, but at least basic values and lists work as shown in this pr. And supporting proper @partialImu usage would be correct here, too, but I didn't look into that in this pr.

I didn't look to see if partial interp support for lists values check @partialImu rules correctly. Lists of strings were already working before any of my recent changes.

@tjpalmer
tjpalmer marked this pull request as ready for review September 7, 2026 17:16
module.addBindingsInjector(injector)
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Do they need to have the authority to affect all modules, or can we just have their effect limited to config modules.

properties: Map<String, Planting.() -> Any?>,
) {
Call {
Rn(ParsedName(imuDecorator.name))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be a builtinname? And ExportDecorator.name below?

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.

Should this be a builtinname? And ExportDecorator.name below?

In at least some cases, I think BuiltinName was failing on me, but I can do a fresh pass to try things out.

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.

Should this be a builtinname? And ExportDecorator.name below?

In latest checks, BuiltinName worked for imu but not export, so I made the change for imu.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for giving that a try.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems less than ideal to have backend-specific stuff outside of that directory.

Is there any way to avoid that?

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.

It seems less than ideal to have backend-specific stuff outside of that directory.

Is there any way to avoid that?

As I've mentioned, I can't put java config info in be-java and also use it in std, because we don't always have be-java on the classpath.

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.

As I've mentioned, I can't put java config info in be-java and also use it in std, because we don't always have be-java on the classpath.

And I already linked in the pr description my failed attempt at controlling config injectors from backends.

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.

@ShawSumma recommends possibly injecting std config content and not just type definitions. Well, full recommendations could be much more elaborate, but that's the simple scope version that maybe I can fit in this pr. So I'll look into that.

@tjpalmer
tjpalmer marked this pull request as draft September 9, 2026 18:06
* Fail at backend-defined std injectors
* Support also injecting std config source
* Move java config back to be-java

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>
val javaPackageConfigKey = Symbol("javaPackage")

/** Config key to specify Maven dependencies */
val javaDependenciesKey = Symbol("javaDependencies")

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 to config description in frontend to be with the new config type for use in std.

Now in a different file in be-java.

/** Config key to specify Maven dependencies */
const val DEPENDENCIES = "dependencies"
val dependenciesGlobal = Symbol("javaDependencies")
}

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.

Separated these keys out.

)
}
}
}

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 type definition for java config. Maybe could make this temper source like the bonus config section, but I already had this part working before that was done. That source injection is only for std config. This here is for config typing for any library, including std.

dependencies: [
"org.junit.jupiter:junit-jupiter:5.9.2",
],
};

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.

Backends now can add their own separate config chunk for std, so this also works for plugged in configs. Alternatively, they could use custom backend logic, but seeing config for std that matches config for other libraries can be nice.

}
}
}
}

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 the actual helper method that gets called from anywhere that organizes backends (currently 3 places). Most importantly, this provides config for supported backends from the temper cli.

It's also called for funtests and backend tests but without any extra backends listed.

// This typically is for tests where not all registered backends are on the classpath.
// In those cases, this message isn't much interesting, but someone's actual environment could be
// broken, and even in that case, the message should be enough info without a stack dump.
console.error("Error loading Temper plugin $jvmClassName: ${r.failure}")

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.

Shrink error messages for when backends aren't available. This still usually is clear about the issue.

}

val defaultBackend: Lazy<Backend.Factory<*>> =
supportedBackendsGlobal.factories.getValue(BackendId("js")).backend

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.

We were only using this in RunTest and not even in the cli, and it was breaking anyone trying to use anything else in this module if be-js wasn't on the classpath, so I removed it.

properties: Map<String, Planting.() -> Any?>,
) {
Call {
Rn(ParsedName(imuDecorator.name))

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.

Should this be a builtinname? And ExportDecorator.name below?

In latest checks, BuiltinName worked for imu but not export, so I made the change for imu.

onError = { error(it) },
)
// We only get here from backend-specific test classes, so no need for other backends.
backendOrganization.addSharedStdConfigInjectors(listOf())

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 cli, we pass in supportedBackends, but here and for funtests we don't need that, and it causes error messages.

}
},
)
backendOrganization.addSharedStdConfigInjectors(supportedBackends)

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 main build, inject all extra std config for all supported backends. Otherwise, a unit test case for one backend misses config for others in the same test class. This logic should also be fine for cli needs.

@tjpalmer
tjpalmer marked this pull request as ready for review September 14, 2026 18:40
@tjpalmer
tjpalmer merged commit ad0bc12 into main Sep 24, 2026
2 of 6 checks passed
@tjpalmer
tjpalmer deleted the typed-config branch September 24, 2026 18:30
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.

2 participants