Skip to content

Commit e948a68

Browse files
authored
Detect and error on duplicate static extension member container names in the same module (same name, different namespaces) (#18821)
1 parent b1dc2f1 commit e948a68

21 files changed

+386
-3
lines changed

docs/release-notes/.FSharp.Compiler.Service/10.0.200.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
### Added
1212

13+
* Detect and error on static extension members extending types with the same simple name but different namespaces in the same module. ([PR #18821](https://github.com/dotnet/fsharp/pull/18821))
1314
* FSharpDiagnostic: add default severity ([#19152](https://github.com/dotnet/fsharp/pull/19152))
1415
* Add warning FS3879 for XML documentation comments not positioned as first non-whitespace on line. ([PR #18891](https://github.com/dotnet/fsharp/pull/18891))
1516
* FsiEvaluationSession.ParseAndCheckInteraction: add keepAssemblyContents optional parameter ([#19155](https://github.com/dotnet/fsharp/pull/19155))

src/Compiler/Checking/PostInferenceChecks.fs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,6 +2680,36 @@ let CheckEntityDefns cenv env tycons =
26802680
// check modules
26812681
//--------------------------------------------------------------------------
26822682

2683+
/// Check for duplicate extension member names that would cause IL conflicts.
2684+
/// Extension members for types with the same simple name but different fully qualified names
2685+
/// will be emitted into the same IL container type, causing a duplicate member error.
2686+
let CheckForDuplicateExtensionMemberNames (cenv: cenv) (vals: Val seq) =
2687+
if cenv.reportErrors then
2688+
let extensionMembers =
2689+
vals
2690+
|> Seq.filter (fun v -> v.IsExtensionMember && v.IsMember)
2691+
|> Seq.toList
2692+
2693+
if not extensionMembers.IsEmpty then
2694+
// Group by LogicalName which includes generic arity suffix (e.g., Expr`1 for Expr<'T>)
2695+
// This matches how types are compiled to IL, so Expr and Expr<'T> are separate groups
2696+
let groupedByLogicalName =
2697+
extensionMembers
2698+
|> List.groupBy (fun v -> v.MemberApparentEntity.LogicalName)
2699+
2700+
for (logicalName, members) in groupedByLogicalName do
2701+
// Check if members extend types from different namespaces/assemblies
2702+
let distinctNamespacePaths =
2703+
members
2704+
|> List.map (fun v -> v.MemberApparentEntity.CompilationPath.MangledPath)
2705+
|> List.distinct
2706+
2707+
if distinctNamespacePaths.Length > 1 then
2708+
// Found extensions for types with same LogicalName but different fully qualified names
2709+
// Report error on the second (and subsequent) extensions
2710+
for v in members |> List.skip 1 do
2711+
errorR(Error(FSComp.SR.tcDuplicateExtensionMemberNames(logicalName), v.Range))
2712+
26832713
let rec CheckDefnsInModule cenv env mdefs =
26842714
for mdef in mdefs do
26852715
CheckDefnInModule cenv env mdef
@@ -2689,6 +2719,7 @@ and CheckNothingAfterEntryPoint cenv m =
26892719
errorR(Error(FSComp.SR.chkEntryPointUsage(), m))
26902720

26912721
and CheckDefnInModule cenv env mdef =
2722+
CheckForDuplicateExtensionMemberNames cenv (allTopLevelValsOfModDef mdef)
26922723
match mdef with
26932724
| TMDefRec(isRec, _opens, tycons, mspecs, m) ->
26942725
CheckNothingAfterEntryPoint cenv m

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,6 +1621,7 @@ featureLowerSimpleMappingsInComprehensionsToFastLoops,"Lowers [for x in xs -> f
16211621
3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation."
16221622
3355,tcNotAnIndexerNamedIndexingNotYetEnabled,"The value '%s' is not a function and does not support index notation."
16231623
3355,tcNotAnIndexerIndexingNotYetEnabled,"This expression is not a function and does not support index notation."
1624+
3356,tcDuplicateExtensionMemberNames,"Extension members extending types with the same simple name '%s' but different fully qualified names cannot be defined in the same module. Consider defining these extensions in separate modules."
16241625
3360,typrelInterfaceWithConcreteAndVariable,"'%s' cannot implement the interface '%s' with the two instantiations '%s' and '%s' because they may unify."
16251626
3361,typrelInterfaceWithConcreteAndVariableObjectExpression,"You cannot implement the interface '%s' with the two instantiations '%s' and '%s' because they may unify."
16261627
featureInterfacesWithMultipleGenericInstantiation,"interfaces with multiple generic instantiation"

src/Compiler/TypedTree/TypedTreeOps.fs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6446,23 +6446,31 @@ and allEntitiesOfModDef mdef =
64466446
yield! allEntitiesOfModDef def
64476447
}
64486448

6449-
and allValsOfModDef mdef =
6449+
and allValsOfModDefWithOption processNested mdef =
64506450
seq { match mdef with
64516451
| TMDefRec(_, _, tycons, mbinds, _) ->
64526452
yield! abstractSlotValsOfTycons tycons
64536453
for mbind in mbinds do
64546454
match mbind with
64556455
| ModuleOrNamespaceBinding.Binding bind -> yield bind.Var
6456-
| ModuleOrNamespaceBinding.Module(_, def) -> yield! allValsOfModDef def
6456+
| ModuleOrNamespaceBinding.Module(_, def) ->
6457+
if processNested then
6458+
yield! allValsOfModDefWithOption processNested def
64576459
| TMDefLet(bind, _) ->
64586460
yield bind.Var
64596461
| TMDefDo _ -> ()
64606462
| TMDefOpens _ -> ()
64616463
| TMDefs defs ->
64626464
for def in defs do
6463-
yield! allValsOfModDef def
6465+
yield! allValsOfModDefWithOption processNested def
64646466
}
64656467

6468+
and allValsOfModDef mdef =
6469+
allValsOfModDefWithOption true mdef
6470+
6471+
and allTopLevelValsOfModDef mdef =
6472+
allValsOfModDefWithOption false mdef
6473+
64666474
and copyAndRemapModDef ctxt compgen tmenv mdef =
64676475
let tycons = allEntitiesOfModDef mdef |> List.ofSeq
64686476
let vs = allValsOfModDef mdef |> List.ofSeq

src/Compiler/TypedTree/TypedTreeOps.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,8 @@ val (|InnerExprPat|): Expr -> Expr
27582758

27592759
val allValsOfModDef: ModuleOrNamespaceContents -> seq<Val>
27602760

2761+
val allTopLevelValsOfModDef: ModuleOrNamespaceContents -> seq<Val>
2762+
27612763
val BindUnitVars: TcGlobals -> Val list * ArgReprInfo list * Expr -> Val list * Expr
27622764

27632765
val isThreadOrContextStatic: TcGlobals -> Attrib list -> bool

src/Compiler/xlf/FSComp.txt.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.fr.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.it.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)