Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Extension: C# as a third front-end language (Roslyn) — architectural fit analysis

Abierto
#111 1 comentario 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
8/100
Tipo de issue
Nueva funcionalidad
Claridad
Bastante claro
Estado de actividad
Activo
Stack tecnológico
csharp, java, kotlin

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

enhancement extension front-end:csharp

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, suspend becomes a Continuation parameter. The Kotlin-specific information the CST needed was added as side data rather than new node types: DetailedSources markers for ?:, ?., a[i] and !!, import aliases, and NullableState. That side data was meant to serve several languages; DetailedSources.java:150-155 names C#, Groovy and TypeScript. C# ??, ?., indexers and is patterns 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|copyOf as defensive copies, the java.util.stream primitive streams, Objects.isNull/nonNull, and AtomicBoolean. 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 how String is already treated.

    The front end chooses the link nature from the type; the link algebra itself does not change.

  • ref, out and in can be lowered the way compilers lower them. kotlinc turns a captured mutable local into a one-element holder (Ref.ObjectRef). Doing the same for ref/out turns variable aliasing into "an object with one field", which the link engine already handles. It loses some precision but needs no structural change. ref locals/returns, Span<T>, unsafe, dynamic and goto would 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 @NullMarked and its annotations. NullabilityPass.Policy already 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

  1. The CST is Java-shaped at the type level. DetailedSources.java:119 says outright that the CST "models Java". TypeInfo has isJavaLangObject, isJavaLangString, isBoxedLong and similar, plus a fixed set of Java primitives, and Predefined is built around them. Kotlin avoided this by mapping its types onto the JVM's (Any → java.lang.Object). C# cannot: System.String is not java.lang.String, and their contracts differ. uint, ulong, decimal and nint also don't exist in Java.
    Fix: Predefined supplies 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.
  2. Type identity is a fully qualified name. C# allows List<T> and List<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.
  3. 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.
  4. 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 a dotnet/ subproject. It would publish a versioned serialized schema read by a maddi-csharp-* module pair in maddi, following the producer/driver pattern the other front ends use. Nothing in maddi-mod would 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 Predefined refactor;
  • 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

  1. 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.
  2. Refactor Predefined so the language supplies the root, string, primitive and numeric types (item 1). Fix the generic-arity naming convention (item 2).
  3. 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.
  4. 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.
  5. Nullability, with Roslyn's nullable-reference-type analysis as the oracle.
  6. 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

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de CodeLaser/maddi

Todos los issues de CodeLaser/maddi

Issues similares

Más issues de Java

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.