Jens Geyer created THRIFT-6199:
----------------------------------

             Summary: CS0121 for container extension methods shared by programs 
that have no include relation
                 Key: THRIFT-6199
                 URL: https://issues.apache.org/jira/browse/THRIFT-6199
             Project: Thrift
          Issue Type: Bug
          Components: netstd - Compiler
            Reporter: Jens Geyer


h3. Problem Description

Follow-up to THRIFT-6198.

THRIFT-6198 stops the netstd generator from emitting a container extension 
method that an *included* program already emits. That covers the reported case, 
but the underlying conflict is not tied to the include relation at all: any two 
programs that end up in the same C# namespace and use the same container type 
declare the same extension method signature, and every call site that sees both 
extension classes fails with

{noformat}
error CS0121: The call is ambiguous between the following methods or properties:
'A1Extensions.DeepCopy(List<int>)' and 'A2Extensions.DeepCopy(List<int>)'
{noformat}

Two topologies remain after THRIFT-6198, both verified against master + 
THRIFT-6198:

h4. 1. Siblings (the common one)

{code:thrift}
// A1.thrift          namespace * MyApp
struct S1 { 1: list<i32> nums }

// A2.thrift          namespace * MyApp
struct S2 { 1: list<i32> nums }

// B.thrift           namespace * MyApp
include "A1.thrift"
include "A2.thrift"
struct B1 { 1: A1.S1 one, 2: A2.S2 two }
{code}

{{A1Extensions}} and {{A2Extensions}} both declare {{DeepCopy(this 
List<int>)}}. Neither program includes the other, so neither generator can see 
the conflict. Compiling the {{-r}} output of {{B.thrift}} fails in {{S1.cs}} 
and {{S2.cs}}.

h4. 2. Container over base types shared with an otherwise unused include

Program B includes A, uses no type declared in A, and both use 
{{list<string>}}. THRIFT-6198 deliberately does not defer here, because B's 
generated code would then depend on files it does not otherwise reference.

h3. Why a per-program rule cannot fix this

A generator instance only ever sees its own program and that program's include 
closure. In topology 1 neither {{A1}} nor {{A2}} appears in the other's 
closure, so no rule evaluated from a single program's point of view can decide 
which of the two should emit the method. A whole-run registry (first program in 
the run claims the type) was considered and rejected: it makes the content of 
{{A1.Extensions.cs}} depend on whether the compiler was invoked with {{-r}} on 
the root program or per file, which breaks reproducible output.

h3. Proposed Behaviour

Make the duplicate declarations harmless instead of trying to prevent all of 
them. Duplicate extension methods in two static classes are perfectly legal C#; 
only an unqualified call that matches both is an error. So generated code 
should call a container's {{DeepCopy()}} through the class that owns it rather 
than through extension method syntax:

{code:c#}
// now
tmp5.Inners = this.Inners.DeepCopy()!;

// proposed
tmp5.Inners = MyAppTypesExtensions.DeepCopy(this.Inners)!;
{code}

That call site is unambiguous no matter how many extension classes of the 
namespace are in scope, and it works the same whether the method stayed with us 
or was left to an included program by THRIFT-6198. Struct and union 
{{DeepCopy()}} are ordinary instance methods and are unaffected.

Duplicate declarations still remain in the two topologies above, so 
hand-written code calling {{someList.DeepCopy()}} in such a namespace continues 
to need qualification. Removing the duplicates altogether is out of reach 
without the cross-program pass described above.


----
Issue description drafted with AI assistance (Claude Opus 5), reviewed by the 
reporter.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to