Skip to content

Support be-js connected code in funtests - #470

Open
tjpalmer wants to merge 13 commits into
mainfrom
connected-js
Open

Support be-js connected code in funtests#470
tjpalmer wants to merge 13 commits into
mainfrom
connected-js

Conversation

@tjpalmer

@tjpalmer tjpalmer commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
  • Make be-js always produce a .internal.js that always exports all top levels
    • This actually net cuts over 100 lines of code in this pr
    • Net line increase is from tests, connected code support, and pretty class names
  • Make be-js class names pretty when possible, so connected code knows how to reference them
  • Support connected code in a separate module so its imports don't clutter Temper-built module namespace

tjpalmer added 8 commits July 24, 2026 17:48
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>
@tjpalmer tjpalmer changed the title Connected js Support be-js connected code in funtests Jul 28, 2026
tjpalmer added 3 commits July 29, 2026 09:26
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
Signed-off-by: Tom <tom@temper.systems>
if (translation.dependencyCategory == DependencyCategory.Test) {
testPaths.add(translation.outPath)
}
}

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 started deleting things I planned to replace then also kept deleting things that weren't being used.

prepareTesting(program, stdTestingRelativePath)
jsDependencies = jsDependencies
.withTestDependency(JsDependency("mocha", "^10.0.0", null))
.withTestDependency(JsDependency("mocha-junit-reporter", "^2.0.2", 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.

Only had 3 lines left from the other fun, so I just inlined here.

.map translations@{ (outPath, program, tmpLModule) ->
if (outPath in testPaths) {
// Functional tests still uses renamed std imports.
// TODO Remove this if we standardize funtests to same imports as elsewhere.

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.

Apparently TODONE.

val outName = outPath.last().fullName
// Skip internal modules.
outName.endsWith(INTERNAL_EXTENSION) && continue@translations
outName.startsWith("_") && continue@translations

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.

Instead of connected.internal.js, I used _connected.js since it's not really an internal version of a separate module or whatever. I'm unsure what best naming is, but this matches my most common _connected naming across backends in #465.

Meanwhile, I don't see a need to "export" internals, so this now avoids them.

val stdTestingPrefix = stdTestingRelativePath ?: run {
val std = libraryConfigurations.byLibraryName.getValue(DashedIdentifier.temperStandardLibraryIdentifier)
"${std.jsLibraryName()}/$TESTING_BASENAME"
}

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 looked at funtest-generated code, and it was even also referencing the sibling std dir in the lock file. I didn't change anything with that in this pr, I don't think. I think it's just vestigial concerns above. (Unless there was something in the other code I also deleted/replaced in this pr.)

is OutDir -> {
console.groupIf(f is OutSubDir, "${f.name}/") {
f.files.forEach { dump(it) }
f.files.sortedBy { it.name }.forEach { dump(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.

Extra internal files in ReplTest translation got different order on my machine vs in ci, so I sort them now. I think this is ok for the actual repl.

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.

I think we take pains not to import and reexport, but might be worth checking that a name in one repl chunk continues to mask a name from an earlier repl chunk.

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 think we take pains not to import and reexport, but might be worth checking that a name in one repl chunk continues to mask a name from an earlier repl chunk.

I'll look into 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.

I don't see old repl chunks even showing up, but I do get an export error on translation. This is on main:

$ class A {}
interactive#0: A__0
$ class B { public a: A = new A() }
interactive#1: B__0
$ translate(1, "java")
1: class B { public a: A = new A() }
                       ⇧
[interactive#1:1+20-21]: interactive#0 does not export symbol A__0
Translated java for interactive#1
  interactive/
    src/
      main/
        java/
          interactive/
            i0001/
              B.java: text/x-java-source
                package interactive.i0001;
                final class B {
                    public final A a;
                    public B() {
                        A t_15 = new A();
                        this.a = t_15;
                    }
                    public A getA() {
                        return this.a;
                    }
                }
              I0001Main.java: text/x-java-source
                package interactive.i0001;
                import temper.core.Core;
                import static interactive.i0001.I0001Global.export;
                public final class I0001Main {
                    private I0001Main() {
                    }
                    public static void main(String[] args) {
                        Core.initSimpleLogging();
                        Core.doNothing(export);
                        Core.waitUntilTasksComplete();
                    }
                }
              I0001Global.java: text/x-java-source
                package interactive.i0001;
                public final class I0001Global {
                    private I0001Global() {
                    }
                    public static Class export;
                    static {
                        export = return__13;
                    }
                }
              I0001Main.java.map: application/json
                { "version": 3, "file": "java/interactiv⋯,SAAiC,CAAA,AAAjC,MAAiC;AAAA;AAAA;AAAA" }
              I0001Global.java.map: application/json
                { "version": 3, "file": "java/interactiv⋯BAAA;AAAjC;AAAiC,iBAAAA,UAAA;AAAA;AAAA" }
              B.java.map: application/json
                { "version": 3, "file": "java/interactiv⋯,WAAAF,CAAA;AAAA,oBAAAC,CAAA;AAAA;AAAA" }
interactive#2: void

I get the same error for explicit export of the classes, by the way.

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.

Let me know if I should write up an issue for that, but even separate from that, I haven't found the name masking yet in the relevant segment of code or things that it calls, but I might be overlooking it.

        fun dump(f: OutFile): Unit = when (f) {
            is OutDir -> {
                console.groupIf(f is OutSubDir, "${f.name}/") {
                    f.files.forEach { dump(it) }
                }
            }
            is OutRegularFile -> dumpBinary(
                f.name,
                f.mimeType,
                outputRoot.byteContentOf(f.path) ?: WrappedByteArray.empty,
            )
        }

This seems just to do recursive dir tree dumping. I don't see it in dumpBinary either, but maybe I'm overlooking it.

Anyway, overall, I don't see issues in the code for this pr yet.

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.

That does seem like a bug.

You can write up an issue if you like and/or you could put an @Ignored ReplTest case in there.

| interactive/
| i0000/
| .*
| I0000Main[.]java: text/x-java-source

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.

After sorting, Main is now longer first.

@@ -0,0 +1,20 @@
// @ts-check
import { Hidden } from "./work.internal.js";

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.

Connected code has to import what it wants from internal. And it's because of arbitrary top-level import namespace pollution that I've pulled it out.

Note that there's cyclic imports here because this module is also imported by the translated internal module. I haven't tested with side effects in both, but this is at least fine without top-level side effects here.

And I only use Hidden for static typing in this module, so it's not actually needed, but this proves I can get it imported at least.

@@ -0,0 +1,20 @@
// @ts-check
import { Hidden } from "./work.internal.js";
import { Support } from "./_support.js";

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 testing additional connected support code like for other backends.

};

/**
* @param {Hidden} hidden

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 the only use of Hidden.

Signed-off-by: Tom <tom@temper.systems>
@tjpalmer
tjpalmer marked this pull request as ready for review July 29, 2026 20:21
Signed-off-by: Tom <tom@temper.systems>
@tjpalmer tjpalmer mentioned this pull request Jul 31, 2026
7 tasks

@mikesamuel mikesamuel left a comment

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.

Still looking

private val exportedIds = mutableListOf<Js.Identifier>()
private val importsFromProdToTest = mutableSetOf<JsIdentifierName>()
private val prodTopIds = mutableSetOf<ResolvedName>()
private var hasConnected = false

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.

How much of this import tracking is JS specific?

is OutDir -> {
console.groupIf(f is OutSubDir, "${f.name}/") {
f.files.forEach { dump(it) }
f.files.sortedBy { it.name }.forEach { dump(it) }

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.

I think we take pains not to import and reexport, but might be worth checking that a name in one repl chunk continues to mask a name from an earlier repl chunk.

"moduleMinimal" to """
|
|export {} from "./implement.internal.js";
""".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.

Having the export {} is actually a good thing since some JS engines will treat content as a ScriptBody if it has no import or export declarations, and unlike a ModuleBody those are not strict mode unless explicitly specified.

is OutDir -> {
console.groupIf(f is OutSubDir, "${f.name}/") {
f.files.forEach { dump(it) }
f.files.sortedBy { it.name }.forEach { dump(it) }

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.

That does seem like a bug.

You can write up an issue if you like and/or you could put an @Ignored ReplTest case in there.

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