Extension: C# as a third front-end language (Roslyn) — architectural fit analysis
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 8/100
Línea de trabajo
This is a design analysis, not a code task: start with docs/kotlin-parity-study-2026-09-26.md and ARCHITECTURE.md, then read DetailedSources.java (around line 119) and LinkNatureImpl.java (lines 15-56). The first concrete step is the proposed one-day Java spike that tests whether a struct copy maps to the shares-fields (≈) link. Done means the spike's verdicts are reported on this issue and the Predefined refactor is scoped.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Question
Can C# be added as a third front-end language next to Java and Kotlin, with parity as the eventual aim, reading the semantically resolved tree from Roslyn? Can that happen within the current maddi / maddi-mod architecture, or would it need a redesign?
Verdict
It fits; no redesign is needed. C# becomes a third front end in maddi. maddi-mod would need extensions in places already shaped to take them. The one structural refactor is in the CST's type layer, which assumes Java's primitives and root types (item 1 under "Where it bends"). It is a lot of work but carries no design risk, and it should come before any C# lowering.
This is a desk analysis, based on docs/kotlin-parity-study-2026-09-26.md, ARCHITECTURE.md and the CST and analysis sources. Nothing has been run. The weakest claim is that a struct copy maps to the existing "shares fields" (≈) link; the first step of the plan is a spike that tests it.
Why it fits
-
One shared tree has already been tested with a second language. Kotlin is converted into the existing CST: properties become a getter and setter,
Int?becomes a boxed type,suspendbecomes aContinuationparameter. The Kotlin-specific information the CST needed was added as side data rather than new node types:DetailedSourcesmarkers for?:,?.,a[i]and!!, import aliases, andNullableState. That side data was meant to serve several languages;DetailedSources.java:150-155names C#, Groovy and TypeScript. C#??,?., indexers andispatterns would reuse the same markers. -
The analysis code is language-neutral. prepwork, link and the analyzer contain no Kotlin names. The JDK names they contain sit in 8 files, about 15 string constants:
List/Set/Map.of|copyOfas defensive copies, thejava.util.streamprimitive streams,Objects.isNull/nonNull, andAtomicBoolean. These become hooks each language supplies, a small, contained change. -
Value types fit the existing link vocabulary (
LinkNatureImpl.java:15-56):- Assigning a struct variable copies the value. That is "shares fields" (≈), the link a shallow copy produces, not "identical to" (≡).
- A
readonly struct, or one with only value fields, needs no links at all, which is howStringis already treated.
The front end chooses the link nature from the type; the link algebra itself does not change.
-
ref,outandincan be lowered the way compilers lower them. kotlinc turns a captured mutable local into a one-element holder (Ref.ObjectRef). Doing the same forref/outturns variable aliasing into "an object with one field", which the link engine already handles. It loses some precision but needs no structural change.reflocals/returns,Span<T>,unsafe,dynamicandgotowould be refused with a named placeholder, as the Kotlin front end refuses its unsupported shapes. -
Nullability lines up directly.
#nullable enable,T?and the[NotNullWhen]/[MaybeNull]/[MemberNotNull]attributes follow the same model as JSpecify's@NullMarkedand its annotations.NullabilityPass.Policyalready chooses behaviour per language (Kotlin is one value of it), so C# would be another. Roslyn also computes a nullability state for every expression, which could check the pass the way the guava oracle checks it for Java. -
Library types come from one source. Roslyn provides the symbols of referenced assemblies, so the Kotlin front end's "two models" problem (K2 symbols vs. class files, parity study §2.2) does not arise.
Where it bends
- The CST is Java-shaped at the type level.
DetailedSources.java:119says outright that the CST "models Java".TypeInfohasisJavaLangObject,isJavaLangString,isBoxedLongand similar, plus a fixed set of Java primitives, andPredefinedis built around them. Kotlin avoided this by mapping its types onto the JVM's (Any→java.lang.Object). C# cannot:System.Stringis notjava.lang.String, and their contracts differ.uint,ulong,decimalandnintalso don't exist in Java.
Fix:Predefinedsupplies the root, string, primitive and numeric types for each language, and the predicates ask it instead of comparing JVM names. This touches many call sites but carries no design risk, and Kotlin benefits too. - Type identity is a fully qualified name. C# allows
List<T>andList<T,U>side by side, so the generic arity has to be part of the name (List`1). That convention should be decided once in the CST API, not left to the front end. - Contract authoring is tied to the JVM. Contracts are written as Java shadow classes and resolved against bytecode. C# needs a counterpart written in C# and resolved through Roslyn; the archive format and how it is loaded stay the same. The BCL archive is a large job in its own right. An uncontracted method defaults to "modifies"; the JDK archive has 4,877 annotated members, while the Kotlin archive has 31 and is already its largest verdict gap (parity study §5.2). LINQ adds lazy evaluation: a lambda passed to it runs when the result is iterated, not when the call is made.
- The build. The Roslyn exporter is a .NET project and doesn't belong in the Gradle build. Under the planned three-repo split (
ARCHITECTURE.md:4), it would go in its own small repo or adotnet/subproject. It would publish a versioned serialized schema read by amaddi-csharp-*module pair inmaddi, following the producer/driver pattern the other front ends use. Nothing inmaddi-modwould depend on .NET.
C# features against the current CST
| C# feature | Mapping |
|---|---|
Properties, indexers, events, operators, implicit conversions, extension methods, using, foreach, lock, local functions, records, pattern matching, tuples |
Lower to calls. Roslyn's IOperation tree already resolves these to method symbols. Front end only. |
| Delegates and lambdas | Become functional interfaces, but they must count as standard ones. Otherwise every delegate parameter defaults to "modified", the same trap as Kotlin's FunctionN. Front end, plus a small change to one analyzer predicate. |
async/await, yield return |
Start as opaque calls, as Kotlin's suspend does. Lower to the compiler's state machine later if precision requires it. |
Reified generics, typeof(T), struct/unmanaged constraints |
Generic arity goes into the name (item 2); constraints are carried on the type parameter. |
| Structs | Copies are ≈ links; readonly structs have no links. Needs the spike below. |
ref/out/in parameters |
Lowered to holder objects. |
ref locals/returns, Span<T>, goto, unsafe, dynamic, stackalloc |
Refused with a named placeholder. |
Effort
For comparison, the Kotlin front end is about 12k lines (maddi-kotlin-k2 plus its drivers), a sixth of the Java one. A rough estimate for C#:
- an exporter of 4–6k lines of C#;
- a CST builder of 10–15k lines, mostly lowering code;
- the
Predefinedrefactor; - the BCL contract archive, which grows corpus by corpus.
Overall, roughly 3–5× what Kotlin has cost so far to reach Kotlin's current level.
Plan
- Spike, about 1 day (blocks the rest): confirm that ≈ is the right link for a struct copy. Write it in Java: a mutable "struct" class whose assignments are replaced by shallow copies, then copied, modified and passed around. Check the verdicts.
- Refactor
Predefinedso the language supplies the root, string, primitive and numeric types (item 1). Fix the generic-arity naming convention (item 2). - Spike, 2–3 weeks (go/no-go): an exporter covering classes, methods, statements and calls, a CST builder, and one small corpus with no value types. Run prepwork, link and the analyzer on it. Write a probe of shapes that convert without error but mean something else, like the Kotlin parity study §2.3, so silent-wrong lowerings are counted from day one.
- Front-end coverage, with a placeholder census and two-sided ratchets per corpus. Grow the BCL archive and its C# authoring tool (item 3) alongside.
- Nullability, with Roslyn's nullable-reference-type analysis as the oracle.
- A parity study modelled on
docs/kotlin-parity-study-2026-09-26.md.
Out of scope for now
- A C# printer and rewiring. They are needed only if maddi will rewrite or generate C#; the printer would be about 1k lines, like
cst-print-kotlin, given lowerings that can be reversed. - MSBuild/NuGet restore integration.
- IDE clients.
- Lenguaje dominante
- Java
- Estrellas
- 1
- Forks
- 1
- Merge medio
- 1 h 58 min
- PR fusionados (30 d)
- 5
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de CodeLaser/maddi
-
build/ci good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
-
bug front-end:javac
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
-
C# printer: an inner class of a generic class needs the outer type's arguments (Outer<A>.Inner)Abiertofront-end:csharp
Dificultad 4/5 3-5 días Aptitud para principiantes 30/100
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 10/100
-
Java → C# translation: an idiomatic C# printer driven by the modification and nullability analysesAbiertoenhancement extension front-end:csharp
Dificultad 5/5 Más de una semana Aptitud para principiantes 8/100
Todos los issues de CodeLaser/maddi
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
OpenIntegrationEngine/engine#496 ·
Los mantenedores suelen responder en 1 día
-
enhancement good first issue
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
SasanLabs/VulnerableApp#836 ·
Los mantenedores suelen responder en 1 día
-
enhancement frontend
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
Albertinesilva/backend-engineering-journey-java-springboot#46 ·
-
Snyk: vulnerabilities in jackson-databind 2.21.5Posiblemente ocupada @harrywu23 la tomó hoy. Abiertobug
Dificultad 1/5 1-3 horas Aptitud para principiantes 80/100
SOFTENG-310-Group-4/Project-A1-310-Group-4#115 ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
carlos-emr/carlos#4833 · 1 comentario ·
Los mantenedores suelen responder en 1 día