babashka-sci-dev

2025-10-31T03:25:36.055639Z

@borkdude I got SCI compiling (tests fail) in ClojureCLR. All other platforms are green. https://github.com/babashka/sci/pull/1006 Might this be a good place to merge to master?

borkdude 2025-11-03T17:08:18.818589Z

Feel free to resume this whenever you'd like. I think the PR looks good now from my side.

2025-11-03T18:00:38.162699Z

Approved, added a couple of doc suggestions and license preservation from Reflection.java.

borkdude 2025-11-03T18:42:54.783389Z

do you agree with the widen-box-args! mutation of the array (instead of re-assigning args)?

borkdude 2025-11-03T18:43:35.794099Z

I asked Alex if re-assigning the args was essential. He said it was, but I couldn't find a single case where it mattered. But as a compromise I made widen-box-args mutate the incoming array which isn't shared with anything else

2025-11-03T18:49:17.331639Z

Yep that's an elegant solution imo.

borkdude 2025-11-03T18:52:37.767849Z

nice, merged. so hopefully the CLR work will be easier now for SCI

borkdude 2025-10-31T08:53:49.076699Z

@ambrosebs Amazing you got this far. But with failing tests, why would it be good to merge with master? I'd like to keep CI green on master?

❤️ 1
borkdude 2025-10-31T08:57:32.996719Z

If you have any questions I can help you progress further of course

2025-10-31T16:27:35.544579Z

My main motivation is future dread for complex merges. I can keep plugging away. The interesting questions are next: how to replace the custom reflector.

borkdude 2025-10-31T16:29:32.424269Z

I think the lowest hanging fruit is fix the getId warning that shows a 1000x-ish times in CI now. This is the id of the current thread

borkdude 2025-10-31T16:29:53.712649Z

aaah yes. the reflector

borkdude 2025-10-31T16:30:23.305149Z

can we compile the reflector and publish it on git?

2025-10-31T16:30:37.462249Z

currently prep steps are not supported by cljr

2025-10-31T16:30:56.022939Z

so I guess committing it woudl be the only option atm

borkdude 2025-10-31T16:30:56.981069Z

it's written in Java

2025-10-31T16:31:15.382029Z

Yes, sorry I meant porting it to C#, then dealing with the publishing issue

borkdude 2025-10-31T16:31:32.480729Z

if we just commit the .dll or whatever .net uses we could do that I guess

2025-10-31T16:31:56.079579Z

fair. could you give me some context about what it does vs the built in reflector. I haven't looked at it yet

borkdude 2025-10-31T16:31:57.067589Z

or we could port it to clojure

👌 1
borkdude 2025-10-31T16:32:21.581239Z

I think I've annotated the difference with the original

borkdude 2025-10-31T16:32:26.415069Z

differences

borkdude 2025-10-31T16:32:41.723489Z

it's basically a copy + some differences

2025-10-31T16:32:45.463369Z

nice!

borkdude 2025-10-31T16:33:03.284559Z

/** clojure.lang.Reflector adapted for sci **/
/**  **/
/** Patches made:
    - Extra imports after package decl.
    - Made invokeMatchingMethod public (around line 169)
    - Compiler.FISupport was extracted into sci.impl.FISupport
**/

borkdude 2025-10-31T16:33:19.128969Z

/** PATCH **/
import clojure.lang.Util;
import clojure.lang.RT;
import clojure.lang.Compiler;
import clojure.lang.IFn;
import java.lang.reflect.Proxy;
/** END PATCH **/

borkdude 2025-10-31T16:33:40.565189Z

/* PATCH: made public */
public static Object invokeMatchingMethod(String methodName, List methods, Object target, Object[] args){
	return invokeMatchingMethod(methodName, methods, target != null ? target.getClass() : null, target, args);
}

borkdude 2025-10-31T16:33:54.548279Z

/* PATCH: made public, added argTypes */
public  static Object invokeMatchingMethod(String methodName, List methods, Class contextClass, Object target, Object[] args, Class[] argTypes)
etc

2025-10-31T16:34:15.530369Z

oh this is much less intimidating than I expected 🙂

borkdude 2025-10-31T16:34:27.341419Z

if you want to port it to clojure, that's fine, but I will only be happy if there's not a severe drop in performance :)

2025-10-31T16:35:34.221639Z

yeah just porting it to .cljr might be a good first step, or asking clojure-clr to make these methods public

borkdude 2025-10-31T16:35:55.149159Z

it's not just make them public but also "added argTypes" for example

borkdude 2025-10-31T16:36:00.796209Z

it's an extra argument

borkdude 2025-10-31T16:36:21.594909Z

clojure.lang.Reflector doesn't do anything with type hints, but my custom reflector does respect them

2025-10-31T16:36:47.925649Z

oh neat

2025-10-31T16:37:14.679779Z

yeah this should really live in the clojure-clr reflector. I'll propose it upstream

borkdude 2025-10-31T16:37:17.666949Z

does .net not have something like mvn?

borkdude 2025-10-31T16:37:50.175119Z

I don't think that clr will accept it since it's not a reflection of JVM Clojure? it has changes

2025-10-31T16:38:00.907559Z

there's something called NuGet but my eyes glaze over.

2025-10-31T16:38:34.074799Z

I don't think that clr will accept it since it's not a reflection of JVM Clojure? it has changesyeah it's possible it won't be accepted

2025-10-31T16:39:42.696399Z

> -- We support git lib and local directory dependencies only. We do not support maven dependencie or local jars. (Though we should have a discussion about the latter.) -- We are still thinking about how nuget might come into play. It's complicated. -- We do not yet have support for tool publishing and preparation steps. See below. https://github.com/clojure/clr.core.cli?tab=readme-ov-file#depsedn-features

2025-10-31T16:41:34.744379Z

on second thought, you're probably right, I'll just write a .cljr file and skip over the other complexities like cljr limitations and clj perf.

borkdude 2025-10-31T16:42:23.009799Z

I'd be happy to have it .cljc if performance is good

borkdude 2025-10-31T16:42:35.976659Z

that would also make porting SCI to ClojureDart easier

2025-10-31T16:42:45.228239Z

ooh nice.

borkdude 2025-10-31T16:43:19.759589Z

there might also be a bunch of unused stuff in the reflector

👍 1
borkdude 2025-10-31T16:43:28.455699Z

so just begin from sci.impl.reflector see what is called

borkdude 2025-10-31T16:43:43.551289Z

not sure if everything is expressible in clojure

2025-10-31T16:44:38.166319Z

I could also go the other direction and see if hello world even works in cljr. But this seems more fun 🙂

borkdude 2025-10-31T16:57:17.541789Z

both seem fun

2025-10-31T16:58:07.370689Z

agreed and exciting

borkdude 2025-10-31T16:58:37.921149Z

there's also a native-image type of thing for dotnet:

dotnet publish -r linux-x64 -c Release /p:PublishAot=true
so one could possibly make a fast starting bb-like thing for dotnet as well

1
borkdude 2025-10-31T16:58:51.483219Z

not sure how well it works/is adopted

borkdude 2025-10-31T17:02:49.061349Z

pfff:

/usr/local/share/dotnet/sdk/9.0.306/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(107,5): error NETSDK1204: Ahead-of-time compilation is not supported on the current platform 'osx-arm64'.

2025-10-31T17:05:22.235639Z

boo

2025-10-31T17:29:35.842909Z

I decided to dispatch copilot in both directions. apparently the bottom-up direction is promising: > SCI has excellent support for non-interop scenarios on ClojureCLR! > All tested features work perfectly: > • Basic data types and collections > • Anonymous functions and function literals > • Control flow (if, when, and, or) > • Sequence operations (map, filter, reduce) > • Threading macros (→, →→) > • Variable definition and namespaces > • Higher-order functions (apply, comp, partial) > • Evaluation with context/bindings > Known limitations (require custom reflector not yet ported): > • defn - function definitions > • loop/recur > • Destructuring >

borkdude 2025-10-31T17:30:47.877889Z

why are those last three dependent on reflector? should not be

2025-10-31T17:31:11.890929Z

agreed, I'm assuming there's some unrelated porting error

2025-10-31T17:31:23.474989Z

I'll look into that manually

2025-10-31T17:32:06.752179Z

I talked about the reflector being the major missing piece in the prompt, probably latched onto that as the reason for all problems.

borkdude 2025-10-31T17:32:20.229649Z

lol, yeah the easy way out

2025-10-31T17:32:37.006299Z

to its credit it fixed the getId problem without me even asking

2025-10-31T17:33:04.533509Z

tho I don't really know what that is yet, didn't look into it myself

borkdude 2025-10-31T17:34:11.248009Z

it is just an identifier for the current thread

2025-10-31T17:37:25.883049Z

ah, yeah simple fix

2025-10-31T17:38:42.202629Z

I wonder if we do this translation in one commit, if it can be used as training data for the next platform.

2025-10-31T17:39:16.808209Z

copilot really struggles with parens matching with reader conditionals tho.

2025-10-31T17:40:23.202429Z

it's ok with indentation. I had an idea to direct it to use par-infer to infer to parens based on indentation, instead of what it usually does, which is write python programs to count parens.

2025-10-31T17:40:44.391789Z

also for it to use edamame for code refactoring.

2025-10-31T17:42:36.901159Z

have you ever tried manipulating/querying programs with edamame? e.g., find all reader conditionals that lack a default branch and move the :clj branch as the final branch and rename to :default.

borkdude 2025-10-31T18:10:19.985799Z

seems more like a job for rewrite-clj

👍 1
borkdude 2025-10-31T18:11:01.367149Z

are you just using copilot or also clojure-mcp or so?

2025-10-31T18:11:39.534539Z

I'm just using copilot agents with a .github/workflows/copilot-setup-steps.yml tailored for clojure

2025-10-31T18:12:43.977649Z

first iteration of the cljc reflector is promising, only native tests fail https://github.com/frenchy64/sci/pull/2

2025-10-31T18:14:00.075249Z

so far I've really disliked having AI in my editor, but had great success just iterating in PR's.

borkdude 2025-10-31T18:20:35.216799Z

if native tests fail then it's reflection likely

2025-10-31T18:22:16.262679Z

there's errors like Caused by: java.lang.IllegalArgumentException: No matching field found: getParameterTypes for class java.lang.reflect.Method

2025-10-31T18:22:45.428929Z

in fact I think that's the only error

2025-10-31T18:26:10.981829Z

do I add this to reflection.json?

borkdude 2025-10-31T18:27:43.694079Z

no

borkdude 2025-10-31T18:27:56.674609Z

let me look at the code

borkdude 2025-10-31T18:28:45.504159Z

you don't get a reflection warning with warn on reflection?

borkdude 2025-10-31T18:28:51.770659Z

did you require it in clojure to check?

2025-10-31T18:29:21.336059Z

I'm just looking at the huge output from the native CI tests

2025-10-31T18:29:34.288199Z

haven't tried it locally yet

borkdude 2025-10-31T18:29:40.272559Z

run it locally and fix the reflection warnings

👍 1
borkdude 2025-10-31T18:29:54.170789Z

reflection in the reflection will make it way slower for sure

😂 1
2025-10-31T18:30:28.960279Z

aha

Reflection warning, sci/impl/reflector.cljc:129:55 - reference to field getClass can't be resolved.
Reflection warning, sci/impl/reflector.cljc:134:45 - reference to field getClass can't be resolved.
Reflection warning, sci/impl/reflector.cljc:271:44 - reference to field getClass can't be resolved.
Reflection warning, sci/impl/reflector.cljc:317:68 - reference to field getParameterTypes can't be resolved.
Reflection warning, sci/impl/reflector.cljc:317:29 - call to method invoke can't be resolved (target class is unknown).
Reflection warning, sci/impl/reflector.cljc:318:40 - reference to field getReturnType can't be resolved.

borkdude 2025-10-31T18:31:10.993189Z

$ clj
Clojure 1.12.1
user=> (load-file "/tmp/reflector.cljc")
Reflection warning, /tmp/reflector.cljc:129:55 - reference to field getClass can't be resolved.
Reflection warning, /tmp/reflector.cljc:134:45 - reference to field getClass can't be resolved.
Reflection warning, /tmp/reflector.cljc:271:44 - reference to field getClass can't be resolved.
Reflection warning, /tmp/reflector.cljc:317:68 - reference to field getParameterTypes can't be resolved.
Reflection warning, /tmp/reflector.cljc:317:29 - call to method invoke can't be resolved (target class is unknown).
Reflection warning, /tmp/reflector.cljc:318:40 - reference to field getReturnType can't be resolved.
#'sci.impl.reflector/invoke-matching-method

borkdude 2025-10-31T18:31:13.195069Z

yeah

2025-10-31T18:32:44.367379Z

why oh why is .getClass a reflection warning.

borkdude 2025-10-31T18:33:02.784399Z

just slap an ^Object tag on it

2025-10-31T18:33:32.083629Z

but why not hardcode object methods? I don't understand

2025-10-31T18:33:37.350719Z

philosophically

borkdude 2025-10-31T18:33:56.380719Z

what do you mean with hardcode object methods?

2025-10-31T18:34:43.099069Z

is .getClass ever ambiguous? could it refer to anything but Object/.getClass? this is what I'm unsure about. if not, couldn't we (clojure) elide the need for ^Object.

borkdude 2025-10-31T18:35:15.222299Z

don't know, I just know that I've needed to to this to avoid reflection 🤷

2025-10-31T18:35:23.625759Z

ok we're on the same page 😄

borkdude 2025-10-31T18:35:32.862139Z

I guess reflector doesn't have a special case for Object methods

2025-10-31T18:36:05.081059Z

maybe you could have a field called .getClass?

borkdude 2025-10-31T18:36:19.326809Z

yeah could also be it

2025-10-31T18:36:45.881639Z

who wins in that case? (.getClass foo) if foo has a field and method with 0 params

2025-10-31T18:37:41.359279Z

haha maybe the answer is literally in the code I'm refactoring.

borkdude 2025-10-31T18:38:09.564399Z

I think getClass field wins

2025-10-31T18:41:04.601889Z

ah. ok then maybe a similar idea with (Object/.equals foo bar)

borkdude 2025-10-31T18:41:15.769979Z

yes

🎉 1
2025-10-31T18:42:06.673349Z

I wonder why (.getClass ^Number foo) is a reflection warning.

2025-10-31T18:42:36.382709Z

a subclass could have a field I guess?

borkdude 2025-10-31T18:43:50.453339Z

user=> ((fn [x] (.getClass ^Number x)) 1)
java.lang.Long

2025-10-31T18:44:01.697399Z

oh wait it's not a reflection warning. I swear I just fixed that in the code.

borkdude 2025-10-31T18:44:02.351389Z

oh reflection warning I see

borkdude 2025-10-31T18:44:17.147429Z

user=> (set! *warn-on-reflection* true)
true
user=> ((fn [x] (.getClass ^Number x)) 1)
java.lang.Long

2025-10-31T18:44:34.883229Z

nvm it works 🙂

borkdude 2025-10-31T18:45:12.455949Z

you might also want to enable:

(set! *unchecked-math* :warn-on-boxed)

👍 1
borkdude 2025-10-31T18:45:46.850439Z

probably not as important as reflection warnings but might prevent some unnecessary boxing here and there

2025-10-31T18:46:05.877519Z

oh interesting!

user=> (deftype Foo [getClass])
user.Foo
user=> (#(.getClass ^Foo %) (->Foo 1))
user.Foo

borkdude 2025-10-31T18:48:11.475129Z

yeah I guess we can save these edge cases for some other time :)

2025-10-31T18:48:32.190469Z

haha yes xD

borkdude 2025-10-31T18:48:49.323799Z

although it might be worth it to file it in clojure-dev

2025-10-31T18:53:45.096589Z

yes, thanks for talking me through that. back to the task.

2025-10-31T19:03:49.113229Z

all green with the resolved reflection. should I pr? https://github.com/frenchy64/sci/pull/2

borkdude 2025-10-31T19:04:32.521999Z

yes. I won't merge it immediately, but will push it to a branch first so I can also test it against babashka and do some local reflection performance tests.

borkdude 2025-10-31T19:04:45.905199Z

and bb CI

borkdude 2025-10-31T19:04:50.452559Z

to see if it passes all tests

borkdude 2025-10-31T19:05:08.510299Z

this is again exciting!

❤️ 1
2025-10-31T19:05:12.239819Z

great. I will just copy it to a .cljr and customize it there. I think this will solve the clr reflection issue.

borkdude 2025-10-31T19:05:35.098079Z

good, but you do make a PR to SCI right?

2025-10-31T19:05:59.976449Z

no I made a PR to my own fork, IIRC the tests weren't running

borkdude 2025-10-31T19:06:08.947409Z

I mean will

2025-10-31T19:06:15.752269Z

yes squashing and pr'ing.

borkdude 2025-10-31T19:06:20.757039Z

🎉

borkdude 2025-10-31T19:06:34.686189Z

maybe this could eventually be a public library even

borkdude 2025-10-31T19:06:46.420699Z

although the market for it would be very niche probably ;)

borkdude 2025-10-31T19:07:24.418029Z

(I haven't needed something like this in other projects yet)

2025-10-31T19:07:32.743749Z

😄 sorta like backtick, you never need it until you do https://github.com/brandonbloom/backtick

borkdude 2025-10-31T19:09:25.652649Z

yeah until now I've never really needed that one

borkdude 2025-10-31T19:09:53.634079Z

although I can see how I could use it in squint maybe

borkdude 2025-10-31T19:10:04.545789Z

to make gensyms in macro bodies more deterministic

borkdude 2025-10-31T19:10:24.090689Z

right now I'm threading my own gensym through macro envs

borkdude 2025-10-31T19:10:35.994029Z

anyway, this is also a tangent :)

😄 1
2025-10-31T19:12:22.505099Z

I used it to iterate more quickly on exploring optimizations to Clojure's own syntax-quote 🙂

2025-10-31T19:18:54.994919Z

I put a big reminder we need to comb through each line before merging 🙂 https://github.com/babashka/sci/pull/1007

borkdude 2025-10-31T19:20:34.080429Z

lol, look how I mistyped locally:

$ checkout -b frenchy64-cljc-reflector master
git pull git@github.com:frenchy64/sci.git cljc-reflector
zsh: command not found: checkout
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 11 (delta 8), reused 11 (delta 8), pack-reused 0 (from 0)
Unpacking objects: 100% (11/11), 4.37 KiB | 372.00 KiB/s, done.
From 
 * branch            cljc-reflector -> FETCH_HEAD
Updating f40ee1a..cdf978d
Fast-forward
 deps.edn                              |   4 +-
 project.clj                           |   1 -
 reflector/.gitignore                  |   1 -
 reflector/project.clj                 |  10 ---
 reflector/script/deploy               |   3 -
 reflector/src/sci/impl/FISupport.java |  32 -------
 reflector/src/sci/impl/Reflector.java | 749 --------------------------------------------------------------------------------------------------------------------------------------------------------------
 src/sci/impl/analyzer.cljc            |  15 ++--
 src/sci/impl/interop.cljc             |  18 ++--
 src/sci/impl/reflector.cljc           | 326 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/sci/impl/test.cljc                |   1 -
 11 files changed, 343 insertions(+), 817 deletions(-)
 delete mode 100644 reflector/.gitignore
 delete mode 100644 reflector/project.clj
 delete mode 100755 reflector/script/deploy
 delete mode 100644 reflector/src/sci/impl/FISupport.java
 delete mode 100644 reflector/src/sci/impl/Reflector.java
 create mode 100644 src/sci/impl/reflector.cljc

borkdude 2025-10-31T19:20:43.993589Z

or miscopied

borkdude 2025-10-31T19:21:03.652069Z

now it's locally on my master branch 😂

2025-10-31T19:21:40.218049Z

LOL

borkdude 2025-10-31T19:22:41.904699Z

ok, there it goes on bb CI. 106 clojure libs are tested there. if it passes, I think it should be good, but then should screen the code for some perf issues maybe. https://github.com/babashka/babashka/tree/frenchy64-cljc-reflector

borkdude 2025-10-31T19:24:36.669239Z

would also be interesting to see the effect on image size but I hope it's not so much

🤞 1
borkdude 2025-10-31T19:26:28.901019Z

all interop (unit) tests pass locally

🎉 1
borkdude 2025-10-31T19:29:47.816739Z

lol, looks like interop got even faster:

borkdude@MBP25-2 ~/dev/babashka (frenchy64-cljc-reflector) $ /opt/homebrew/bin/bb -e '(time (dotimes [i 1000000] (.toString 1)))'
"Elapsed time: 2089.690417 msecs"
borkdude@MBP25-2 ~/dev/babashka (frenchy64-cljc-reflector) $ /opt/homebrew/bin/bb --version
babashka v1.12.208
borkdude@MBP25-2 ~/dev/babashka (frenchy64-cljc-reflector) $ ./bb -e '(time (dotimes [i 1000000] (.toString 1)))'
"Elapsed time: 1291.895917 msecs"

borkdude 2025-10-31T19:30:40.392589Z

hmm, no it's probably just a matter of which machine it was compiled on or so.

$ tmp/bb-interop -e '(time (dotimes [i 1000000] (.toString 1)))'
"Elapsed time: 476.7175 msecs"

😁 1
borkdude 2025-10-31T19:31:03.922959Z

I'll compile the master branch locally for comparison

borkdude 2025-10-31T19:32:15.196999Z

From CI:

ERROR in (websockets-test) (test_utils.clj:100)
expected: (= "zomg websockets!" (bb (quote (do (ns net (:require [clojure.string :as str]) (:import ( URI) (java.net.http HttpClient WebSocket$Listener) (java.util.concurrent CompletableFuture) (java.util.function Function))) (let [p (promise) uri (URI. "") listener (reify WebSocket$Listener (onOpen [_ ws] (.request ws 1)) (onText [_ ws data last?] (.request ws 1) (.thenApply (CompletableFuture/completedFuture nil) (reify Function (apply [_ _] (deliver p (str data))))))) client (HttpClient/newHttpClient) ws (-> (.newWebSocketBuilder client) (.buildAsync uri listener) (deref))] (.sendText ws "zomg websockets!" true) (deref p 5000 :babashka.java-net-http-test/timeout))))))
  actual: clojure.lang.ExceptionInfo: ----- Error --------------------------------------------------------------------
Type:     java.lang.NoSuchMethodException
Message:  clojure.lang.Reflector.getAsMethodOfAccessibleBase(java.lang.Class, java.lang.reflect.Method, java.lang.Object)
Location: NO_SOURCE_PATH:1:559

🤔 1
borkdude 2025-10-31T19:36:00.035159Z

some more:

ERROR in (send-test) (test_utils.clj:100)
expected: (= [200 true] (bb (quote (do (ns net (:import ( URI) (java.net.http HttpClient HttpRequest HttpResponse$BodyHandlers))) (def req (-> (HttpRequest/newBuilder (URI. "")) (.GET) (.build))) (def client (HttpClient/newHttpClient)) (def res (.send client req (HttpResponse$BodyHandlers/ofString))) [(.statusCode res) (string? (.body res))]))))
  actual: clojure.lang.ExceptionInfo: ----- Error --------------------------------------------------------------------
Type:     java.lang.NoSuchMethodException
Message:  clojure.lang.Reflector.getAsMethodOfAccessibleBase(java.lang.Class, java.lang.reflect.Method, java.lang.Object)

borkdude 2025-10-31T19:36:37.360259Z

one that is a little bit more narrowed down:

ERROR in (java-time-test) (test_utils.clj:100)
expected: (= "GMT+03:00" (bb "(System/setProperty \"user.timezone\" \"GMT+3\") (.getId (java.time.ZoneId/systemDefault))"))
  actual: clojure.lang.ExceptionInfo: ----- Error --------------------------------------------------------------------
Type:     java.lang.NoSuchMethodException
Message:  clojure.lang.Reflector.getAsMethodOfAccessibleBase(java.lang.Class, java.lang.reflect.Method, java.lang.Object)

borkdude 2025-10-31T19:37:53.893609Z

$ ./bb -e "(.getId (java.time.ZoneId/systemDefault))"
----- Error --------------------------------------------------------------------
Type:     java.lang.NoSuchMethodException
Message:  clojure.lang.Reflector.getAsMethodOfAccessibleBase(java.lang.Class, java.lang.reflect.Method, java.lang.Object)
Location: NO_SOURCE_PATH:1:1

borkdude 2025-10-31T19:38:10.703539Z

this one does work:

$ ./bb -e "(java.time.ZoneId/systemDefault)"
#object[java.time.ZoneRegion 0x40bce22 "Europe/Amsterdam"]

2025-10-31T19:38:49.441749Z

Ah can you use ZoneId/.getId ?

borkdude 2025-10-31T19:39:10.214469Z

yes:

$ ./bb -e "(java.time.ZoneId/.getId (java.time.ZoneId/systemDefault))"
"Europe/Amsterdam"

2025-10-31T19:39:27.119299Z

guessing it didn't faithfully port the argTypes stuff

borkdude 2025-10-31T19:39:56.728809Z

there's actually no arg type here, arg type only works with explicit type hints, there's no return type inference in interop in SCI (yet)

2025-10-31T19:40:17.131719Z

oh! yeah I'm a bit lost in all the layers

2025-10-31T19:40:38.417719Z

this is reflection happening at runtime during sci's interpreter?

borkdude 2025-10-31T19:40:51.952269Z

yes

2025-10-31T19:41:27.433549Z

so it should just know it's a ZoneId since it has the value of the target?

borkdude 2025-10-31T19:41:38.769759Z

but now that we have it in clojure it's a lot easier to debug. let me have a look...

👍 1
2025-10-31T19:41:50.993799Z

brilliant

borkdude 2025-10-31T19:42:22.455209Z

drat, the in the JVM it does work :)

$ clj -M:babashka/dev -e "(.getId (java.time.ZoneId/systemDefault))"
"Europe/Amsterdam"
so I'll have to compile bb to debug, but that's ok

2025-10-31T19:43:13.878949Z

haha disappointed that it works

borkdude 2025-10-31T19:43:16.000349Z

wtf is this?

(let [reflector-class clojure.lang.Reflector
                                         method (.getMethod reflector-class "getAsMethodOfAccessibleBase"
                                                           (into-array Class [Class Method Object]))]
                                     (.invoke method nil
                                              (object-array [(or context-class (.getDeclaringClass m))
                                                            m
                                                            target])))

2025-10-31T19:43:23.310399Z

I don't know

borkdude 2025-10-31T19:43:36.736769Z

I mean, why does it call clojure.lang.Reflector in such a cumbersome way

borkdude 2025-10-31T19:43:45.030249Z

ooh I know

borkdude 2025-10-31T19:44:04.220349Z

because clojure.lang.Reflector has this as a dynamic method which is conditioned on the JVM version

2025-10-31T19:45:23.384539Z

I think I accidentally included that, it was originally this:

(Reflector/getAsMethodOfAccessibleBase
                                    (or context-class (.getDeclaringClass m))
                                    m
                                    target)
I asked copilot to fix the native errors and that's what it came up with. then I reverted it but obviously not correctly.

2025-10-31T19:46:04.383309Z

oh no wait, other way around.

2025-10-31T19:46:20.702899Z

it was originally the "wtf" code, and it attempted to rewrite to the above, but it broke everything

2025-10-31T19:46:27.226959Z

so I reverted it back to wtf

borkdude 2025-10-31T19:46:49.611139Z

oh yes, it's just a public method:

public static Method getAsMethodOfAccessibleBase(Class c, Method m, Object target){
	for(Class iface : c.getInterfaces())

borkdude 2025-10-31T19:47:08.874709Z

I think this is a reflection issue

borkdude 2025-10-31T19:47:26.819539Z

native-image doesn't have this method in its config so it can't call it, so that explains it

borkdude 2025-10-31T19:47:34.782589Z

just normally calling it should fix it

2025-10-31T19:48:10.935179Z

this was the kind of error for the non-wtf version (jvm):

Error: Exception in thread "main" java.lang.IllegalArgumentException: No matching method: getAsMethodOfAccessibleBase, compiling:(sci/impl/reflector.cljc:305:36)

borkdude 2025-10-31T19:49:30.798369Z

just this should work:

(clojure.lang.Reflector/getAsMethodOfAccessibleBase (or context-class (.getDeclaringClass m))
                                                                                       m
                                                                                       target)
let me test locally

👍 1
borkdude 2025-10-31T19:50:47.411609Z

yep:

$ ./bb -e "(.getId (java.time.ZoneId/systemDefault))"
"Europe/Amsterdam"

borkdude 2025-10-31T19:51:14.811849Z

pushed it to SCI branch

borkdude 2025-10-31T19:51:23.295899Z

if you have additions, let's PR to that branch

👍 1
borkdude 2025-10-31T19:52:33.844929Z

Ran 255 tests containing 666 assertions.
0 failures, 0 errors.

2025-10-31T19:52:37.394289Z

WOW

borkdude 2025-10-31T19:53:54.453849Z

hmm lsp says there are 0 references to this function which is a bit weird

borkdude 2025-10-31T19:54:30.334019Z

oh I have lsp set in the babashka root

borkdude 2025-10-31T19:54:36.248229Z

whatever

borkdude 2025-10-31T20:01:34.149689Z

even more simple, since we can assume JVM 9+

(or (not (Modifier/isPublic
                                                (.getModifiers (.getDeclaringClass m))))
                                         (and target
                                              (.canAccess m target)))

borkdude 2025-10-31T20:01:53.864619Z

or maybe not since clojure still supports 8. we can just say we don't anymore with SCI

👍 1
borkdude 2025-10-31T20:06:00.815449Z

well this is looking good:

$ ./bb -e "(time (dotimes [i 1000000] (.getId (java.time.ZoneId/systemDefault))))"
"Elapsed time: 2168.60525 msecs"

$ tmp/bb-without-cljc-reflector2 -e "(time (dotimes [i 1000000] (.getId (java.time.ZoneId/systemDefault))))"
"Elapsed time: 3312.227791 msecs"

2025-10-31T20:09:35.489449Z

huh??

borkdude 2025-10-31T20:11:51.833519Z

{:test 5508, :pass 20431, :fail 0, :error 0}

1
2025-10-31T20:12:01.659009Z

pretty drastic perf difference for supposedly a transliteration

borkdude 2025-10-31T20:12:30.012709Z

maybe it's just because we removed the dynamic invocation of the method. not sure. graalvm is a bit of a blackbox to me

borkdude 2025-10-31T20:13:03.639389Z

anyway I think it's good to go actually

2025-10-31T20:14:27.358689Z

ha! ok but I never actually read the code closely. I'll take a few minutes later today to step through each line to ensure it's transliterated faithfully.

borkdude 2025-10-31T20:14:56.458259Z

hanks

2025-10-31T20:14:56.490609Z

it generated weird stuff like == to = instead of identical?. I fixed those up, but maybe more.

borkdude 2025-10-31T20:14:58.060089Z

thanks

borkdude 2025-10-31T20:15:11.516019Z

I actually introduced a few == myself

borkdude 2025-10-31T20:15:15.692909Z

into the clojure ocde

borkdude 2025-10-31T20:15:19.729109Z

for numeric comparison

2025-10-31T20:15:37.173489Z

how does the jvm handle that for numerics?

2025-10-31T20:15:47.064009Z

is it really pointer identity?

borkdude 2025-10-31T20:16:09.660439Z

I guess Clojure compiles that to (= 1 2) in bytecode

borkdude 2025-10-31T20:16:18.409669Z

or something similar, don't know

👍 1
borkdude 2025-10-31T20:16:36.671569Z

maybe identical? is even better if you know the types are the same

borkdude 2025-10-31T20:17:26.403799Z

oh:

user=> (require '[clj-java-decompiler.core :as d])
nil
user=> (d/decompile (fn [x y] (== x y)))

// Decompiling class: user$fn_line_1__254
import clojure.lang.*;

public final class user$fn_line_1__254 extends AFunction
{
    public static Object invokeStatic(final Object x, final Object y) {
        return Numbers.equiv(x, y) ? Boolean.TRUE : Boolean.FALSE;
    }

borkdude 2025-10-31T20:17:44.567489Z

and identical?

public final class user$fn_line_1__258 extends AFunction
{
    public static Object invokeStatic(final Object x, final Object y) {
        return Util.identical(x, y) ? Boolean.TRUE : Boolean.FALSE;
    }

borkdude 2025-10-31T20:17:57.731189Z

let's benchmark this

borkdude 2025-10-31T20:20:08.585849Z

I don't think it matters at all in a benchmark :)

borkdude 2025-10-31T20:20:32.222379Z

even = is very very fast

borkdude 2025-10-31T20:21:50.644519Z

hm, getting some of these now in SCI CI:

Caused by: java.lang.IllegalArgumentException: Can't call public method of non-public class: public abstract java.lang.Object java.util.function.Function.apply(java.lang.Object)

borkdude 2025-10-31T20:22:50.605949Z

I must have screwed something up

borkdude 2025-10-31T20:25:11.161869Z

ah yes, the "more simple" commit screwed it up

borkdude 2025-10-31T20:25:19.959239Z

@@ -9,8 +9,10 @@
   - FISupport - extracted from Compiler to support functional interface adaptation"
   {:no-doc true}
   #?(:clj
-     (:import [java.lang.reflect Method Modifier Proxy]
-              [clojure.lang Reflector Compiler IFn RT])))
+     (:import [java.lang.reflect Method Modifier]
+              [clojure.lang Reflector Compiler IFn RT]
+              [java.lang.invoke MethodHandles MethodType]
+              [java.lang.reflect Proxy])))

 #?(:clj (set! *warn-on-reflection* :warn-on-boxed))

@@ -294,8 +296,15 @@
               (let [^Method
                     accessible-m (if (or (not (Modifier/isPublic
                                                 (.getModifiers (.getDeclaringClass m))))
+                                         ;; Check canAccess on Java 9+
                                          (and target
-                                              (.canAccess m target)))
+                                              (try
+                                                (when-let [can-access-method
+                                                           (try (.getMethod Method "canAccess"
+                                                                           (into-array Class [Object]))
+                                                                (catch NoSuchMethodException _ nil))]
+                                                  (not (.invoke ^Method can-access-method m (object-array [target]))))
+                                                (catch Exception _ false))))
                                    (clojure.lang.Reflector/getAsMethodOfAccessibleBase (or context-class (.getDeclaringClass m))
                                                                                        m
                                                                                        target)

borkdude 2025-10-31T20:25:48.398139Z

(not (.canAccesss ...)) ah

borkdude 2025-10-31T20:25:51.226229Z

forgot a not

👍 1
borkdude 2025-10-31T20:27:05.412169Z

I think I can get rid of the borkdude/locking library as well, don't know if you ran into that

2025-10-31T20:27:20.239969Z

didn't need it for cljr so I just dropped it

borkdude 2025-10-31T20:27:23.059569Z

it was a workaround for an issue in clojure

borkdude 2025-10-31T20:27:26.220469Z

back in the day

borkdude 2025-10-31T20:29:11.153609Z

I made a new PR here now. https://github.com/babashka/sci/pull/1008

borkdude 2025-10-31T20:31:24.738039Z

> I'll take a few minutes later today to step through each line to ensure it's transliterated faithfully if you can review that later today I'll merge and bump in bb

👍 1
2025-10-31T22:21:51.098139Z

Good and bad news. I reviewed the wrong PR, and I'm totally clocked out, but I got to the end https://github.com/babashka/sci/pull/1007#pullrequestreview-3406268482 I found only one major issue. There's a place where we call (widen-boxed-args args) and we need to use it to shadow the original args but we don't. https://github.com/babashka/sci/pull/1007#discussion_r2482785595 I ported over the helpful comments and suggestions to refactor to be less opaque.

2025-10-31T22:22:40.039739Z

But now that I reconsider, widen-boxed-args probably mutates its arg, so maybe its fine.

2025-10-31T22:22:58.051629Z

My brain is fried!! talk later

borkdude 2025-10-31T23:20:21.029819Z

Only 2 open comments, rest is resolved in the other PR

borkdude 2025-10-31T23:34:09.819389Z

I made a mistake somewhere along processing the comments. Will try to find the bug tomorrow

borkdude 2025-10-31T23:36:13.448069Z

ah darn, it's lein do clean, test :only babashka.interop-test. I forgot clean, this is why it didn't work locally

borkdude 2025-10-31T23:37:28.955429Z

no, still something's failing. I introduced something stupid somewhere.

ERROR in (SSL-test) (test_utils.clj:82)
expected: (= :user/success (bb nil "(try (.createSocket (javax.net.ssl.SSLSocketFactory/getDefault) \"localhost\" 4444) (catch java.net.ConnectException e ::success))"))
  actual: clojure.lang.ExceptionInfo: ----- Error --------------------------------------------------------------------
Type:     java.lang.reflect.InvocationTargetException

borkdude 2025-10-31T23:37:37.973289Z

(this is in a bb test)

borkdude 2025-10-31T23:41:30.747639Z

oh lol, it turns out you do need the sneakythrow.

borkdude 2025-10-31T23:41:32.539989Z

(try
                (let [ret (.invoke accessible-m target (box-args (.getParameterTypes accessible-m) args))]
                  (Reflector/prepRet (.getReturnType accessible-m) ret))
                (catch Exception e
                  (throw (clojure.lang.Util/sneakyThrow
                          (or (.getCause e) e)))))

borkdude 2025-10-31T23:41:38.846769Z

when I remove it tests fail

borkdude 2025-10-31T23:41:41.920639Z

with the wrong error

borkdude 2025-10-31T23:43:53.415119Z

all good now. I also made widen-box-args mutate the original array since it was reassigned anyway

borkdude 2025-10-31T23:45:25.679609Z

hmm, no that's not clean, I'm not sure if that will have an effect on the original caller...

borkdude 2025-10-31T23:46:30.775839Z

yeah that's actually fine I think