Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.200.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Add a read-only repository review agent for performance-improvement PRs to assess measurement quality, behavior preservation, and implementation risk using source-attributed review precedents. ([PR #20619](https://github.com/dotnet/fsharp/pull/20619))
* Completion: respect EditorBrowsableAttribute ([PR #20729](https://github.com/dotnet/fsharp/pull/20729))
* Runtime-async sequences allow awaits inside `try/with` bodies, filters and handlers, and reject overlapping `MoveNextAsync` calls. ([PR #20655](https://github.com/dotnet/fsharp/pull/20655))
* Ranges can be mixed with values in list, array, sequence and computation expressions under `--langversion:preview`. ([RFC FS-1031](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1031-mixing-ranges-and-values-in-sequences.md), [PR #20759](https://github.com/dotnet/fsharp/pull/20759))
* F# Interactive gains a JSON-RPC server mode, `--fsi-server-jsonrpc:<pipe name>`, in which a host submits interactions over a named pipe and receives structured results — diagnostics with positions, escaping exceptions, the values each interaction bound, and the session's own process id — instead of recovering them by looking for a `SERVER-PROMPT>` marker in the output text. Program output continues to flow through the redirected console streams. The pipe admits only the user running the session; `--fsi-server-client-pid:<pid>` names the host process whose exit ends the session. `FsiEvaluationSession` exposes both options as `JsonRpcServerPipeName` and `JsonRpcClientProcessId`. The mode is part of the .NET fsi only. ([PR #20396](https://github.com/dotnet/fsharp/pull/20396))
* Include individual active-pattern cases from unopened modules in pattern completion. ([PR #20719](https://github.com/dotnet/fsharp/pull/20719))

Expand All @@ -22,6 +23,7 @@
* `FsiEvaluationSession.EvalInteraction` and `EvalInteractionNonThrowing` evaluate every `;;`-separated interaction in the text they are given, as their documentation has always said, instead of silently discarding everything after the first. ([PR #20396](https://github.com/dotnet/fsharp/pull/20396))
* Fix runtime-async callbacks chosen by a branch rerunning their setup and losing shared state on each call, `Await` inlined into a `with` handler not being rewritten, and `Await` in an object-expression base constructor call not being reported. ([Issue #20577](https://github.com/dotnet/fsharp/issues/20577), [PR #20647](https://github.com/dotnet/fsharp/pull/20647))
* Inline members can now call inline base-class members with optimizations off while retaining debugger step-into. Uneliminated `base` references report FS3925 instead of an internal error. ([Issue #20614](https://github.com/dotnet/fsharp/issues/20614), [PR #20639](https://github.com/dotnet/fsharp/pull/20639))
* Fix the counted-loop lowering of integral ranges: a range with a single `unativeint` element is no longer treated as empty, a mutable range bound is read before the later bounds are evaluated, and a `nativeint` range with the smallest step no longer yields extra elements. ([PR #20759](https://github.com/dotnet/fsharp/pull/20759))
* Prevent `InvalidProgramException` when runtime async awaits a `Task<T>` upcast to `Task`, including through `ConfigureAwait`. ([Issue #20686](https://github.com/dotnet/fsharp/issues/20686), [PR #20726](https://github.com/dotnet/fsharp/pull/20726))
* Struct/reference anonymous-record mismatches now name anonymous records rather than tuples. ([Issue #6110](https://github.com/dotnet/fsharp/issues/6110), [PR #20681](https://github.com/dotnet/fsharp/pull/20681))
* Preserve constraint inference order when explicit generic arguments share a caller type parameter, avoiding spurious FS0001 errors with multiply implemented generic interfaces. ([PR #20491](https://github.com/dotnet/fsharp/pull/20491))
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Features assigned to F# 11.2 are listed in [11.2](https://fsharp.github.io/fshar

### Added

* Ranges can be mixed with values in list, array, sequence and computation expressions, as in `[ -3; 1..10; 19 ]`; the range splices its elements like `yield!`. ([Language suggestion #1031](https://github.com/fsharp/fslang-suggestions/issues/1031), [RFC FS-1031](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1031-mixing-ranges-and-values-in-sequences.md), [PR #20759](https://github.com/dotnet/fsharp/pull/20759))

### Fixed

### Changed
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallT
let g = cenv.g

// The syntax '[ n .. m ]' and '[ n .. step .. m ]' is not really part of array or list syntax.
// It could be in the future, e.g. '[ 1; 2..30; 400 ]'
//
// The elaborated form of '[ n .. m ]' is 'List.ofSeq (seq (op_Range n m))' and this shouldn't change
match RewriteRangeExpr comp with
Expand Down Expand Up @@ -99,7 +98,8 @@ let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallT
| _ -> SynExpr.ArrayOrList(isArray, elems, m)

TcExprUndelayed cenv overallTy env tpenv replacementExpr
| _ ->
| SimpleSemicolonSequenceWithRanges cenv comp
| comp ->

let genCollElemTy = NewInferenceType g

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,8 @@ let rec TryTranslateComputationExpression
Some(translatedCtxt callExpr)

| SynExpr.YieldOrReturnFrom((true, _), synYieldExpr, mFull, { YieldOrReturnFromKeyword = m }) ->
let synYieldExpr = rewriteYieldFromRange cenv synYieldExpr

let yieldFromExpr =
mkSourceExpr synYieldExpr ceenv.sourceMethInfo ceenv.builderValName

Expand Down Expand Up @@ -2456,6 +2458,7 @@ let rec TryTranslateComputationExpression
if ceenv.isQuery && not isYield then
error (Error(FSComp.SR.tcReturnMayNotBeUsedInQueries (), m))

let synYieldOrReturnExpr = rejectRangeOperand cenv synYieldOrReturnExpr
requireBuilderMethod methName ceenv m m

let yieldOrReturnCall =
Expand All @@ -2469,6 +2472,8 @@ let rec TryTranslateComputationExpression

Some(translatedCtxt yieldOrReturnCall)

| BareRangeExpr _ -> TryTranslateComputationExpression ceenv firstTry q varSpace (mkRangeSplice comp) translatedCtxt

| _ -> None)

and ConsumeCustomOpClauses
Expand Down Expand Up @@ -2756,6 +2761,8 @@ and TranslateComputationExpressionBind
/// The inner option indicates if a custom operation is involved inside
and convertSimpleReturnToExpr (ceenv: ComputationExpressionContext<'a>) comp varSpace innerComp =
match innerComp with
| SynExpr.YieldOrReturn((false, _), RangeAsSingleValue ceenv.cenv, _, _) -> None

| SynExpr.YieldOrReturn((false, _), returnExpr, m, _) ->
let returnExpr =
SynExpr.DebugPoint(DebugPointAtLeafExpr.Yes(false, m), false, returnExpr)
Expand Down Expand Up @@ -2878,6 +2885,7 @@ and isSimpleExpr ceenv comp =
| SynExpr.YieldOrReturnFrom _ -> false
| SynExpr.YieldOrReturn _ -> false
| SynExpr.DoBang _ -> false
| BareRangeExpr _ -> false
| _ -> true

and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) firstTry q varSpace comp translatedCtxt =
Expand Down
64 changes: 63 additions & 1 deletion src/Compiler/Checking/Expressions/CheckExpressionsOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.SyntaxTreeOps
open FSharp.Compiler.SyntaxTrivia

let TryAllowFlexibleNullnessInControlFlow isFirst (g: TcGlobals.TcGlobals) ty =
match isFirst, g.checkNullness, GetTyparTyIfSupportsNull g ty with
Expand Down Expand Up @@ -181,6 +182,33 @@ let RewriteRangeExpr synExpr =
Some otherExpr
| _ -> None

[<return: Struct>]
let (|BareRangeExpr|_|) synExpr =
RewriteRangeExpr synExpr |> ValueOption.ofOption

let (|RangeAsSingleValue|_|) (cenv: TcFileState) synExpr =
match synExpr with
| BareRangeExpr _ -> cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
| _ -> false

let mkRangeSplice (synRange: SynExpr) =
let m = synRange.Range
SynExpr.YieldOrReturnFrom((true, false), synRange, m, { YieldOrReturnFromKeyword = m })

let rewriteYieldFromRange (cenv: TcFileState) synExpr =
match synExpr with
| BareRangeExpr rangeCall ->
checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions synExpr.Range
rangeCall
| _ -> synExpr

let rejectRangeOperand (cenv: TcFileState) operand =
match operand with
| RangeAsSingleValue cenv ->
errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), operand.Range))
arbExpr ("rangeAsSingleValue", operand.Range)
| _ -> operand

/// Check if a computation or sequence expression is syntactically free of 'yield' (though not yield!)
let YieldFree (_cenv: TcFileState) expr =
let rec YieldFree expr =
Expand Down Expand Up @@ -223,7 +251,8 @@ let inline IsSimpleSemicolonSequenceElement expr cenv acceptDeprecated =
| SynExpr.Do _
| SynExpr.MatchBang _
| SynExpr.While _
| SynExpr.WhileBang _ -> false
| SynExpr.WhileBang _
| BareRangeExpr _ -> false
| _ -> true

[<TailCall>]
Expand All @@ -247,6 +276,39 @@ let rec TryGetSimpleSemicolonSequenceOfComprehension expr acc cenv acceptDepreca
let (|SimpleSemicolonSequence|_|) cenv acceptDeprecated cexpr =
TryGetSimpleSemicolonSequenceOfComprehension cexpr [] cenv acceptDeprecated

[<return: Struct>]
let (|SimpleSemicolonSequenceWithRanges|_|) cenv cexpr =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖🕵️⏱️🔥

[<Params(1_000, 2_500, 5_000)>]
member val Count = 0 with get, set

[<Benchmark>]
member this.CheckMixedRange() =
    check $"[ 0; 1..{this.Count}; 9 ]"

Add mixed-range, explicit-yield!, and flat-value compiler benchmarks before merge. The bounded 5k computed forms exceeded 180s while flat values stayed ~0.8s; track the scaling instead of relying on correctness-only tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in a4bd607: MixedRangeBenchmarks in the compiler benchmarks. Each benchmark checks a list of Count elements for Count 250, 500 and 1000: the flat literal is the baseline, the other two are mixed ranges and the explicit yield! form, with a range at every tenth element. Local Release build, --inProcess:

Count flat mixed ranges explicit yield!
250 128 ms 201 ms 216 ms
500 121 ms 467 ms 546 ms
1000 92 ms 1,723 ms 1,944 ms

The flat literal stays constant. Mixed ranges and explicit yield! grow about 4x per doubling, and so do their allocations (105, 196, 543 MB against a flat 70 MB), and mixed is never slower than explicit: both are checked by the sequence-expression checker. So the PR adds no new cliff, but the cost of these forms is not linear. The SDK compiler already overflows its stack on the explicit form at 1,500 elements, while the flat literal is fine. I did not change the checker's complexity here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up after the rebase onto main (the benchmark commit is c89137e now, not a4bd607). On current main the stack guard from #20480 keeps the explicit and the mixed form from crashing, but they are slow instead: the Typecheck phase takes 5 s for 2000 yield statements and 40 to 60 s for 3000, against 1.5 s for the flat literal at any size, and much more when the machine is busy, so the more than 180 s you saw at 5k fits. I am filing an issue for the checker, and I suggested a softer Scaling sentence in fsharp/fslang-design#860 (the RFC says checking MUST stay linear).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue for the checker is #20887.

let isRange = RewriteRangeExpr >> Option.isSome

let yieldValue expr =
SynExpr.YieldOrReturn((true, false), expr, expr.Range, SynExprYieldOrReturnTrivia.Zero)

let sequential head tail =
SynExpr.Sequential(
DebugPointAtSequential.SuppressNeither,
true,
head,
tail,
unionRanges head.Range tail.Range,
SynExprSequentialTrivia.Zero
)

let rec elements acc expr =
match expr with
| SynExpr.Sequential(isTrueSeq = true; expr1 = head; expr2 = tail) -> elements (head :: acc) tail
| _ -> List.rev (expr :: acc)

match elements [] cexpr with
| elems when
List.exists isRange elems
&& List.forall (fun e -> isRange e || IsSimpleSemicolonSequenceElement e cenv false) elems
->
elems
|> List.map (fun e -> if isRange e then e else yieldValue e)
|> List.reduceBack sequential
|> ValueSome
| _ -> ValueNone

let elimFastIntegerForLoop (spFor, spTo, id, start: SynExpr, dir, finish: SynExpr, innerExpr, m: range) =
let mOp = (unionRanges start.Range finish.Range).MakeSynthetic()

Expand Down
17 changes: 17 additions & 0 deletions src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,13 @@ let TcSequenceExpression (cenv: TcFileState) env tpenv comp (overallTy: OverallT

| SynExpr.YieldOrReturnFrom(flags = (isYield, _); expr = synYieldExpr; trivia = { YieldOrReturnFromKeyword = m }) ->
let env = { env with eIsControlFlow = false }

let synYieldExpr =
if isYield then
rewriteYieldFromRange cenv synYieldExpr
else
synYieldExpr

let resultExpr, genExprTy, tpenv = TcExprOfUnknownType cenv env tpenv synYieldExpr

if not isYield then
Expand All @@ -397,6 +404,14 @@ let TcSequenceExpression (cenv: TcFileState) env tpenv comp (overallTy: OverallT

Some(resultExpr, tpenv)

| SynExpr.YieldOrReturn(flags = (isYield, _); expr = RangeAsSingleValue cenv as synRange; range = m) ->
if isYield then
rejectRangeOperand cenv synRange |> ignore
else
errorR (Error(FSComp.SR.tcSeqResultsUseYield (), m))

tryTcSequenceExprBody env genOuterTy tpenv (mkRangeSplice synRange)

| SynExpr.YieldOrReturn(flags = (isYield, _); expr = synYieldExpr; range = m) ->
let env = { env with eIsControlFlow = false }
let genResultTy = NewInferenceType g
Expand All @@ -418,6 +433,8 @@ let TcSequenceExpression (cenv: TcFileState) env tpenv comp (overallTy: OverallT

Some(resultExpr, tpenv)

| BareRangeExpr _ -> tryTcSequenceExprBody env genOuterTy tpenv (mkRangeSplice comp)

| _ -> None

and tcSequenceExprBody env (genOuterTy: TType) tpenv comp =
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,7 @@ featureUseBangBindingValueDiscard,"Allows use! _ = ... in computation expression
featureBetterAnonymousRecordParsing,"Support for better anonymous record parsing"
featureScopedNowarn,"Support for scoped enabling / disabling of warnings by #warn and #nowarn directives, also inside modules"
featureErrorOnInvalidDeclsInTypeDefinitions,"Error when invalid declarations are used in type definitions."
featureAllowMixedRangesAndValuesInSeqExpressions,"Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }}"
featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type annotations without requiring parentheses"
3874,lexWarnDirectiveMustBeFirst,"#nowarn/#warnon directives must appear as the first non-whitespace characters on a line"
3875,lexWarnDirectiveMustHaveArgs,"Warn directives must have warning number(s) as argument(s)"
Expand Down Expand Up @@ -1803,4 +1804,5 @@ featureTypeArgumentDependencyOrdering,"ordering explicit type arguments by their
3923,tcMethodRequiresNamedArguments,"The method '%s' requires named arguments. Use named-argument syntax, e.g. 'MethodName(argumentName = value)'."
3924,chkNativePtrStackallocInHandler,"'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded."
3925,optInlineMemberCannotUseBase,"An inline member cannot use 'base' or protected members unless those uses can be eliminated before the member itself is inlined."
3926,tcRangeIsNotASingleValue,"A range expression cannot be used as a single value here. Use 'yield!' to splice the range, or wrap it in a list or sequence expression to use it as one value."
featureReraiseInComputationExpressions,"calls to 'reraise' in a computation expression handler"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type LanguageFeature =
| BetterAnonymousRecordParsing
| ScopedNowarn
| ErrorOnInvalidDeclsInTypeDefinitions
| AllowMixedRangesAndValuesInSeqExpressions
| AllowTypedLetUseAndBang
| ReturnFromFinal
| MoreConcreteTiebreaker
Expand Down Expand Up @@ -164,6 +165,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
// F# preview
// Unfinished features that still need work before they can be assigned a release language version.
LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work
LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions, previewVersion
]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -285,6 +287,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.BetterAnonymousRecordParsing -> FSComp.SR.featureBetterAnonymousRecordParsing ()
| LanguageFeature.ScopedNowarn -> FSComp.SR.featureScopedNowarn ()
| LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions -> FSComp.SR.featureErrorOnInvalidDeclsInTypeDefinitions ()
| LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions -> FSComp.SR.featureAllowMixedRangesAndValuesInSeqExpressions ()
| LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()
| LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal ()
| LanguageFeature.MoreConcreteTiebreaker -> FSComp.SR.featureMoreConcreteTiebreaker ()
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type LanguageFeature =
| BetterAnonymousRecordParsing
| ScopedNowarn
| ErrorOnInvalidDeclsInTypeDefinitions
| AllowMixedRangesAndValuesInSeqExpressions
| AllowTypedLetUseAndBang
| ReturnFromFinal
| MoreConcreteTiebreaker
Expand Down
10 changes: 10 additions & 0 deletions src/Compiler/Optimize/LowerComputedCollections.fs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr =
Result.Ok (flag, exprR)
| Result.Error msg -> Result.Error msg

| IntegralRange g (rangeTy, (start, step, finish)) when
not isUninteresting
&& g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops
&& typeEquiv g (mkSeqTy g rangeTy) collectorSeqTy ->
let m = expr.Range
let exprR =
mkOptimizedRangeLoop g (m, m, m, DebugPointAtWhile.No) (rangeTy, expr) (start, step, finish) (fun _count mkLoop ->
Comment thread
xperiandri marked this conversation as resolved.
mkLoop (fun _idx elem -> mkCallCollectorAdd tcVal g infoReader m collExpr elem))
Result.Ok (false, exprR)

// yield! e ---> (for x in e -> x)

| arbitrarySeqExpr ->
Expand Down
Loading
Loading