Skip to content

Commit 125e0c9

Browse files
committed
Match extension method metadata to emitted signatures
Keep extension receivers in static method parameter identities, names, and counts. Use the actual declaring module and recover unnamed receiver metadata from the implementation binder. Validation: Debug solution build passes with zero warnings or errors. All 61 service HotReload and surface-area tests pass. Regressions verify receiver parameter updates, emitted parameter identities, and constructor parameter preservation.
1 parent 79e39a7 commit 125e0c9

3 files changed

Lines changed: 73 additions & 11 deletions

File tree

‎src/Compiler/CodeGen/IlxDeltaEmitter.fs‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3462,9 +3462,7 @@ let private createMetadataReferenceRemapper (context: MetadataReferenceRemapCont
34623462
RemapAssemblyRefToken = remapAssemblyRefToken
34633463
}
34643464

3465-
/// Emits the delta artifacts for a request. The current implementation populates token projections
3466-
/// while leaving the raw metadata/IL/PDB payload empty; future work will replace the placeholders
3467-
/// with fully emitted heaps.
3465+
/// Emits metadata, IL, and portable PDB deltas with token mappings for the baseline and new definitions.
34683466
///
34693467
/// <paramref name="freshDebugPdb"/> carries the FRESH compile's on-disk portable PDB when the
34703468
/// caller has one and no emitted-artifact PDB is available. <c>request.EmittedArtifacts</c>

‎src/Compiler/TypedTree/TypedTreeDiff.fs‎

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,13 @@ let private tryGetMethodTyparOrdinalsAndGenericArity (g: TcGlobals) (var: Val) =
626626
let private tryGetParameterTypeIdentities (g: TcGlobals) (typarOrdinals: Map<Stamp, int>) (var: Val) =
627627
let parameterTypes =
628628
match var.MemberInfo, var.ValReprInfo with
629+
| Some _, Some valReprInfo when var.IsExtensionMember ->
630+
let numEnclosingTypars = CountEnclosingTyparsOfActualParentOfVal var
631+
632+
let _, _, argInfos, _, _ =
633+
GetValReprTypeInCompiledForm g valReprInfo numEnclosingTypars var.Type var.Range
634+
635+
argInfos |> List.concat |> List.map fst
629636
| Some _, _ -> ArgInfosOfMember g (mkLocalValRef var) |> List.concat |> List.map fst
630637
| None, Some valReprInfo ->
631638
let _, argInfos, _, _ = GetValReprTypeInFSharpForm g valReprInfo var.Type var.Range
@@ -1641,10 +1648,10 @@ let private slotParameterMetadataIdentity denv (var: Val) =
16411648
match var.ValReprInfo with
16421649
| None -> "none"
16431650
| Some(ValReprInfo(_, arguments, result)) ->
1644-
// The instance receiver has no Param row and its source name does not affect metadata.
1651+
// Extension receivers are real parameters of static methods and retain their metadata.
16451652
let emittedArguments =
1646-
match var.MemberInfo, arguments with
1647-
| Some memberInfo, _ :: rest when memberInfo.MemberFlags.IsInstance -> rest
1653+
match arguments with
1654+
| _ :: rest when ValSpecIsCompiledAsInstance denv.g var -> rest
16481655
| _ -> arguments
16491656

16501657
identityNode
@@ -2355,6 +2362,21 @@ let private snapshotModuleEntity denv (moduleEntity: ModuleOrNamespace) path : E
23552362
IsSynthesized = false
23562363
}
23572364

2365+
/// Gets the emitted receiver name when an extension method lacks a signature argument name.
2366+
let private tryExtensionReceiverName (var: Val) expr =
2367+
match var.MemberInfo with
2368+
| Some memberInfo when var.IsExtensionMember && memberInfo.MemberFlags.IsInstance ->
2369+
// IlxGen.GenParams falls back to the implementation binder for unnamed signature arguments.
2370+
let rec receiverName expr =
2371+
match stripDebugPoints expr with
2372+
| Expr.TyLambda(_, _, body, _, _)
2373+
| Expr.TyChoose(_, body, _) -> receiverName body
2374+
| Expr.Lambda(_, _, _, receiver :: _, _, _, _) -> Some receiver.LogicalName
2375+
| _ -> None
2376+
2377+
receiverName expr
2378+
| _ -> None
2379+
23582380
let rec private snapshotModuleBinding g denv (path: string list) (map, entities) binding =
23592381
match binding with
23602382
| ModuleOrNamespaceBinding.Binding b ->
@@ -2398,7 +2420,12 @@ and private tryGetContainingEntityFullName (var: Val) =
23982420
match var.MemberInfo with
23992421
| Some memberInfo ->
24002422
try
2401-
let tyconRef = memberInfo.ApparentEnclosingEntity
2423+
let tyconRef =
2424+
if var.IsExtensionMember then
2425+
(mkLocalValRef var).DeclaringEntity
2426+
else
2427+
memberInfo.ApparentEnclosingEntity
2428+
24022429
let ilTypeRef = tyconRef.CompiledRepresentationForNamedType
24032430
Some(ilTypeRef.FullName)
24042431
with _ ->
@@ -2434,10 +2461,7 @@ and private snapshotBinding g denv path (TBind(var, expr, _)) =
24342461
with _ ->
24352462
None
24362463

2437-
let isInstanceMember =
2438-
match var.MemberInfo with
2439-
| Some memberInfo -> memberInfo.MemberFlags.IsInstance
2440-
| None -> false
2464+
let isInstanceMember = ValRefIsCompiledAsInstanceMember g vref
24412465

24422466
let totalArgCount =
24432467
var.ValReprInfo
@@ -2468,6 +2492,11 @@ and private snapshotBinding g denv path (TBind(var, expr, _)) =
24682492
|> List.collect (List.map (fun (argInfo: ArgReprInfo) -> argInfo.Name |> Option.map (fun ident -> ident.idText)))
24692493
| None -> []
24702494

2495+
let parameterNames =
2496+
match parameterNames, tryExtensionReceiverName var expr with
2497+
| None :: rest, Some receiverName -> Some receiverName :: rest
2498+
| _ -> parameterNames
2499+
24712500
let parameterMetadataIdentity =
24722501
let argMetadata (argInfo: ArgReprInfo) =
24732502
argInfo.Attribs.AsList()

‎tests/FSharp.Compiler.Service.Tests/HotReload/TypedTreeDiffTests.fs‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -638,3 +638,38 @@ let value (x: A.C) = x
638638

639639
Assert.Empty(result.SemanticEdits)
640640
Assert.Empty(result.RudeEdits)
641+
642+
[<Fact>]
643+
member _.``extension receiver rename reports parameter updates`` () =
644+
use harness = new DiffTestHarness()
645+
let source = "module Library\ntype C() = class end\nmodule Extensions =\n type C with\n member this.M(x: int) = x\n"
646+
harness.Rewrite(source)
647+
let baseline = harness.Compile()
648+
harness.Rewrite(source.Replace("member this.", "member that."))
649+
let updated = harness.Compile()
650+
651+
let result = harness.Diff baseline updated
652+
653+
Assert.Empty(result.RudeEdits)
654+
let edit = Assert.Single(result.SemanticEdits)
655+
Assert.Equal(Some 2, edit.Symbol.TotalArgCount)
656+
Assert.Equal(
657+
Some [ RuntimeTypeIdentity.NamedType("Library.C", []); RuntimeTypeIdentity.NamedType("System.Int32", []) ],
658+
edit.Symbol.ParameterTypeIdentities)
659+
Assert.Equal(Some "Library.Extensions", edit.ContainingEntity)
660+
assertRequiredCapabilities [ "Baseline"; "UpdateParameters" ] result
661+
662+
[<Fact>]
663+
member _.``constructor parameter rename reports parameter updates`` () =
664+
use harness = new DiffTestHarness()
665+
harness.Rewrite(Sources.moduleHeader + "type C(before: int) = class end")
666+
let baseline = harness.Compile()
667+
harness.Rewrite(Sources.moduleHeader + "type C(after: int) = class end")
668+
let updated = harness.Compile()
669+
670+
let result = harness.Diff baseline updated
671+
672+
Assert.Empty(result.RudeEdits)
673+
let edit = Assert.Single(result.SemanticEdits |> List.filter (fun edit -> edit.Symbol.CompiledName = Some ".ctor"))
674+
Assert.Equal(Some 1, edit.Symbol.TotalArgCount)
675+
assertRequiredCapabilities [ "Baseline"; "UpdateParameters" ] result

0 commit comments

Comments
 (0)