Skip to content

C#: member operators lose their implicit operand, so binary + silently becomes unary + and unary/conversion operators fail with CS1019 #126

Closed

Description

@matt-edmondson

What's wrong

Operators in the AST are member-style: the receiver is implicit, as in C++ (Money operator+(Money rhs)), Rust (impl Add for Point { fn add(self, rhs) }) and Go. CSharpGenerator writes them as C# operators, which are always static and take every operand explicitly. It adds static (Coder/Languages/CSharpGenerator.cs:734-738) but writes the AST's parameters unchanged, so the implicit operand is never added. As a result:

  • A binary operator with one parameter compiles, but as a unary operator. a + b on the type then fails at every call site, and nothing in the generated file shows why.
  • A unary operator with no parameters (CompiledExemplar.Negate) and a conversion operator with no parameters fail with CS1019.
  • With IsReadOnly = true (as in CompiledExemplar.Plus, Negate and ToDouble), the output is static readonly, which fails with CS0106 (:749-752).

Repro

ClassDeclaration money = new("Money") { Kind = TypeDeclarationKind.Struct };
money.Members.Add(new FieldDeclaration("cents", new TypeReference("int")));
FunctionDeclaration plus = new("+") { Kind = FunctionKind.Operator, ReturnType = "Money" };
plus.Parameters.Add(new Parameter("rhs", "Money"));
plus.Body.Add(new ReturnStatement(new VariableReference("rhs")));
money.Members.Add(plus);
FunctionDeclaration toInt = new("ignored") { Kind = FunctionKind.ConversionOperator, ReturnType = "int", IsExplicit = true };
toInt.Body.Add(new ReturnStatement(new LiteralExpression<int>(0)));
money.Members.Add(toInt);
new CSharpGenerator().Generate(money);

Output:

public struct Money
{
    public int cents;
    public static Money operator +(Money rhs)   // compiles, but as UNARY plus
    { return rhs; }
    public static explicit operator int()       // error CS1019
    { return 0; }
}

The C++ output for the same AST is Money operator+(Money rhs) (binary) and explicit operator int(). The exemplar's Plus and Negate come out as public static readonly Point operator +(Point rhs), which fails with CS0106. All errors were confirmed with dotnet build on the generated file (net10.0).

Suggested fix / acceptance criteria

  • For FunctionKind.Operator and ConversionOperator inside a type, prepend a parameter of the enclosing type for the implicit operand, named self as in the C lowering, so bodies that use self.x read the same. A binary operator then has two parameters, a unary operator one, and a conversion takes its source.
  • Never write readonly on a static member.
  • Add a CSharpGeneratedSourceCompilesTests that compiles the generated C#, for example via Microsoft.CodeAnalysis.CSharp, like the existing Cpp/C/Rust/GoGeneratedSourceCompilesTests. Cover CompiledExemplar.Plus(), Negate() and ToDouble(...) on Point(). C# is currently the only statically typed target whose output no test compiles.

Activity

  1. matt-edmondson commented on Sep 29, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions