Skip to content

Commit 5dce49e

Browse files
T-GroCopilot
andcommitted
Revert unrelated changes, add .WithSpecialName fix, add instrumentation tests
Production changes (vs main): - IlxGen.fs: Remove .specialname from staticInitialization@ method (fixes JMC debugger skipping module-level breakpoints) - IlxGen.fs: Always emit EmitStartOfHiddenCode at match join points (fixes #12052) - CheckComputationExpressions.fs: Use mFull for yield/return debug point ranges (fixes #19248), fix use binding debug points (fixes #19255) Reverted from branch: - LowerComputedCollections.fs SeqMap DebugPoints change (proven no-op) - resumableCodeDefinitions removal (unrelated to debug points) - All unrelated lexer/parser/service changes Added instrumentation tests: - H3: Verify for-line and body-line SPs are in same method (3 tests) - H4: Verify no JMC-suppressing attrs on body method (2 tests) - H1: Verify --realsig- same-method placement (1 test) - #12052: Verify hidden FeeFee points ARE emitted at match joins - #19248: Multi-line return expression range test - #19255: Sequence point count verification for use in task CE - Implicit yield regression check Co-authored-by: Copilot <[email protected]>
1 parent dbb5cb2 commit 5dce49e

38 files changed

Lines changed: 951 additions & 106 deletions

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -982,15 +982,16 @@ let AdjustValSynInfoInSignature g ty (SynValInfo(argsData, retData) as sigMD) =
982982
sigMD
983983

984984

985-
let TranslateTopArgSynInfo (cenv: cenv) isArg m tcAttributes (SynArgInfo(Attributes attrs, isOpt, nm)) =
985+
let TranslateTopArgSynInfo (cenv: cenv) isArg (m: range) tcAttributes (SynArgInfo(Attributes attrs, isOpt, nm)) =
986986
// Synthesize an artificial "OptionalArgument" attribute for the parameter
987-
let optAttrs =
987+
let optAttrs: SynAttribute list =
988988
if isOpt then
989-
[ ( { TypeName=SynLongIdent(pathToSynLid m ["Microsoft";"FSharp";"Core";"OptionalArgument"], [], [None;None;None;None])
990-
ArgExpr=mkSynUnit m
991-
Target=None
992-
AppliesToGetterAndSetter=false
993-
Range=m} : SynAttribute) ]
989+
let m = m.MakeSynthetic()
990+
[ { TypeName = SynLongIdent(pathToSynLid m ["Microsoft"; "FSharp"; "Core"; "OptionalArgument"], [], [None; None; None; None])
991+
ArgExpr = mkSynUnit m
992+
Target = None
993+
AppliesToGetterAndSetter = false
994+
Range = m } ]
994995
else
995996
[]
996997

@@ -1000,8 +1001,7 @@ let TranslateTopArgSynInfo (cenv: cenv) isArg m tcAttributes (SynArgInfo(Attribu
10001001
if not isArg && Option.isSome nm then
10011002
errorR(Error(FSComp.SR.tcReturnValuesCannotHaveNames(), m))
10021003

1003-
// Call the attribute checking function
1004-
let attribs = tcAttributes (optAttrs@attrs)
1004+
let attribs = tcAttributes (optAttrs @ attrs)
10051005

10061006
let key = nm |> Option.map (fun id -> id.idText, id.idRange)
10071007

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,11 @@ and IlxGenEnv =
12611261
intraAssemblyInfo: IlxGenIntraAssemblyInfo
12621262

12631263
realsig: bool
1264+
1265+
/// Expression definitions of variables returning resumable code from outer scopes.
1266+
/// Used by state machine lowering to resolve otherwise-free expand variables
1267+
/// when the state machine is inside a lambda whose outer let-binding provides the definition.
1268+
resumableCodeDefinitions: ValMap<Expr>
12641269
}
12651270

12661271
override _.ToString() = "<IlxGenEnv>"
@@ -3004,7 +3009,9 @@ and GenExprPreSteps (cenv: cenv) (cgbuf: CodeGenBuffer) eenv expr sequel =
30043009
true
30053010
| None ->
30063011

3007-
match LowerStateMachineExpr cenv.g expr with
3012+
let smResult = LowerStateMachineExpr cenv.g eenv.resumableCodeDefinitions expr
3013+
3014+
match smResult with
30083015
| LoweredStateMachineResult.Lowered res ->
30093016
let eenv = RemoveTemplateReplacement eenv
30103017
checkLanguageFeatureError cenv.g.langVersion LanguageFeature.ResumableStateMachines expr.Range
@@ -3499,6 +3506,16 @@ and GenLinearExpr cenv cgbuf eenv expr sequel preSteps (contf: FakeUnit -> FakeU
34993506
GenDebugPointForBind cenv cgbuf bind
35003507
GenBindingAfterDebugPoint cenv cgbuf eenv bind false (Some startMark)
35013508

3509+
// Track expand-var (resumable code) definitions so state machine lowering
3510+
// inside nested lambdas can resolve otherwise-free expand variables.
3511+
let eenv =
3512+
if isReturnsResumableCodeTy cenv.g bind.Var.TauType then
3513+
{ eenv with
3514+
resumableCodeDefinitions = eenv.resumableCodeDefinitions.Add bind.Var bind.Expr
3515+
}
3516+
else
3517+
eenv
3518+
35023519
// Generate the body
35033520
GenLinearExpr cenv cgbuf eenv body (EndLocalScope(sequel, endMark)) true contf
35043521

@@ -10261,7 +10278,7 @@ and CodeGenInitMethod cenv (cgbuf: CodeGenBuffer) eenv tref (codeGenInitFunc: Co
1026110278
let ilReturn = mkILReturn ILType.Void
1026210279

1026310280
let method =
10264-
(mkILNonGenericStaticMethod (eenv.staticInitializationName, access, [], ilReturn, ilBody)).WithSpecialName
10281+
mkILNonGenericStaticMethod (eenv.staticInitializationName, access, [], ilReturn, ilBody)
1026510282

1026610283
cgbuf.mgbuf.AddMethodDef(tref, method)
1026710284
CountMethodDef()
@@ -12047,6 +12064,7 @@ let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
1204712064
intraAssemblyInfo = IlxGenIntraAssemblyInfo.Create()
1204812065
realsig = g.realsig
1204912066
initClassFieldSpec = None
12067+
resumableCodeDefinitions = ValMap<_>.Empty
1205012068
}
1205112069

1205212070
type IlxGenResults =

src/Compiler/Driver/CompilerDiagnostics.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,7 +1284,8 @@ type Exception with
12841284
| Parser.TOKEN_HASH_LINE
12851285
| Parser.TOKEN_HASH_IF
12861286
| Parser.TOKEN_HASH_ELSE
1287-
| Parser.TOKEN_HASH_ENDIF -> SR.GetString("Parser.TOKEN.HASH.ENDIF")
1287+
| Parser.TOKEN_HASH_ENDIF
1288+
| Parser.TOKEN_HASH_ELIF -> SR.GetString("Parser.TOKEN.HASH.ENDIF")
12881289
| Parser.TOKEN_INACTIVECODE -> SR.GetString("Parser.TOKEN.INACTIVECODE")
12891290
| Parser.TOKEN_LEX_FAILURE -> SR.GetString("Parser.TOKEN.LEX.FAILURE")
12901291
| Parser.TOKEN_WHITESPACE -> SR.GetString("Parser.TOKEN.WHITESPACE")

src/Compiler/FSComp.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,8 @@ lexHashEndingNoMatchingIf,"#endif has no matching #if"
10571057
1169,lexHashIfMustHaveIdent,"#if directive should be immediately followed by an identifier"
10581058
1170,lexWrongNestedHashEndif,"Syntax error. Wrong nested #endif, unexpected tokens before it."
10591059
lexHashBangMustBeFirstInFile,"#! may only appear as the first line at the start of a file."
1060+
lexHashElifNoMatchingIf,"#elif has no matching #if"
1061+
lexHashElifAfterElse,"#elif is not allowed after #else"
10601062
1171,pplexExpectedSingleLineComment,"Expected single line comment or end of line"
10611063
1172,memberOperatorDefinitionWithNoArguments,"Infix operator member '%s' has no arguments. Expected a tuple of 2 arguments, e.g. static member (+) (x,y) = ..."
10621064
1173,memberOperatorDefinitionWithNonPairArgument,"Infix operator member '%s' has %d initial argument(s). Expected a tuple of 2 arguments, e.g. static member (+) (x,y) = ..."
@@ -1805,5 +1807,8 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an
18051807
featureReturnFromFinal,"Support for ReturnFromFinal/YieldFromFinal in computation expressions to enable tailcall optimization when available on the builder."
18061808
featureMethodOverloadsCache,"Support for caching method overload resolution results for improved compilation performance."
18071809
featureImplicitDIMCoverage,"Implicit dispatch slot coverage for default interface member implementations"
1810+
featurePreprocessorElif,"#elif preprocessor directive"
18081811
3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s"
1809-
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
1812+
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
1813+
3882,lexHashElifMustBeFirst,"#elif directive must appear as the first non-whitespace character on a line"
1814+
3883,lexHashElifMustHaveIdent,"#elif directive should be immediately followed by an identifier"

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ type LanguageFeature =
106106
| ReturnFromFinal
107107
| MethodOverloadsCache
108108
| ImplicitDIMCoverage
109+
| PreprocessorElif
109110

110111
/// LanguageVersion management
111112
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
@@ -247,6 +248,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
247248

248249
// F# 11.0
249250
// Put stabilized features here for F# 11.0 previews via .NET SDK preview channels
251+
LanguageFeature.PreprocessorElif, languageVersion110
250252

251253
// Difference between languageVersion110 and preview - 11.0 gets turned on automatically by picking a preview .NET 11 SDK
252254
// previewVersion is only when "preview" is specified explicitly in project files and users also need a preview SDK
@@ -446,6 +448,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
446448
| LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal ()
447449
| LanguageFeature.MethodOverloadsCache -> FSComp.SR.featureMethodOverloadsCache ()
448450
| LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage ()
451+
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
449452

450453
/// Get a version string associated with the given feature.
451454
static member GetFeatureVersionString feature =

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ type LanguageFeature =
9797
| ReturnFromFinal
9898
| MethodOverloadsCache
9999
| ImplicitDIMCoverage
100+
| PreprocessorElif
100101

101102
/// LanguageVersion management
102103
type LanguageVersion =

src/Compiler/Interactive/fsi.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1295,6 +1295,7 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
12951295

12961296
fsiConsoleOutput.uprintfn """ #clear;; // %s""" (FSIstrings.SR.fsiIntroTextHashclearInfo ())
12971297
fsiConsoleOutput.uprintfn """ #quit;; // %s""" (FSIstrings.SR.fsiIntroTextHashquitInfo ())
1298+
fsiConsoleOutput.uprintfn """ #exit;; // %s""" (FSIstrings.SR.fsiIntroTextHashquitInfo ())
12981299
fsiConsoleOutput.uprintfn ""
12991300
fsiConsoleOutput.uprintfnn "%s" (FSIstrings.SR.fsiIntroTextHeader2commandLine ())
13001301
fsiConsoleOutput.uprintfn "%s" (FSIstrings.SR.fsiIntroTextHeader3 helpLine)
@@ -3885,7 +3886,7 @@ type FsiInteractionProcessor
38853886
fsiOptions.ClearScreen()
38863887
istate, Completed None
38873888

3888-
| ParsedHashDirective(("q" | "quit"), [], _) -> fsiInterruptController.Exit()
3889+
| ParsedHashDirective(("q" | "quit" | "exit"), [], _) -> fsiInterruptController.Exit()
38893890

38903891
| ParsedHashDirective("help", hashArguments, m) ->
38913892
let args = (parsedHashDirectiveArguments hashArguments tcConfigB.langVersion)

src/Compiler/Optimize/LowerComputedCollections.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,12 +616,12 @@ let gatherPrelude ((|App|_|) : _ -> _ voption) expr =
616616
[<return: Struct>]
617617
let (|SeqMap|_|) g =
618618
gatherPrelude (function
619-
| ValApp g g.seq_map_vref ([ty1; ty2], [Expr.Lambda (valParams = [loopVal]; bodyExpr = DebugPoints (body, debug); range = mIn) as mapping; input], mFor) ->
619+
| ValApp g g.seq_map_vref ([ty1; ty2], [Expr.Lambda (valParams = [loopVal]; bodyExpr = body; range = mIn) as mapping; input], mFor) ->
620620
let spIn = match mIn.NotedSourceConstruct with NotedSourceConstruct.InOrTo -> DebugPointAtInOrTo.Yes mIn | _ -> DebugPointAtInOrTo.No
621621
let spFor = DebugPointAtBinding.Yes mFor
622622
let spInWhile = match spIn with DebugPointAtInOrTo.Yes m -> DebugPointAtWhile.Yes m | DebugPointAtInOrTo.No -> DebugPointAtWhile.No
623623
let ranges = body.Range, spFor, spIn, mFor, mIn, spInWhile
624-
ValueSome (ty1, ty2, input, mapping, loopVal, debug body, ranges)
624+
ValueSome (ty1, ty2, input, mapping, loopVal, body, ranges)
625625

626626
| _ -> ValueNone)
627627

src/Compiler/Optimize/LowerStateMachines.fs

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ type LoweredStateMachineResult =
167167
| NotAStateMachine
168168

169169
/// Used to scope the action of lowering a state machine expression
170-
type LowerStateMachine(g: TcGlobals) =
170+
type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap<Expr>) =
171171

172172
let mutable pcCount = 0
173173
let genPC() =
@@ -189,6 +189,11 @@ type LowerStateMachine(g: TcGlobals) =
189189
if sm_verbose then printfn "eliminating 'if __useResumableCode...'"
190190
BindResumableCodeDefinitions env thenExpr
191191

192+
// Look through debug points to find resumable code bindings inside
193+
| Expr.DebugPoint (_, innerExpr) ->
194+
let envR, _ = BindResumableCodeDefinitions env innerExpr
195+
(envR, expr)
196+
192197
| _ ->
193198
(env, expr)
194199

@@ -235,7 +240,16 @@ type LowerStateMachine(g: TcGlobals) =
235240
TryReduceApp env expandedExpr laterArgs
236241

237242
| Expr.Let (bind, bodyExpr, m, _) ->
238-
match TryReduceApp env bodyExpr args with
243+
// If the binding returns resumable code, add it to the env so that
244+
// references to it in the body can be resolved during reduction.
245+
// This handles patterns like 'let cont = (fun () -> ...; Zero()) in cont()'
246+
// generated by the optimizer for CE if-then branches.
247+
let envR =
248+
if isExpandVar g bind.Var then
249+
{ env with ResumableCodeDefns = env.ResumableCodeDefns.Add bind.Var bind.Expr }
250+
else
251+
env
252+
match TryReduceApp envR bodyExpr args with
239253
| Some bodyExpr2 -> Some (mkLetBind m bind bodyExpr2)
240254
| None -> None
241255

@@ -308,6 +322,14 @@ type LowerStateMachine(g: TcGlobals) =
308322
| Some innerExpr2 -> Some (Expr.DebugPoint (dp, innerExpr2))
309323
| None -> None
310324

325+
// Resolve variables known to the env, e.g. locally-bound resumable code continuations
326+
| Expr.Val (vref, _, _) when env.ResumableCodeDefns.ContainsVal vref.Deref ->
327+
TryReduceApp env env.ResumableCodeDefns[vref.Deref] args
328+
329+
// Push through function applications by combining the arg lists
330+
| Expr.App (f, _fty, _tyargs, fArgs, _m) ->
331+
TryReduceApp env f (fArgs @ args)
332+
311333
| _ ->
312334
None
313335

@@ -349,7 +371,19 @@ type LowerStateMachine(g: TcGlobals) =
349371

350372
// Repeated top-down rewrite
351373
let makeRewriteEnv (env: env) =
352-
{ PreIntercept = Some (fun cont e -> match TryReduceExpr env e [] id with Some e2 -> Some (cont e2) | None -> None)
374+
{ PreIntercept = Some (fun cont e ->
375+
match e with
376+
// Don't recurse into nested state machine expressions - they will be
377+
// processed by their own LowerStateMachineExpr during codegen.
378+
// This prevents modification of the nested machine's internal
379+
// 'if __useResumableCode' patterns which select its dynamic fallback.
380+
| _ when Option.isSome (IsStateMachineExpr g e) -> Some e
381+
// Eliminate 'if __useResumableCode' - nested state machines are already
382+
// guarded above, so any remaining occurrences at this level are from
383+
// beta-reduced inline helpers and should take the static branch.
384+
| IfUseResumableStateMachinesExpr g (thenExpr, _) -> Some (cont thenExpr)
385+
| _ ->
386+
match TryReduceExpr env e [] id with Some e2 -> Some (cont e2) | None -> None)
353387
PostTransform = (fun _ -> None)
354388
PreInterceptBinding = None
355389
RewriteQuotations=true
@@ -375,7 +409,9 @@ type LowerStateMachine(g: TcGlobals) =
375409
[<return: Struct>]
376410
let (|ExpandedStateMachineInContext|_|) inputExpr =
377411
// All expanded resumable code state machines e.g. 'task { .. }' begin with a bind of @builder or 'defn'
378-
let env, expr = BindResumableCodeDefinitions env.Empty inputExpr
412+
// Seed the env with any expand-var definitions from outer scopes (e.g. across lambda boundaries)
413+
let initialEnv = { env.Empty with ResumableCodeDefns = outerResumableCodeDefns }
414+
let env, expr = BindResumableCodeDefinitions initialEnv inputExpr
379415
match expr with
380416
| StructStateMachineExpr g
381417
(dataTy,
@@ -858,8 +894,8 @@ type LowerStateMachine(g: TcGlobals) =
858894
let env, codeExprR = RepeatBindAndApplyOuterDefinitions env codeExpr
859895
let frees = (freeInExpr CollectLocals overallExpr).FreeLocals
860896

861-
if frees |> Zset.exists (isExpandVar g) then
862-
let nonfree = frees |> Zset.elements |> List.filter (isExpandVar g) |> List.map (fun v -> v.DisplayName) |> String.concat ","
897+
if frees |> Zset.exists (fun v -> isExpandVar g v && not (env.ResumableCodeDefns.ContainsVal v)) then
898+
let nonfree = frees |> Zset.elements |> List.filter (fun v -> isExpandVar g v && not (env.ResumableCodeDefns.ContainsVal v)) |> List.map (fun v -> v.DisplayName) |> String.concat ","
863899
let msg = FSComp.SR.reprResumableCodeValueHasNoDefinition(nonfree)
864900
fallback msg
865901
else
@@ -913,12 +949,12 @@ type LowerStateMachine(g: TcGlobals) =
913949
let msg = FSComp.SR.reprStateMachineInvalidForm()
914950
fallback msg
915951

916-
let LowerStateMachineExpr g (overallExpr: Expr) : LoweredStateMachineResult =
952+
let LowerStateMachineExpr g (outerResumableCodeDefns: ValMap<Expr>) (overallExpr: Expr) : LoweredStateMachineResult =
917953
// Detect a state machine and convert it
918954
let stateMachine = IsStateMachineExpr g overallExpr
919955

920956
match stateMachine with
921957
| None -> LoweredStateMachineResult.NotAStateMachine
922958
| Some altExprOpt ->
923959

924-
LowerStateMachine(g).Apply(overallExpr, altExprOpt)
960+
LowerStateMachine(g, outerResumableCodeDefns).Apply(overallExpr, altExprOpt)

src/Compiler/Optimize/LowerStateMachines.fsi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module internal FSharp.Compiler.LowerStateMachines
44

55
open FSharp.Compiler.TypedTree
6+
open FSharp.Compiler.TypedTreeOps
67
open FSharp.Compiler.TcGlobals
78

89
type LoweredStateMachine =
@@ -30,4 +31,5 @@ type LoweredStateMachineResult =
3031

3132
/// Analyze a TAST expression to detect the elaborated form of a state machine expression, a special kind
3233
/// of object expression that uses special code generation constructs.
33-
val LowerStateMachineExpr: g: TcGlobals -> overallExpr: Expr -> LoweredStateMachineResult
34+
val LowerStateMachineExpr:
35+
g: TcGlobals -> outerResumableCodeDefns: ValMap<Expr> -> overallExpr: Expr -> LoweredStateMachineResult

0 commit comments

Comments
 (0)