Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

オープン
#111 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
8/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
活発
技術スタック
csharp, java, kotlin

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

説明

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.
主要言語
Java
スター
1
フォーク
1
平均マージ
2時間 22分
マージ済み PR(30日)
4

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

CodeLaser/maddi のほかの issue

CodeLaser/maddi の issue をすべて見る

似ている issue

Java の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。