Skip to content

elide ${} after they're no longer needed and consistently coerce interpolations in untagged string expressions to string#392

Merged
mikesamuel merged 4 commits intomainfrom
elide-empty-interps
Mar 27, 2026
Merged

elide ${} after they're no longer needed and consistently coerce interpolations in untagged string expressions to string#392
mikesamuel merged 4 commits intomainfrom
elide-empty-interps

Conversation

@mikesamuel
Copy link
Copy Markdown
Contributor

@mikesamuel mikesamuel commented Mar 24, 2026

Fixes #388

Partially addresses #388

`.toString()` inference in complex string expressions is still needed.

Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
input = $$"""
|$${"\"\"\""}
|:for (var i = 1; i < 100; i *= 2) {
| ~${i.toString()}, ${}
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.

Working on a commit so the .toString() is implied.

Copy link
Copy Markdown
Contributor

@tjpalmer tjpalmer left a comment

Choose a reason for hiding this comment

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

I know this is marked as draft but looks good overall, so feel free to merge if you're happy with it as is.

Previously, *StrCatMacro* was a macro that resolved to *StrCatFn*,
a regular, translatable function that concatenates string values.

*StrCatMacro* took care to implicitly coerce its arguments to strings.

- `null` values coerce to `"null"`
- otherwise, it's equivalent to structurally applying
  `.toString()` to the argument, but if the type is
  *String* we can skip that step.

This multi-step logic, and the need to generate `if (isNull(...))` for
nullable values, involved some code generation.

Also, with the addition of embedded statements to string expressions,
not all untagged string expressions go through *StrCatMacro*.

This commit extracts out the logic for string coercions and embeds it
in a helper macro, *str*, which erases itself once it has a type for
its argument to code that does the steps above.

This commit also reworks untagged string expression handling so that
interpolated expressions are consistently coerced to strings.

Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
@mikesamuel mikesamuel changed the title lexer: elide ${} after they're no longer needed elide ${} after they're no longer needed and consistently coerce interpolations in untagged string expressions to string Mar 25, 2026
@mikesamuel mikesamuel marked this pull request as ready for review March 25, 2026 19:18
@mikesamuel
Copy link
Copy Markdown
Contributor Author

I know this is marked as draft but looks good overall, so feel free to merge if you're happy with it as is.

@tjpalmer I added another commit, so I think the bug is fixed now.

if (isTagged) {
Replant(next)
} else {
Call(next.pos, CoerceToString) {
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 fixes the bug. Since we weren't going through StrCatMacro, no coercion was being done for string expressions that have embedded statements and are untagged.

Call(macroEnv.pos) {
V(macroEnv.callee.pos, BuiltinFuns.vStrCatFn)
for ((arg, argType) in argsWithTypes) {
if (argType == WellKnownTypes.stringType) {
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 logic is pulled out into a helper below. The only changes are that, instead of looking at macroEnv for information available from the document, we just get that from arg directly.

}
}

internal object CoerceToString : SpecialFunction, BuiltinMacro("str", 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.

str is short and reminiscent of Python's string coercion operator.

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.

Since str waits for type stage, it actually erases at the stage after Stage.Type, Stage.FunctionMacro

@tjpalmer
Copy link
Copy Markdown
Contributor

I looked up the be-js errors and see these messages:

Unexpected static errors
  - CannotTranslate: work/json-syntax-tree.temper.md+4043:Cannot translate builtin str not supported by JS backend!
  - CannotTranslate: work/json-syntax-tree.temper.md+4043:Cannot translate Invalid!
Unexpected static errors
  - CannotTranslate: work/static-properties-scope.temper.md+415:Cannot translate builtin str not supported by JS backend!
  - CannotTranslate: work/static-properties-scope.temper.md+415:Cannot translate Invalid!

Copy link
Copy Markdown
Contributor

@tjpalmer tjpalmer left a comment

Choose a reason for hiding this comment

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

I think the funtest fails are real.

Some expansions, type-aware `as` and `is` simplification and constant
inlining were preventing the string coercion macro from getting type
information.  Previously, doing manual post-Typer simplification of
`.toString()` worked because it happened at a very specific place.

Having type aware macros work is generally good, so I rolled some
changes in to preserve type info better.

Now, `as` and `is` desugar to `if` explicitly which means they flow
into the Weaver nicely.

And subtree inlining preserves type information.

Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
@mikesamuel
Copy link
Copy Markdown
Contributor Author

mikesamuel commented Mar 26, 2026

I think the funtest fails are real.

I think these are fixed now.

|{
| run: ["1, 2, 4, 8, 16, 32, 64, and so on", "String"],
|}
""".trimMargin(),
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.

🎉

Signed-off-by: Mike Samuel <mikesamuel@gmail.com>
@tjpalmer
Copy link
Copy Markdown
Contributor

Looks like a timeout:

java.util.concurrent.TimeoutException: functionsRestFormal() timed out after 60 seconds

@mikesamuel mikesamuel merged commit 63ca699 into main Mar 27, 2026
2 of 3 checks passed
@mikesamuel mikesamuel deleted the elide-empty-interps branch March 27, 2026 17:53
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.

String template weirdness

2 participants