|
2 | 2 |
|
3 | 3 | module internal FSharp.Compiler.CheckExpressionsOps |
4 | 4 |
|
| 5 | +open Internal.Utilities.Collections |
5 | 6 | open Internal.Utilities.Library |
6 | 7 | open Internal.Utilities.Library.Extras |
7 | 8 | open FSharp.Compiler.CheckBasics |
@@ -389,3 +390,128 @@ let inline mkOptionalParamTyBasedOnAttribute (g: TcGlobals.TcGlobals) tyarg attr |
389 | 390 | mkValueOptionTy g tyarg |
390 | 391 | else |
391 | 392 | mkOptionTy g tyarg |
| 393 | + |
| 394 | +//------------------------------------------------------------------------- |
| 395 | +// Struct byref capture fix for object expressions |
| 396 | +//------------------------------------------------------------------------- |
| 397 | + |
| 398 | +/// When a struct instance method creates an object expression that captures constructor |
| 399 | +/// parameters, those captures go through 'this' which is a byref. This would create |
| 400 | +/// illegal byref fields in the closure class. This function detects such captures and |
| 401 | +/// extracts them to local bindings before the object expression. |
| 402 | +/// |
| 403 | +/// Returns: (shouldTransform, structCaptures, methodParamStamps) |
| 404 | +let AnalyzeObjExprStructCaptures |
| 405 | + (enclosingStructTyconRefOpt: TyconRef option) |
| 406 | + (ctorCall: Expr) |
| 407 | + (overrides: ObjExprMethod list) |
| 408 | + (extraImpls: (TType * ObjExprMethod list) list) |
| 409 | + : bool * Val list * Set<Stamp> = |
| 410 | + |
| 411 | + // Collect free variables from an expression |
| 412 | + let collectFreeVars expr = |
| 413 | + (freeInExpr CollectLocals expr).FreeLocals |> Zset.elements |
| 414 | + |
| 415 | + // Collect all method parameters (bound variables) from object expression methods |
| 416 | + // These should NOT be treated as struct instance captures |
| 417 | + let methodParams = |
| 418 | + [ |
| 419 | + for TObjExprMethod(_, _, _, paramGroups, _, _) in overrides do |
| 420 | + for paramGroup in paramGroups do |
| 421 | + for v in paramGroup do |
| 422 | + yield v |
| 423 | + for (_, methods) in extraImpls do |
| 424 | + for TObjExprMethod(_, _, _, paramGroups, _, _) in methods do |
| 425 | + for paramGroup in paramGroups do |
| 426 | + for v in paramGroup do |
| 427 | + yield v |
| 428 | + ] |
| 429 | + |> List.map (fun v -> v.Stamp) |
| 430 | + |> Set.ofList |
| 431 | + |
| 432 | + let allFreeVars = |
| 433 | + [ |
| 434 | + yield! collectFreeVars ctorCall |
| 435 | + for TObjExprMethod(_, _, _, _, body, _) in overrides do |
| 436 | + yield! collectFreeVars body |
| 437 | + for (_, methods) in extraImpls do |
| 438 | + for TObjExprMethod(_, _, _, _, body, _) in methods do |
| 439 | + yield! collectFreeVars body |
| 440 | + ] |
| 441 | + |> List.distinctBy (fun v -> v.Stamp) |
| 442 | + |
| 443 | + // Filter to struct instance captures: |
| 444 | + // - We're in a struct context (enclosingStructTyconRefOpt is Some) |
| 445 | + // - The value is NOT a method parameter of the object expression |
| 446 | + // - The value is NOT a module binding |
| 447 | + // - The value is NOT a member or module binding (excludes property getters, etc.) |
| 448 | + // - The value is NOT a constructor |
| 449 | + let structCaptures = |
| 450 | + match enclosingStructTyconRefOpt with |
| 451 | + | None -> [] |
| 452 | + | Some _ -> |
| 453 | + allFreeVars |
| 454 | + |> List.filter (fun v -> |
| 455 | + not v.IsModuleBinding |
| 456 | + && not v.IsMemberOrModuleBinding |
| 457 | + && not (Set.contains v.Stamp methodParams) |
| 458 | + && v.LogicalName <> ".ctor") |
| 459 | + |
| 460 | + let shouldTransform = not (List.isEmpty structCaptures) |
| 461 | + (shouldTransform, structCaptures, methodParams) |
| 462 | + |
| 463 | +/// Transform an object expression to avoid byref captures from struct instance state. |
| 464 | +/// Creates local bindings for captured values and remaps references in the object expression. |
| 465 | +let TransformObjExprForStructByrefCaptures |
| 466 | + (g: TcGlobals.TcGlobals) |
| 467 | + (mWholeExpr: Text.range) |
| 468 | + (structCaptures: Val list) |
| 469 | + (objtyR: TType) |
| 470 | + (baseValOpt: Val option) |
| 471 | + (ctorCall: Expr) |
| 472 | + (overrides: ObjExprMethod list) |
| 473 | + (extraImpls: (TType * ObjExprMethod list) list) |
| 474 | + (realObjTy: TType) |
| 475 | + : Expr = |
| 476 | + |
| 477 | + // Create local bindings for each captured value to avoid byref captures |
| 478 | + let localBindings = |
| 479 | + structCaptures |
| 480 | + |> List.map (fun v -> |
| 481 | + let local, _localExpr = |
| 482 | + mkCompGenLocal mWholeExpr (v.LogicalName + "$captured") v.Type |
| 483 | + |
| 484 | + let readExpr = exprForVal mWholeExpr v |
| 485 | + (v, local, readExpr)) |
| 486 | + |
| 487 | + // Build remap: original val -> local val |
| 488 | + let remap = |
| 489 | + localBindings |
| 490 | + |> List.fold |
| 491 | + (fun (r: Remap) (orig, local, _) -> |
| 492 | + { r with |
| 493 | + valRemap = r.valRemap.Add orig (mkLocalValRef local) |
| 494 | + }) |
| 495 | + Remap.Empty |
| 496 | + |
| 497 | + // Helper to remap an object expression method |
| 498 | + let remapMethod (TObjExprMethod(slotSig, attrs, mtps, paramGroups, body, range)) = |
| 499 | + TObjExprMethod(slotSig, attrs, mtps, paramGroups, remapExpr g CloneAll remap body, range) |
| 500 | + |
| 501 | + // Remap all parts of the object expression |
| 502 | + let ctorCall' = remapExpr g CloneAll remap ctorCall |
| 503 | + let overrides' = overrides |> List.map remapMethod |
| 504 | + |
| 505 | + let extraImpls' = |
| 506 | + extraImpls |> List.map (fun (ty, ms) -> (ty, ms |> List.map remapMethod)) |
| 507 | + |
| 508 | + // Build the object expression with remapped references |
| 509 | + let objExpr = |
| 510 | + mkObjExpr (objtyR, baseValOpt, ctorCall', overrides', extraImpls', mWholeExpr) |
| 511 | + |
| 512 | + let objExpr = mkCoerceIfNeeded g realObjTy objtyR objExpr |
| 513 | + |
| 514 | + // Wrap with let bindings: let x$captured = x in ... |
| 515 | + localBindings |
| 516 | + |> List.foldBack (fun (_, local, valueExpr) body -> mkLet DebugPointAtBinding.NoneAtInvisible mWholeExpr local valueExpr body) |
| 517 | + <| objExpr |
0 commit comments