Conversation
…144) The copy takes the assemblies the build loaded, so the loader that resolved an :import is what selects the file to ship. :csharp-out sends them to a dir of their own, which :clean? never wipes.
…mes (#144) The parsed log fields become keywords at the parse boundary and the CLI state becomes one at the entry point, so the expectation maps stop being keyed by string.
…#144) The committed DLL carried the assembly identity `nocomment`, because it was built under a different `-out:` basename and renamed afterwards. Unity's IL2CPP linker resolves a reference by identity, so it looked for `nocomment.dll` and aborted the player build. Mono loads by file path, so `nos dotnet/run-tests` never reached it.
A method written on a deftype, reify or proxy bound to a single MethodInfo, matched on parameter types alone, so an interface that redeclares an inherited member gave several indistinguishable candidates and the analysis reported no match. Candidates now group by name, parameter types and return type, and one group is one slot that the written method fills whole. The emitters skip a NotImplementedException default for every declaration it covers. A return type hint on the method name picks between groups whose return types differ.
…d conv (#148) A type hint made (int x) discard the high bits instead of throwing, because convert-type emitted an unchecked conv opcode whenever both sides were primitive. Reference Clojure calls the RT cast rather than emitting conv.ovf, which throws a different exception and rejects NaN.
…fault (#149) production-flags bound *unchecked-math* true, so every project built with nos build wrapped on integer overflow and skipped the narrowing range checks. The build blocks it replaced bound the flag to *warn-on-reflection*, which reads false, so this was never what any project compiled under. repl.clj still had the same line, where rebinding a var to its own value is the intent.
longCast(object) now returns the ulong local its type check already produced instead of unboxing the box a second time as Int64, which the CLR rejects for a UInt64 box. Casting any boxed ulong threw InvalidCastException, and intCast(object) delegates here, so (int ...) failed the same way.
#153) reinterpret-value now leaves a Single or Double literal alone when the target is an integer type, and catches OverflowException, deferring both to the checked RT cast at runtime. Convert rounds where the cast truncates ((int 1.5) compiled to 2), throws during compilation where no user try/catch exists yet, and on Mono saturates a floating-point source into a silently wrong constant.
file-mode returned FileMode/OpenOrCreate for every write, so an existing file was opened at position 0 without truncating, and :append was ignored. A plain write now opens with FileMode/Create and :append with FileMode/Append, matching JVM Clojure.
print-tagged-object writes (.FullName c), so (pr-str (System.Text.StringBuilder.)) names System.Text.StringBuilder and agrees with print-method on the type object. (.Name c) drops the namespace, and a short name does not identify a type. .FullName is what the JVM's .getName returns. The array branch changes with it, though arrays dispatch to the ICollection print-method and never reach it.
print-throwable writes the :message value after its label, so an #error via map carries the message. The call was absent, so the label was written with nothing after it and the map held an odd number of forms.
resolve-tag writes (.FullName c), so a hint like ^Regex is stored as System.Text.RegularExpressions.Regex and resolves from any namespace. It wrote (.Name c), which drops the namespace, defeating the point of the branch: it exists to turn a short tag into a name that stands on its own. The analyzer infers an invoke's static type from the matching arglist's tag, so this changes emission wherever a dotless tag is recorded.
sort wraps its result with (meta coll), so the sorted seq keeps the metadata, as Clojure 1.10 does. sort-by goes through sort and gains it too.
…165) lift-ns collects [key value] pairs into a vector and print-prefix-map takes those pairs, so #:a{...} prints in the order the map seqs. lift-ns used to accumulate into a map and rebuild with (apply conj (empty m) lm), so once the accumulator outgrew an array-map the original order was gone. qualified-ident? replaces the (or (keyword? k) (symbol? k)) guard, which matches what upstream did in CLJ-2469.
…p key order fix (#165)
…form once (#166, #167) pprint-meta writes ^{...} before a list, vector, map or set when *print-meta* is true, and pprint-set becomes a defn so it can call it. print-doc moves the "Please see" block out of the special-form cond arm into its own when, so the arm no longer prints the docstring that the (when doc ...) below it already prints.
The 5-arity called type with 6 arguments and the next arity takes 7, so every arity below the 7-arity threw ArityException. It now passes the missing custom-attributes as [], the way method does directly below it. The README's Quick Example goes back to the short form, and a new magic.test.mage checks each arity against the full one.
ifn-invoke-compiler boxes a value-typed callee, so the cast to IFn runs on an object reference and the method verifies. castclass needs a reference, and the callee reached it unboxed, which made the JIT refuse the whole method. JVM Clojure emits the same shape, and the arguments beside it already convert the same way. The conversion is guarded on the callee being a value type: converting unconditionally sends an interface-typed callee through castclass Object and re-emits every invoke in the tree.
.NET's Regex.Split has none of Java's three split rules, so they are applied here. Java drops trailing empty strings at limit 0. It returns every part at a negative limit. It also drops a leading empty part when the pattern matches nothing at the start.
…ditor (#157) DebouncedFileWatcher debounces multiple events from FileSystemWatcher upon one save. ClojureReloader polls this debounced event and retries reads if there is any race condition with the write
The bump crosses flybot4 and flybot5, so the Editor runtime picks up the spit and writer truncation, the double round-trip in fp-str, the boxed UInt64 cast, the declaring type in a printed stack frame, the clojure.string/split fixes, and the JVM hash for strings, maps and records. Clojure.dll still reports assembly version 1.11.0.0, so bb coexist-noise asserts the same clojure-versions as before.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.13.0 - 2026-09-09
nos buildcopies a library's C# assemblies into the build output, so shipping a compiled assembly to Unity takes no hand-writtenFile/Copy, and Unity imports the plugin once instead of on every build. Saving a.clj,.cljcor.cljrre-evaluates it in the ClojureCLR Editor, on the one save rather than the third.nos buildalso compiles with*unchecked-math*false, Clojure's default, so arithmetic and a narrowing cast throw on overflow instead of wrapping silently. Fifteen fixes across the compiler, runtime, stdlib and Mage align behaviour with JVM Clojure and ClojureCLR. The Editor runs ClojureCLR 1.11.0-flybot5.Compiler
InvalidCastException, the way JVM Clojure throwsClassCastException, instead ofInvalidProgramException: Invalid IL code. The compiler boxes a value-typed callee before casting it toIFn, so the method it lands in verifies;(1 2)and(let [x (int 1)] (x 2))each produced a method the JIT refused to load - #171.deftype,reifyandproxybind a method to the slot it overrides rather than to one declaration of it, so a type can implement an interface that redeclares an inherited member. Writingcountonclojure.lang.IPersistentMapused to fail withNo match binding method, and naming the interface to get past it left the sibling declarations throwingNotImplementedExceptionat run time. A return type hint on the method name picks between overloads that differ only in return type, as it does on ClojureCLR, and the compiler names the choices when the hint is missing - #146.(int 4294967296)on a^longthrowsArgumentExceptionrather than returning0, as on ClojureCLR and JVM Clojure - #148.(int 1.5)returns 1 instead of 2, and an out-of-range literal like(int 4294967296)throws a catchableArgumentExceptionat runtime instead of aborting compilation with a bareOverflowException- #153.Runtime
UInt64converts instead of throwingInvalidCastException, so(int (identity (ulong 1)))returns 1 - #151.Stdlib
spitandwritertruncate the file they overwrite, and:appendappends, matching the JVM. A write used to open at position 0 without truncating, so shorter content produced a mix of new and old bytes, and:appendwas silently dropped - #155.#object[...]prints the qualified type name, so(pr-str (System.Text.StringBuilder.))namesSystem.Text.StringBuilderinstead ofStringBuilder.print-tagged-objectwrote.Name, which drops the namespace and cannot identify a type, whileprint-methodon the type object already wrote.FullName- #142.pr-strof an exception writes the:messagevalue, so#erroroutput carries the message instead of a bare:messagekey with nothing after it. The:viamap was left with an odd number of forms - #158.defnrecords the qualified type name in an arglist:tag, so a hint like^Regexis stored asSystem.Text.RegularExpressions.Regexand resolves from any namespace instead of only where the import is in scope - #162.sortandsort-bycarry the collection's metadata through to the sorted seq - #163.#:a{...}no longer reorders them once the map outgrows an array-map - #165.clojure.pprint/pprintwrites collection metadata when*print-meta*is true, so a pretty-printed value carries its metadata likeprdoes - #166.clojure.repl/docprints a special form's docstring once instead of repeating it after the "Please see" line - #167.clojure.string/splitdrops trailing empty strings, and a negative limit returns every part.(split "a b " #" ")returned["a" "b" ""].split-linesgained a final""on a trailing newline, and the negative limit threw. A pattern that matches nothing at the start no longer adds a leading"", so(split "abc" #"")returns["a" "b" "c"]- #174.Mage
il/type's short arities work, so a caller can write(il/type "Name" body)instead of spelling out attributes, interfaces, supertype, generic parameters and custom attributes every time. Every arity below the 7-arity threwArityException- #143.Nostrand
nos buildcompiles with*unchecked-math*false, Clojure's default, so arithmetic and narrowing casts keep their overflow checks instead of wrapping silently. A namespace that wants wrapping sets the flag itself - #149.nos buildcopies the C# assemblies a library ships into the output dir, so a consumer no longer writes its ownFile/Copyto get them there. Point:csharp-outat a second dir to keep them out of:out, which:clean?deletes on every build: Unity then imports the plugin once and its GUID holds, instead of a new one on every build - #144.Unity
spitandwritertruncate the file they overwrite, a printed double reads back equal, and(long x)on a boxedUInt64converts.clojure.string/splitdrops trailing empty strings, a stack frame names the code that ran, and a string, map or record hashes to the JVM's value - clojure-1.11.0-flybot4, clojure-1.11.0-flybot5.Magic.Unity.ClojureReloader, so saving a.clj,.cljcor.cljrre-evaluates it in the ClojureCLR Editor on the one save. A consumer's ownFileSystemWatcherhook fires two or three events per save, so a reload took several saves before - #157.