From 8d4c04e817c4dd5d7286ee2d647523bf1855b31f Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sat, 10 Oct 2026 02:23:10 +0200 Subject: [PATCH 1/5] Allow mixing ranges and values in collection expressions Implements RFC FS-1031 under --langversion:preview: an unparenthesized range in element position of a list, array, sequence or computation expression splices its elements, as yield! does. Builds on Edgar Gonzalez's prototype in dotnet/fsharp#18670, reworked to follow the amended RFC. Co-authored-by: Edgar Gonzalez Co-Authored-By: Claude Opus 5.5 --- .../CheckArrayOrListComputedExpressions.fs | 5 + .../CheckComputationExpressions.fs | 13 + .../Expressions/CheckExpressionsOps.fs | 59 +++- .../Expressions/CheckSequenceExpressions.fs | 21 ++ src/Compiler/FSComp.txt | 2 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + .../Optimize/LowerComputedCollections.fs | 12 + src/Compiler/xlf/FSComp.txt.cs.xlf | 10 + src/Compiler/xlf/FSComp.txt.de.xlf | 10 + src/Compiler/xlf/FSComp.txt.es.xlf | 10 + src/Compiler/xlf/FSComp.txt.fr.xlf | 10 + src/Compiler/xlf/FSComp.txt.it.xlf | 10 + src/Compiler/xlf/FSComp.txt.ja.xlf | 10 + src/Compiler/xlf/FSComp.txt.ko.xlf | 10 + src/Compiler/xlf/FSComp.txt.pl.xlf | 10 + src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 10 + src/Compiler/xlf/FSComp.txt.ru.xlf | 10 + src/Compiler/xlf/FSComp.txt.tr.xlf | 10 + src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 10 + src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 10 + .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../MixedSequenceExpressionTests.fs | 312 ++++++++++++++++++ .../SequenceExpressions02a.fs | 8 + .../SequenceExpressions02b.fs | 8 + .../SequenceExpressions02c.fs | 8 + .../SequenceExpressions02d.fs | 8 + .../SequenceExpressions03.fs | 27 ++ .../SequenceExpressions04.fs | 25 ++ .../SequenceExpressions05.fs | 21 ++ .../SequenceExpressions06.fs | 18 + .../SequenceExpressions07.fs | 25 ++ .../SequenceExpressions08.fs | 17 + .../SequenceExpressions10.fs | 63 ++++ .../SequenceExpressions11.fs | 63 ++++ .../SequenceExpressions12.fs | 17 + .../SequenceExpressions13.fs | 17 + .../SequenceExpressions14.fs | 16 + .../SequenceExpressions15.fs | 22 ++ .../SequenceExpressions16.fs | 39 +++ .../SequenceExpressions17.fs | 55 +++ .../SequenceExpressions18.fs | 8 + .../SequenceExpressions19.fs | 9 + .../SequenceExpressions20.fs | 8 + 44 files changed, 1040 insertions(+), 1 deletion(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs diff --git a/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs b/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs index bfabf93f5cf..2a34fdda72e 100644 --- a/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs @@ -101,6 +101,11 @@ let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallT TcExprUndelayed cenv overallTy env tpenv replacementExpr | _ -> + let comp = + match comp with + | SimpleSemicolonSequenceWithRanges cenv valuesAndRanges -> valuesAndRanges + | _ -> comp + let genCollElemTy = NewInferenceType g let genCollTy = (if isArray then mkArrayType else mkListTy) cenv.g genCollElemTy diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index a9dd1205512..f5196e55f57 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -2391,6 +2391,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 @@ -2446,6 +2448,14 @@ let rec TryTranslateComputationExpression Some(translatedCtxt returnFromCall) | SynExpr.YieldOrReturn((isYield, _), synYieldOrReturnExpr, mFull, { YieldOrReturnKeyword = m }) -> + // A range is not yielded or returned as one value (FS-1031); recover with the range as a sequence. + let synYieldOrReturnExpr = + match synYieldOrReturnExpr with + | BareRangeExpr rangeExpr when cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions -> + errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synYieldOrReturnExpr.Range)) + rangeExpr + | _ -> synYieldOrReturnExpr + let methName = (if isYield then "Yield" else "Return") if ceenv.isQuery && not isYield then @@ -2464,6 +2474,8 @@ let rec TryTranslateComputationExpression Some(translatedCtxt yieldOrReturnCall) + | BareRangeExpr _ -> TryTranslateComputationExpression ceenv firstTry q varSpace (mkRangeSplice comp) translatedCtxt + | _ -> None) and ConsumeCustomOpClauses @@ -2868,6 +2880,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 = diff --git a/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs b/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs index dbb0a7d6703..3c4307a6255 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs @@ -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 @@ -181,6 +182,30 @@ let RewriteRangeExpr synExpr = Some otherExpr | _ -> None +/// A closed range 'e1..e2' or 'e1..e2..e3' that is not parenthesized, as a call to '(..)' or '(.. ..)'. +[] +let (|BareRangeExpr|_|) synExpr = + match synExpr with + | SynExpr.IndexRange _ -> + match RewriteRangeExpr synExpr with + | Some rangeExpr -> ValueSome rangeExpr + | None -> ValueNone + | _ -> ValueNone + +/// A range in element position of a list, array, sequence or computation expression +/// splices its elements, as 'yield! e1..e2' does (FS-1031). +let mkRangeSplice (synRange: SynExpr) = + let m = synRange.Range + SynExpr.YieldOrReturnFrom((true, false), synRange, m, { YieldOrReturnFromKeyword = m }) + +/// The source of 'yield! e1..e2' is '(..) e1 e2' (FS-1031). +let rewriteYieldFromRange (cenv: TcFileState) synExpr = + match synExpr with + | BareRangeExpr rangeExpr -> + checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions synExpr.Range + rangeExpr + | _ -> synExpr + /// Check if a computation or sequence expression is syntactically free of 'yield' (though not yield!) let YieldFree (_cenv: TcFileState) expr = let rec YieldFree expr = @@ -223,7 +248,8 @@ let inline IsSimpleSemicolonSequenceElement expr cenv acceptDeprecated = | SynExpr.Do _ | SynExpr.MatchBang _ | SynExpr.While _ - | SynExpr.WhileBang _ -> false + | SynExpr.WhileBang _ + | BareRangeExpr _ -> false | _ -> true [] @@ -247,6 +273,37 @@ let rec TryGetSimpleSemicolonSequenceOfComprehension expr acc cenv acceptDepreca let (|SimpleSemicolonSequence|_|) cenv acceptDeprecated cexpr = TryGetSimpleSemicolonSequenceOfComprehension cexpr [] cenv acceptDeprecated +/// A flat list or array of values with at least one range, such as '[ 0; 1..3; 10 ]' (FS-1031), +/// as '[ yield 0; 1..3; yield 10 ]': every value stays an element, as in a list literal. +[] +let (|SimpleSemicolonSequenceWithRanges|_|) cenv cexpr = + let yieldElement (e: SynExpr) = + SynExpr.YieldOrReturn((true, false), e, e.Range, { YieldOrReturnKeyword = e.Range }) + + let rec collectReversed expr acc hasRange = + match expr with + | SynExpr.Sequential(isTrueSeq = true; expr1 = BareRangeExpr _ as e1; expr2 = e2) -> collectReversed e2 (e1 :: acc) true + | SynExpr.Sequential(isTrueSeq = true; expr1 = e1; expr2 = e2) when IsSimpleSemicolonSequenceElement e1 cenv false -> + collectReversed e2 (yieldElement e1 :: acc) hasRange + | BareRangeExpr _ -> ValueSome(expr :: acc) + | _ when hasRange && IsSimpleSemicolonSequenceElement expr cenv false -> ValueSome(yieldElement expr :: acc) + | _ -> ValueNone + + match collectReversed cexpr [] false with + | ValueSome(last :: before) -> + let mkSequential (tail: SynExpr) (e: SynExpr) = + SynExpr.Sequential( + DebugPointAtSequential.SuppressNeither, + true, + e, + tail, + unionRanges e.Range tail.Range, + SynExprSequentialTrivia.Zero + ) + + ValueSome(List.fold mkSequential last before) + | _ -> ValueNone + let elimFastIntegerForLoop (spFor, spTo, id, start: SynExpr, dir, finish: SynExpr, innerExpr, m: range) = let mOp = (unionRanges start.Range finish.Range).MakeSynthetic() diff --git a/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs b/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs index c491fd42988..2a2de2780e8 100644 --- a/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs @@ -7,6 +7,7 @@ open FSharp.Compiler.CheckBasics open FSharp.Compiler.CheckExpressions open FSharp.Compiler.CheckExpressionsOps open FSharp.Compiler.ConstraintSolver +open FSharp.Compiler.Features open FSharp.Compiler.NameResolution open FSharp.Compiler.PatternMatchCompilation open FSharp.Compiler.Syntax @@ -379,6 +380,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 @@ -397,6 +405,17 @@ let TcSequenceExpression (cenv: TcFileState) env tpenv comp (overallTy: OverallT Some(resultExpr, tpenv) + // A range is not yielded or returned as one value (FS-1031); recover as 'yield! e1..e2'. + | SynExpr.YieldOrReturn(flags = (isYield, _); expr = BareRangeExpr _ as synRange; range = m) when + g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions + -> + if isYield then + errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synRange.Range)) + 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 @@ -418,6 +437,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 = diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 1fa6ba59402..1e6b110c98e 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1731,6 +1731,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)" @@ -1804,4 +1805,5 @@ featureTypeArgumentDependencyOrdering,"ordering explicit type arguments by their 3922,ilRuntimeAsyncSequenceNotStaticallyKnown,"The runtime-async sequence recipe could not be statically lowered. Supply a statically known delayed sequence recipe through an inline builder." 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,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" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 0a34624112e..57cf90ee37c 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -45,6 +45,7 @@ type LanguageFeature = | BetterAnonymousRecordParsing | ScopedNowarn | ErrorOnInvalidDeclsInTypeDefinitions + | AllowMixedRangesAndValuesInSeqExpressions | AllowTypedLetUseAndBang | ReturnFromFinal | MoreConcreteTiebreaker @@ -172,6 +173,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") @@ -295,6 +297,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 () diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 4afa992cde5..4232b9ad9ed 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -36,6 +36,7 @@ type LanguageFeature = | BetterAnonymousRecordParsing | ScopedNowarn | ErrorOnInvalidDeclsInTypeDefinitions + | AllowMixedRangesAndValuesInSeqExpressions | AllowTypedLetUseAndBang | ReturnFromFinal | MoreConcreteTiebreaker diff --git a/src/Compiler/Optimize/LowerComputedCollections.fs b/src/Compiler/Optimize/LowerComputedCollections.fs index 26a75fbde7b..a8080797fc1 100644 --- a/src/Compiler/Optimize/LowerComputedCollections.fs +++ b/src/Compiler/Optimize/LowerComputedCollections.fs @@ -199,6 +199,18 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr = Result.Ok (flag, exprR) | Result.Error msg -> Result.Error msg + // yield! start..step..finish over an integral type: a counted loop adding each element, + // instead of AddMany over the range's enumerator. + | 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 -> + mkLoop (fun _idx elem -> mkCallCollectorAdd tcVal g infoReader m collExpr elem)) + Result.Ok (false, exprR) + // yield! e ---> (for x in e -> x) | arbitrarySeqExpr -> diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 8bd83440050..9a7859968eb 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index ab6af560121..1dde1001524 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 3f7773686fd..d63c6815603 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 7a3822ab851..5593c82cf66 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 21aa6a838be..a3cc862885b 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index c49b5490212..e1378c6f9b0 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 11cebee14be..f5ab6276f97 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 9aa0452706b..b38b397ac78 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 388d56c9a0c..456e6bb3d66 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index ef935e3d593..b238f104b82 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 1a917da78aa..6239a6279f1 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index f159ad4ee1b..f841dbea3ad 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index abb0041c10d..0759b5ae9dd 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -307,6 +307,11 @@ Allow let! and use! type annotations without requiring parentheses + + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + Allow mixed ranges and values in sequence expressions, e.g. seq {{ 1..10; 20 }} + + Allow object expressions without overrides Allow object expressions without overrides @@ -1602,6 +1607,11 @@ You can remove this `nonNull` assertion. + + 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. + 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. + + Explicit field '{0}' shadows a field with the same name from an earlier spread. Explicit field '{0}' shadows a field with the same name from an earlier spread. diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 2b473df5e1b..309b89256bf 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -382,6 +382,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs new file mode 100644 index 00000000000..2ea2ef82e73 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs @@ -0,0 +1,312 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open Xunit +open FSharp.Test +open FSharp.Test.Compiler + +// FS-1031: ranges mixed with values in list, array, sequence and computation expressions. +module MixedSequenceExpressionTests = + + let private runWithPreview compilation = + compilation + |> asExe + |> withLangVersionPreview + |> withOptions [ "--nowarn:988" ] + |> compileAndRun + |> shouldSucceed + + let private typecheckWithPreview compilation = + compilation |> asLibrary |> withLangVersionPreview |> typecheck + + let private featureNotAvailable = + "Feature 'Allow mixed ranges and values in sequence expressions, e.g. seq { 1..10; 20 }' is not available in F# 11.0. Please use language version 'PREVIEW' or greater." + + let private rangeIsNotASingleValue = + "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." + + [] + let ``Ranges mixed with values give the spliced elements`` compilation = runWithPreview compilation + + [] + let ``A range splices in every element position`` () = + FSharp + """ +module Positions + +let check name expected actual = + if actual <> expected then failwith $"{name}: {actual}" + +type ListBuilder() = + member _.Yield(x) = [ x ] + member _.YieldFrom(xs: seq<_>) = List.ofSeq xs + member _.Zero() = [] + member _.Combine(a, b) = a @ b + member _.Delay(f: unit -> _ list) = f () + member _.Bind(x: _ option, f) = match x with Some v -> f v | None -> [] + +let list = ListBuilder() +let disposable = { new System.IDisposable with member _.Dispose() = () } + +check "if" [ 2; 3 ] [ if false then 1 else 2..3 ] +check "match" [ 1; 2 ] [ match Some 2 with Some n -> 1..n | None -> 0 ] +check "for" [ 1; 2; 2; 3 ] [ for x in [ 1; 2 ] do x..x + 1 ] +check "while" [ 0; 1; 1; 2 ] [ + let i = ref 0 + while i.Value < 2 do + i.Value..i.Value + 1 + i.Value <- i.Value + 1 ] +check "try/with" [ 1; 2 ] [ try 1..2 with _ -> () ] +check "try/finally" [ 1; 2 ] [ try 1..2 finally () ] +check "let" [ 1; 2; 3; 10 ] [ let lo = 1 in lo..3; 10 ] +check "use" [ 1; 2 ] [ use _d = disposable in 1..2 ] +check "seq" [ 0; 1; 2 ] (List.ofSeq (seq { 0; 1..2 })) +check "array" [| 0; 1; 2 |] [| 0; 1..2 |] +check "computation expression" [ 0; 1; 2; 9 ] (list { 0; 1..2; 9 }) +check "let!" [ 1; 2; 3 ] (list { let! n = Some 3 in 1..n }) +""" + |> runWithPreview + + [] + let ``A range neither counts as an explicit yield nor disables implicit yields`` () = + FSharp + """ +module ImplicitYields + +let xs = [ 0 ] +if [ yield! xs; 2..3; 4 ] <> [ 0; 2; 3; 4 ] then failwith "yield!" +if [ yield 1; 2..3 ] <> [ 1; 2; 3 ] then failwith "yield" +""" + |> runWithPreview + + [] + let ``In a flat list or array every value stays an element`` () = + FSharp + """ +module FlatLiterals + +let withRange c = [ 1; 2..3; c ] +let withoutRange c = [ 1; c ] +if withRange 5 <> [ 1; 2; 3; 5 ] || withoutRange 5 <> [ 1; 5 ] then failwith "list" +let array c = [| 0..1; c |] +if array 7 <> [| 0; 1; 7 |] then failwith "array" +""" + |> runWithPreview + + [] + let ``A plain value next to a range keeps the explicit yield rule`` () = + FSharp + """ +module M + +let a = [ yield 1; 2..3; 4 ] +""" + |> typecheckWithPreview + |> shouldFail + |> withDiagnostics + [ (Warning 3221, + Line 4, + Col 26, + Line 4, + Col 27, + "This expression returns a value of type 'int' but is implicitly discarded. Consider using 'let' to bind the result to a name, e.g. 'let result = expression'. If you intended to use the expression as a value in the sequence then use an explicit 'yield'.") ] + + [] + let ``A range is not yielded or returned as one value`` () = + FSharp + """ +module M + +type B() = + member _.Yield(x) = [ x ] + member _.Return(x) = [ x ] + member _.Delay(f) = f () + +let b = B() +let a = [ yield 1..3 ] +let c = [ for x in [ 1 ] -> 1..3 ] +let d = b { yield 1..3 } +let e = b { return 1..3 } +let f = [ return 1..3 ] +let g = [ 1; (2..3) ] +""" + |> typecheckWithPreview + |> shouldFail + |> withDiagnostics + [ (Error 3925, Line 10, Col 17, Line 10, Col 21, rangeIsNotASingleValue) + (Error 3925, Line 11, Col 29, Line 11, Col 33, rangeIsNotASingleValue) + (Error 3925, Line 12, Col 19, Line 12, Col 23, rangeIsNotASingleValue) + (Error 3925, Line 13, Col 20, Line 13, Col 24, rangeIsNotASingleValue) + (Error 635, Line 14, Col 11, Line 14, Col 22, "In sequence expressions, results are generated using 'yield'") + (Error 751, Line 15, Col 15, Line 15, Col 19, "Incomplete expression or invalid use of indexer syntax") ] + + [] + let ``A splice uses the range operator in scope`` () = + FSharp + """ +module CustomRange + +let (..) (a: int) (b: int) = seq { a; b } +if [ 0; 1..3 ] <> [ 0; 1; 3 ] then failwith "custom (..)" +""" + |> runWithPreview + + [] + let ``A builder without YieldFrom cannot splice a range`` () = + FSharp + """ +module M + +type B() = + member _.Yield(x) = [ x ] + member _.Combine(a, b) = a @ b + member _.Delay(f: unit -> _ list) = f () + +let b = B() +let r = b { 1; 2..3 } +""" + |> typecheckWithPreview + |> shouldFail + |> withDiagnostics + [ (Error 708, + Line 10, + Col 16, + Line 10, + Col 20, + "This control construct may only be used if the computation expression builder defines a 'YieldFrom' method") ] + + [] + let ``A splice in tail position uses YieldFromFinal`` () = + FSharp + """ +module Final + +let mutable calls = [] + +type B() = + member _.Yield(x) = [ x ] + member _.YieldFrom(xs: seq) = + calls <- "YieldFrom" :: calls + List.ofSeq xs + member _.YieldFromFinal(xs: seq) = + calls <- "YieldFromFinal" :: calls + List.ofSeq xs + member _.Combine(a, b) = a @ b + member _.Delay(f: unit -> _ list) = f () + +let b = B() +let r = b { 1..2; 3..4 } +if r <> [ 1; 2; 3; 4 ] || calls <> [ "YieldFromFinal"; "YieldFrom" ] then failwith $"{r} {calls}" +""" + |> runWithPreview + + [] + let ``Quotations show the splice as a range call`` () = + FSharp + """ +module Quoted + +open Microsoft.FSharp.Quotations +open Microsoft.FSharp.Quotations.Patterns + +let rec callsRange (e: Expr) = + match e with + | Call(_, mi, _) when mi.Name = "op_Range" -> true + | ExprShape.ShapeVar _ -> false + | ExprShape.ShapeLambda(_, body) -> callsRange body + | ExprShape.ShapeCombination(_, args) -> List.exists callsRange args + +if not (callsRange <@ [ 0; 1..2 ] @>) then failwith "op_Range" +""" + |> runWithPreview + + [] + let ``An integral splice in a list or array is a counted loop`` () = + FSharp + """ +module M + +let list n = [ 0; 1..n ] +let array n = [| 0; 1..2..n |] +""" + |> asLibrary + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> verifyILNotPresent [ "GetEnumerator"; "AddMany" ] + + [] + let ``A counted splice keeps the range semantics`` () = + FSharp + """ +module CountedSplices + +let check name expected actual = + if actual <> expected then failwith $"{name}: {actual}" + +let n = 3 +check "list" [ 0; 1; 2; 3; 9 ] [ 0; 1..n; 9 ] +check "array" [| 0; 3; 2; 1 |] [| 0; n .. -1 .. 1 |] +check "empty" [ 0 ] [ 0; n..1 ] +check "int64" [ 0L; 1L; 2L ] [ 0L; 1L..2L ] +check "byte" [ 0uy; 254uy; 255uy ] [ 0uy; 254uy..255uy ] +check "uint64" [ 0UL; System.UInt64.MaxValue ] [ 0UL; System.UInt64.MaxValue..System.UInt64.MaxValue ] +check "char" [ 'a'; 'b'; 'c' ] [ 'a'; 'b'..'c' ] +let threw = try ignore [ 0; 1..0..5 ]; false with :? System.ArgumentException -> true +if not threw then failwith "zero step" +""" + |> runWithPreview + + [] + let ``Before the feature each new form reports FS3350 once`` () = + FSharp + """ +module M + +type B() = + member _.Yield(x) = [ x ] + member _.YieldFrom(xs: seq<_>) = List.ofSeq xs + member _.Combine(a, b) = a @ b + member _.Delay(f: unit -> _ list) = f () + +let b = B() +let a = [ 0; 1..3 ] +let c = seq { 0; 1..3 } +let d = [| yield! 1..3 |] +let e = b { 0; 1..3 } +let f = [ yield 1..3 ] +""" + |> asLibrary + |> withLangVersion11 + |> typecheck + |> shouldFail + |> withDiagnostics + [ (Error 3350, Line 11, Col 14, Line 11, Col 18, featureNotAvailable) + (Error 3350, Line 12, Col 18, Line 12, Col 22, featureNotAvailable) + (Error 3350, Line 13, Col 19, Line 13, Col 23, featureNotAvailable) + (Error 3350, Line 14, Col 16, Line 14, Col 20, featureNotAvailable) + (Error 751, Line 15, Col 17, Line 15, Col 21, "Incomplete expression or invalid use of indexer syntax") ] diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs new file mode 100644 index 00000000000..a0a66b83526 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs @@ -0,0 +1,8 @@ +module MixedRangeListTest01 + +let test = [-3; 1..10] +let expected = [-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs new file mode 100644 index 00000000000..98384aaed9d --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs @@ -0,0 +1,8 @@ +module MixedRangeListTest02 + +let test = [-3; 1..10; 19] +let expected = [-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs new file mode 100644 index 00000000000..89fca24084a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs @@ -0,0 +1,8 @@ +module MixedRangeListTest03 + +let test = [1..3; 5..7; 10] +let expected = [1; 2; 3; 5; 6; 7; 10] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs new file mode 100644 index 00000000000..bd95c934f10 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs @@ -0,0 +1,8 @@ +module MixedRangeListTest04 + +let test = [0; 2..2..10; 15] +let expected = [0; 2; 4; 6; 8; 10; 15] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs new file mode 100644 index 00000000000..f835c06052f --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs @@ -0,0 +1,27 @@ +module MixedRangeArrayTests + +let test1 = [|-3; 1..10|] +let expected1 = [|-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|] +if test1 <> expected1 then failwith $"test1 failed: got {test1}" + +let test2 = [|-3; 1..10; 19|] +let expected2 = [|-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19|] +if test2 <> expected2 then failwith $"test2 failed: got {test2}" + +let test3 = [|1..3; 5..7; 10|] +let expected3 = [|1; 2; 3; 5; 6; 7; 10|] +if test3 <> expected3 then failwith $"test3 failed: got {test3}" + +let test4 = [|0; 2..2..10; 15|] +let expected4 = [|0; 2; 4; 6; 8; 10; 15|] +if test4 <> expected4 then failwith $"test4 failed: got {test4}" + +let test5 = [|1; 5..4; 10|] +let expected5 = [|1; 10|] +if test5 <> expected5 then failwith $"test5 failed: got {test5}" + +let test6 = [|0; 5..5; 10|] +let expected6 = [|0; 5; 10|] +if test6 <> expected6 then failwith $"test6 failed: got {test6}" + +printfn "All array tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs new file mode 100644 index 00000000000..46b6ccaef15 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs @@ -0,0 +1,25 @@ +module MixedRangeSeqTests + +let test1 = seq { 1..10; 19 } +let expected1 = seq { 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 } +if List.ofSeq test1 <> List.ofSeq expected1 then failwith $"test1 failed got {test1}" + +let test2 = seq { -3; 1..5; 10..12 } +let expected2 = seq { -3; 1; 2; 3; 4; 5; 10; 11; 12 } +if List.ofSeq test2 <> List.ofSeq expected2 then failwith $"test2 failed got {test2}" + +let test3 = seq { 0; 2..2..10; 15 } +let expected3 = seq { 0; 2; 4; 6; 8; 10; 15 } +if List.ofSeq test3 <> List.ofSeq expected3 then failwith $"test3 failed got {test3}" + +let test4 = seq { + yield 1 + yield! seq { 2..5 } + yield 10 + yield! seq { 15..2..20 } +} + +let expected4 = seq { 1; 2; 3; 4; 5; 10; 15; 17; 19 } +if List.ofSeq test4 <> List.ofSeq expected4 then failwith $"test4 failed got {test4}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs new file mode 100644 index 00000000000..0a7e119714b --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs @@ -0,0 +1,21 @@ +module MixedRangeSeqTests + +let test1 = seq { 1..10; 19 } +let expected1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19] +if List.ofSeq test1 <> expected1 then failwith $"test1 failed" + +let test2 = seq { 0; 2..2..10; 15 } +let expected2 = [|0; 2; 4; 6; 8; 10; 15|] +if Array.ofSeq test2 <> expected2 then failwith $"test2 failed" + +let test3 = seq { + yield 1 + yield! [2..5] + yield 10 + yield! [| 15..2..20 |] +} + +let expected3 = [1; 2; 3; 4; 5; 10; 15; 17; 19] +if List.ofSeq test3 <> expected3 then failwith $"test3 failed" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs new file mode 100644 index 00000000000..8a9bce2ed58 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs @@ -0,0 +1,18 @@ +module TypeInferenceTests + +let test1: int list = [1..5; 10] +let test2: int array = [|1..5; 10|] +let test3: seq = seq { 1..5; 10 } + +let inline mixedRange start finish extra = + [start..finish; extra] + +let test4 = mixedRange 1 5 10 +let test5 = mixedRange 1.0 5.0 10.0 +let test6 = mixedRange 'a' 'e' 'z' + +if test4 <> [1; 2; 3; 4; 5; 10] then failwith "int range failed" +if test5 <> [1.0; 2.0; 3.0; 4.0; 5.0; 10.0] then failwith "float range failed" +if test6 <> ['a'; 'b'; 'c'; 'd'; 'e'; 'z'] then failwith "char range failed" + +printfn "Type inference tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs new file mode 100644 index 00000000000..7989a0ace81 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs @@ -0,0 +1,25 @@ +module CustomCEWithRanges + +type ArrayBuilder() = + member _.Yield(x) = [| x |] + member _.YieldFrom(xs: seq<_>) = Seq.toArray xs + member _.Zero() = [||] + member _.Combine(a: int array, b: unit -> int array) = Array.append a (b()) + member _.Delay(f) = f + member _.Run(f) = f() + +let array = ArrayBuilder() + +let test1 = array { -3; 1..10; 19 } +let expected1 = [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] +if test1 <> expected1 then failwith $"test1 failed: got {test1}" + +let test2 = array { 1..3; 5..7; 10 } +let expected2 = [| 1; 2; 3; 5; 6; 7; 10 |] +if test2 <> expected2 then failwith $"test2 failed: got {test2}" + +let test3 = array { 0; 2..2..10; 15 } +let expected3 = [| 0; 2; 4; 6; 8; 10; 15 |] +if test3 <> expected3 then failwith $"test3 failed: got {test3}" + +printfn "Custom CE with ranges tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs new file mode 100644 index 00000000000..6090432cf8c --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs @@ -0,0 +1,17 @@ +module MixedRangeVersionTest + +let a = seq { yield! seq { 1..10 }; 19 } +let b = [-3; yield! [1..10]] +let c = [|-3; yield! [|1..10|]; 19|] + +let d = seq { 1..10; 19 } +let e = [-3; 1..10] +let f = [|-3; 1..10; 19|] + +if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" + +if b <> e then failwithf $"b = e failed got {b}" + +if c <> f then failwithf $"c = f failed got {c}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs new file mode 100644 index 00000000000..0434fd0a57b --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs @@ -0,0 +1,63 @@ +module MixedRangeVersionTest + +let a = seq { + 0 + 1..5 + 10 + 11..15 + 20 + 21..25 +} + +let b = [ + 0 + 1..5 + 10 + 11..15 + 20 + 21..25 +] +let c = [| + 0 + 1..5 + 10 + 11..15 + 20 + 21..25 +|] + +let d = seq { + 0 + yield! [1..5] + 10 + yield! [11..15] + 20 + yield! [21..25] + +} + +let e = [ + 0 + yield! [1..5] + 10 + yield! [11..15] + 20 + yield! [21..25] +] + +let f = [| + 0 + yield! [1..5] + 10 + yield! [11..15] + 20 + yield! [21..25] +|] + +if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" + +if b <> e then failwithf $"b = e failed got {b}" + +if c <> f then failwithf $"c = f failed got {c}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs new file mode 100644 index 00000000000..b85c0e03565 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs @@ -0,0 +1,63 @@ +module MixedRangeVersionTest + +let a = seq { + 0 + 1..2..10 + 10 + 11..15 + 20 + 21..1..30 +} + +let b = [ + 0 + 1..2..10 + 10 + 11..15 + 20 + 21..1..30 +] +let c = [| + 0 + 1..2..10 + 10 + 11..15 + 20 + 21..1..30 +|] + +let d = seq { + 0 + yield! [1..2..10] + 10 + yield! [11..15] + 20 + yield! [21..1..30] + +} + +let e = [ + 0 + yield! [1..2..10] + 10 + yield! [11..15] + 20 + yield! [21..1..30] +] + +let f = [| + 0 + yield! [1..2..10] + 10 + yield! [11..15] + 20 + yield! [21..1..30] +|] + +if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" + +if b <> e then failwithf $"b = e failed got {b}" + +if c <> f then failwithf $"c = f failed got {c}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs new file mode 100644 index 00000000000..a3c010c5fbe --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs @@ -0,0 +1,17 @@ +module MixedRangeVersionTest + +let a = [ yield! 1..10 ] + +let b = [ yield! [1..10] ] + +let c = seq { yield! seq { 1..10 } } + +let d = [ 1..10 ] +let e = seq { 1..10 } +let f = [| 1..10 |] + +if a <> d then failwithf $"a = d failed got {a}" +if b <> List.ofSeq e then failwithf $"b = d failed got {b}" +if Array.ofSeq c <> f then failwithf $"f = d failed got {f}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs new file mode 100644 index 00000000000..e7e713f8a9c --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs @@ -0,0 +1,17 @@ +module MixedRangeVersionTest + +let a = [ yield! 1..10; 19; yield! [20..30] ] + +let b = [ yield! [1..10]; 19; yield! 20..30 ] + +let c = seq { yield! seq { 1..10 }; 19; yield! seq { 20..30 } } + +let d = [ 1..10; 19; yield! 20..30 ] +let e = seq { yield! 1..10; 19; yield! 20..30 } +let f = [| yield! 1..10; 19; yield! 20..30 |] + +if a <> d then failwithf $"a = d failed got {a}" +if b <> List.ofSeq e then failwithf $"b = d failed got {b}" +if Array.ofSeq c <> f then failwithf $"f = d failed got {f}" + +printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs new file mode 100644 index 00000000000..2995b7d4fbd --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs @@ -0,0 +1,16 @@ +module BackwardCompatTest + +let makeCustomAttributes() = + [| + if true then + yield "attr1" + yield! ["attr2"; "attr3"] + yield! ["attr4"] + |] + +let attrs = makeCustomAttributes() +let expected = [|"attr1"; "attr2"; "attr3"; "attr4"|] + +if attrs <> expected then failwithf $"attrs failed: got {attrs}" + +printfn "Backward compatibility test passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs new file mode 100644 index 00000000000..8b85e5dfc46 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs @@ -0,0 +1,22 @@ +module YieldVsYieldBang + +let a1 = [ yield! 1..5 ] +let a2 = seq { yield! 1..5 } +let a3 = [| yield! 1..5 |] + +// let b1: int list = [ yield 1..5 ] // Would give type error + +let c1: int list = [ yield! 1..5 ] +let c2: int seq = seq { yield! 1..5 } +let c3: int array = [| yield! 1..5 |] + +let expected = [1; 2; 3; 4; 5] + +if a1 <> expected then failwithf $"a1 failed: got {a1}" +if List.ofSeq a2 <> expected then failwithf $"a2 failed: got {List.ofSeq a2}" +if List.ofArray a3 <> expected then failwithf $"a3 failed: got {List.ofArray a3}" +if c1 <> expected then failwithf $"c1 failed: got {c1}" +if List.ofSeq c2 <> expected then failwithf $"c2 failed: got {List.ofSeq c2}" +if List.ofArray c3 <> expected then failwithf $"c3 failed: got {List.ofArray c3}" + +printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs new file mode 100644 index 00000000000..98466908767 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs @@ -0,0 +1,39 @@ +module MixedRangeVersionTest + +let a: int list = [ yield! 1..10 ] +let a1: int list = [ yield! [1..10] ] + +let b: int seq = seq { yield! seq { 1..10 } } + +let c: int list = [ if true then yield 1 else yield! 1..10] +let c1: int list = [ if true then yield! 1..10] + +let d: int list = [if true then yield 1 else yield! [1..10]] +let d1 : int list = [if true then yield! [1..10]] + +let e: int seq = seq { if true then yield 1 else yield! seq { 1..10 } } +let e1: int seq = seq { if true then yield! seq { 1..10 } } + +let f: int list = [ match true with | true -> yield 1 | false -> yield! 1..10] + +let f1: int list = [ match true with | true -> yield 1 | false -> yield! [1..10]] + +let g: int seq = seq { match true with | true -> yield 1 | false -> yield! seq { 1..10 } } + +let expected = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] +let expectedOne = [1] + +if a <> expected then failwithf $"a failed: got {a}" +if a1 <> expected then failwithf $"a1 failed: got {a1}" +if List.ofSeq b <> expected then failwithf $"b failed: got {List.ofSeq b}" +if c <> expectedOne then failwithf $"c failed: got {c}" +if c1 <> expected then failwithf $"c1 failed: got {c1}" +if d <> expectedOne then failwithf $"d failed: got {d}" +if d1 <> expected then failwithf $"d1 failed: got {d1}" +if List.ofSeq e <> expectedOne then failwithf $"e failed: got {List.ofSeq e}" +if List.ofSeq e1 <> expected then failwithf $"e1 failed: got {List.ofSeq e1}" +if f <> expectedOne then failwithf $"f failed: got {f}" +if f1 <> expectedOne then failwithf $"f1 failed: got {f1}" +if List.ofSeq g <> expectedOne then failwithf $"g failed: got {List.ofSeq g}" + +printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs new file mode 100644 index 00000000000..39fbaa196b8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs @@ -0,0 +1,55 @@ +module MixedRangeVersionTest + +let f p q r = + [ + if p then + if q then 3 + if p then + if q then + if r then 3 + ] + +let f1 p q r = + [ + if p then + if q then yield! 1..10 + if p then + if q then + if r then yield! 1..10 + ] + +let f2 x (|P|_|) (|Q|_|) (|R|_|) = + [ + match x with + | P _ -> 3 + | _ -> () + + match x with + | P y -> + match y with + | Q z -> + match z with + | R -> 3 + | _ -> () + | _ -> () + | _ -> () + ] + +let f3 x (|P|_|) (|Q|_|) (|R|_|) = + [ + match x with + | P _ -> yield! 1..10 + | _ -> () + + match x with + | P y -> + match y with + | Q z -> + match z with + | R -> yield! 1..10 + | _ -> () + | _ -> () + | _ -> () + ] + +printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs new file mode 100644 index 00000000000..c05b2893c6a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs @@ -0,0 +1,8 @@ +module CharRangesTest + +let test = seq { 'a'..'c'; 'Z' } |> Seq.toArray +let expected = [| 'a'; 'b'; 'c'; 'Z' |] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs new file mode 100644 index 00000000000..4e7863d73fc --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs @@ -0,0 +1,9 @@ +module BigIntRangesTest +open System.Numerics + +let test = [ 0I..2I..6I; 10I ] +let expected = [0I;2I;4I;6I;10I] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs new file mode 100644 index 00000000000..928797117ea --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs @@ -0,0 +1,8 @@ +module EmptyRangesTest + +let test = [ 0; 1..0; 2..2; 3..1; 4 ] +let expected = [0;2;4] + +if test <> expected then failwith $"test failed: got {test}" + +printfn "All list tests passed!" From f166dc04fbd41f10ec32ea657e00cd19fd8057c9 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 11 Oct 2026 01:00:10 +0200 Subject: [PATCH 2/5] Add release notes for mixed ranges and values Co-Authored-By: Claude Opus 5.5 --- docs/release-notes/.FSharp.Compiler.Service/11.0.200.md | 1 + docs/release-notes/.Language/preview.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index 99464a277dd..d4fc180ecb5 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -1,5 +1,6 @@ ### Added +* 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:`, 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:` 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)) ### Fixed diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d90fb95fbe7..204b19c320f 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -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 From db4095be06ea7a71196c2a720cca081dffde62fe Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 11 Oct 2026 10:39:50 +0200 Subject: [PATCH 3/5] Report only FS3925 for a range that is yielded or returned After the error, recovery passed the range to 'Yield' or 'Return' as a sequence, so a builder whose method takes an element also reported a type mismatch. 'yield e1..e2' now recovers as 'yield! e1..e2' when the builder has 'YieldFrom'. Otherwise, and for 'return', the method gets a placeholder argument of any type. Co-Authored-By: Claude Opus 5.5 --- .../CheckComputationExpressions.fs | 23 ++++++++++----- .../MixedSequenceExpressionTests.fs | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index f5196e55f57..d71bbc23a4d 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -2447,15 +2447,22 @@ let rec TryTranslateComputationExpression Some(translatedCtxt returnFromCall) - | SynExpr.YieldOrReturn((isYield, _), synYieldOrReturnExpr, mFull, { YieldOrReturnKeyword = m }) -> - // A range is not yielded or returned as one value (FS-1031); recover with the range as a sequence. - let synYieldOrReturnExpr = - match synYieldOrReturnExpr with - | BareRangeExpr rangeExpr when cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions -> - errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synYieldOrReturnExpr.Range)) - rangeExpr - | _ -> synYieldOrReturnExpr + // A range is not yielded or returned as one value (FS-1031). Recover 'yield e1..e2' as 'yield! e1..e2' when the + // builder can splice; otherwise 'Yield' or 'Return' takes an argument of any type, so no type error follows. + | SynExpr.YieldOrReturn(((isYield, _) as flags), (BareRangeExpr _ as synRange), mFull, ({ YieldOrReturnKeyword = m } as trivia)) when + cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions + -> + errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synRange.Range)) + + let recovery = + if isYield && hasBuilderMethod ceenv m "YieldFrom" then + mkRangeSplice synRange + else + SynExpr.YieldOrReturn(flags, arbExpr ("rangeAsSingleValue", synRange.Range), mFull, trivia) + + TryTranslateComputationExpression ceenv firstTry q varSpace recovery translatedCtxt + | SynExpr.YieldOrReturn((isYield, _), synYieldOrReturnExpr, mFull, { YieldOrReturnKeyword = m }) -> let methName = (if isYield then "Yield" else "Return") if ceenv.isQuery && not isYield then diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs index 2ea2ef82e73..194122cac06 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs @@ -164,6 +164,35 @@ let g = [ 1; (2..3) ] (Error 635, Line 14, Col 11, Line 14, Col 22, "In sequence expressions, results are generated using 'yield'") (Error 751, Line 15, Col 15, Line 15, Col 19, "Incomplete expression or invalid use of indexer syntax") ] + [] + let ``A yielded or returned range gives no type error for a builder that takes an element`` () = + FSharp + """ +module M + +type WithYieldFrom() = + member _.Yield(x: int) = [ x ] + member _.YieldFrom(xs: seq) = List.ofSeq xs + member _.Return(x: int) = [ x ] + member _.Delay(f: unit -> int list) = f () + +type WithoutYieldFrom() = + member _.Yield(x: int) = [ x ] + member _.Delay(f: unit -> int list) = f () + +let splicing = WithYieldFrom() +let single = WithoutYieldFrom() +let a = splicing { yield 1..3 } +let b = splicing { return 1..3 } +let c = single { yield 1..3 } +""" + |> typecheckWithPreview + |> shouldFail + |> withDiagnostics + [ (Error 3925, Line 16, Col 26, Line 16, Col 30, rangeIsNotASingleValue) + (Error 3925, Line 17, Col 27, Line 17, Col 31, rangeIsNotASingleValue) + (Error 3925, Line 18, Col 24, Line 18, Col 28, rangeIsNotASingleValue) ] + [] let ``A splice uses the range operator in scope`` () = FSharp From d053f72c83621c5ec0a8ea1cb31c055b2bc465c9 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Sun, 11 Oct 2026 10:39:53 +0200 Subject: [PATCH 4/5] Fold the mixed range fixture files into named scenarios The 21 standalone fixtures each compiled their own program. Their input and output pairs now run as named checks in two programs, and the two files that differed only in two ranges are one theory. Co-Authored-By: Claude Opus 5.5 --- .../MixedSequenceExpressionTests.fs | 283 ++++++++++++++++-- .../SequenceExpressions02a.fs | 8 - .../SequenceExpressions02b.fs | 8 - .../SequenceExpressions02c.fs | 8 - .../SequenceExpressions02d.fs | 8 - .../SequenceExpressions03.fs | 27 -- .../SequenceExpressions04.fs | 25 -- .../SequenceExpressions05.fs | 21 -- .../SequenceExpressions06.fs | 18 -- .../SequenceExpressions07.fs | 25 -- .../SequenceExpressions08.fs | 17 -- .../SequenceExpressions10.fs | 63 ---- .../SequenceExpressions11.fs | 63 ---- .../SequenceExpressions12.fs | 17 -- .../SequenceExpressions13.fs | 17 -- .../SequenceExpressions14.fs | 16 - .../SequenceExpressions15.fs | 22 -- .../SequenceExpressions16.fs | 39 --- .../SequenceExpressions17.fs | 55 ---- .../SequenceExpressions18.fs | 8 - .../SequenceExpressions19.fs | 9 - .../SequenceExpressions20.fs | 8 - 22 files changed, 257 insertions(+), 508 deletions(-) delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs index 194122cac06..a7c29ff761f 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs @@ -3,7 +3,6 @@ namespace Language open Xunit -open FSharp.Test open FSharp.Test.Compiler // FS-1031: ranges mixed with values in list, array, sequence and computation expressions. @@ -26,31 +25,263 @@ module MixedSequenceExpressionTests = let private rangeIsNotASingleValue = "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." - [] - let ``Ranges mixed with values give the spliced elements`` compilation = runWithPreview compilation + [] + let ``Ranges mixed with values give the spliced elements`` () = + FSharp + """ +module MixedRanges + +let check name expected actual = + if actual <> expected then failwith $"{name}: {actual}" + +check "list: value, range" [ -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 ] [ -3; 1..10 ] +check "list: value, range, value" [ -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 ] [ -3; 1..10; 19 ] +check "list: two ranges, value" [ 1; 2; 3; 5; 6; 7; 10 ] [ 1..3; 5..7; 10 ] +check "list: stepped range" [ 0; 2; 4; 6; 8; 10; 15 ] [ 0; 2..2..10; 15 ] +check "list: empty and one-element ranges" [ 0; 2; 4 ] [ 0; 1..0; 2..2; 3..1; 4 ] +check "list: bigint" [ 0I; 2I; 4I; 6I; 10I ] [ 0I..2I..6I; 10I ] + +check "array: value, range" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |] [| -3; 1..10 |] +check "array: value, range, value" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] [| -3; 1..10; 19 |] +check "array: two ranges, value" [| 1; 2; 3; 5; 6; 7; 10 |] [| 1..3; 5..7; 10 |] +check "array: stepped range" [| 0; 2; 4; 6; 8; 10; 15 |] [| 0; 2..2..10; 15 |] +check "array: empty range" [| 1; 10 |] [| 1; 5..4; 10 |] +check "array: one-element range" [| 0; 5; 10 |] [| 0; 5..5; 10 |] + +check "seq: range, value" [ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 ] (List.ofSeq (seq { 1..10; 19 })) +check "seq: value, two ranges" [ -3; 1; 2; 3; 4; 5; 10; 11; 12 ] (List.ofSeq (seq { -3; 1..5; 10..12 })) +check "seq: stepped range" [ 0; 2; 4; 6; 8; 10; 15 ] (List.ofSeq (seq { 0; 2..2..10; 15 })) +check "seq: char" [| 'a'; 'b'; 'c'; 'Z' |] (Seq.toArray (seq { 'a'..'c'; 'Z' })) + +type ArrayBuilder() = + member _.Yield(x) = [| x |] + member _.YieldFrom(xs: seq<_>) = Seq.toArray xs + member _.Zero() = [||] + member _.Combine(a: int array, b: unit -> int array) = Array.append a (b ()) + member _.Delay(f) = f + member _.Run(f) = f () + +let array = ArrayBuilder() + +check "builder: value, range, value" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] (array { -3; 1..10; 19 }) +check "builder: two ranges, value" [| 1; 2; 3; 5; 6; 7; 10 |] (array { 1..3; 5..7; 10 }) +check "builder: stepped range" [| 0; 2; 4; 6; 8; 10; 15 |] (array { 0; 2..2..10; 15 }) + +let annotatedList: int list = [ 1..5; 10 ] +let annotatedArray: int array = [| 1..5; 10 |] +let annotatedSeq: seq = seq { 1..5; 10 } + +check "annotated list" [ 1; 2; 3; 4; 5; 10 ] annotatedList +check "annotated array" [| 1; 2; 3; 4; 5; 10 |] annotatedArray +check "annotated seq" [ 1; 2; 3; 4; 5; 10 ] (List.ofSeq annotatedSeq) + +let inline mixedRange start finish extra = [ start..finish; extra ] + +check "inline: int" [ 1; 2; 3; 4; 5; 10 ] (mixedRange 1 5 10) +check "inline: float" [ 1.0; 2.0; 3.0; 4.0; 5.0; 10.0 ] (mixedRange 1.0 5.0 10.0) +check "inline: char" [ 'a'; 'b'; 'c'; 'd'; 'e'; 'z' ] (mixedRange 'a' 'e' 'z') +""" + |> runWithPreview + + [] + [] + [] + let ``Elements on separate lines mix values and ranges`` (first: string) (last: string) = + FSharp( + """ +module SeparateLines + +let a = seq { + 0 + FIRST + 10 + 11..15 + 20 + LAST +} + +let b = [ + 0 + FIRST + 10 + 11..15 + 20 + LAST +] + +let c = [| + 0 + FIRST + 10 + 11..15 + 20 + LAST +|] + +let d = seq { + 0 + yield! [FIRST] + 10 + yield! [11..15] + 20 + yield! [LAST] +} + +let e = [ + 0 + yield! [FIRST] + 10 + yield! [11..15] + 20 + yield! [LAST] +] + +let f = [| + 0 + yield! [FIRST] + 10 + yield! [11..15] + 20 + yield! [LAST] +|] + +if List.ofSeq a <> List.ofSeq d then failwith $"seq: {List.ofSeq a}" +if b <> e then failwith $"list: {b}" +if c <> f then failwith $"array: {c}" +""" + .Replace("FIRST", first) + .Replace("LAST", last) + ) + |> runWithPreview + + [] + let ``yield! takes a range and gives the elements of the same collection`` () = + FSharp + """ +module YieldBang + +let check name expected actual = + if actual <> expected then failwith $"{name}: {actual}" + +let oneToFive = [ 1; 2; 3; 4; 5 ] +let oneToTen = [ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 ] + +check "list: yield! range" [ 1..10 ] [ yield! 1..10 ] +check "list: yield! list" (List.ofSeq (seq { 1..10 })) [ yield! [ 1..10 ] ] +check "seq: yield! seq" [| 1..10 |] (Array.ofSeq (seq { yield! seq { 1..10 } })) + +check "list: yield! range, value, yield! list" [ 1..10; 19; yield! 20..30 ] [ yield! 1..10; 19; yield! [ 20..30 ] ] +check "list: yield! list, value, yield! range" (List.ofSeq (seq { yield! 1..10; 19; yield! 20..30 })) [ yield! [ 1..10 ]; 19; yield! 20..30 ] +check "array: yield! ranges" (Array.ofSeq (seq { yield! seq { 1..10 }; 19; yield! seq { 20..30 } })) [| yield! 1..10; 19; yield! 20..30 |] + +check "seq: range as yield! seq" (List.ofSeq (seq { yield! seq { 1..10 }; 19 })) (List.ofSeq (seq { 1..10; 19 })) +check "list: range as yield! list" [ -3; yield! [ 1..10 ] ] [ -3; 1..10 ] +check "array: range as yield! array" [| -3; yield! [| 1..10 |]; 19 |] [| -3; 1..10; 19 |] + +check "seq: yield and yield! seq" [ 1; 2; 3; 4; 5; 10; 15; 17; 19 ] (List.ofSeq (seq { yield 1; yield! seq { 2..5 }; yield 10; yield! seq { 15..2..20 } })) +check "seq: yield and yield! list, array" [ 1; 2; 3; 4; 5; 10; 15; 17; 19 ] (List.ofSeq (seq { yield 1; yield! [ 2..5 ]; yield 10; yield! [| 15..2..20 |] })) + +let attributes = + [| + if true then + yield "attr1" + yield! [ "attr2"; "attr3" ] + yield! [ "attr4" ] + |] + +check "explicit yields without ranges" [| "attr1"; "attr2"; "attr3"; "attr4" |] attributes + +check "yield! 1..5: list" oneToFive [ yield! 1..5 ] +check "yield! 1..5: seq" oneToFive (List.ofSeq (seq { yield! 1..5 })) +check "yield! 1..5: array" oneToFive (List.ofArray [| yield! 1..5 |]) + +let annotatedList: int list = [ yield! 1..5 ] +let annotatedSeq: int seq = seq { yield! 1..5 } +let annotatedArray: int array = [| yield! 1..5 |] + +check "annotated list" oneToFive annotatedList +check "annotated seq" oneToFive (List.ofSeq annotatedSeq) +check "annotated array" oneToFive (List.ofArray annotatedArray) + +let a: int list = [ yield! 1..10 ] +let a1: int list = [ yield! [ 1..10 ] ] +let b: int seq = seq { yield! seq { 1..10 } } +let c: int list = [ if true then yield 1 else yield! 1..10 ] +let c1: int list = [ if true then yield! 1..10 ] +let d: int list = [ if true then yield 1 else yield! [ 1..10 ] ] +let d1: int list = [ if true then yield! [ 1..10 ] ] +let e: int seq = seq { if true then yield 1 else yield! seq { 1..10 } } +let e1: int seq = seq { if true then yield! seq { 1..10 } } +let f: int list = [ match true with | true -> yield 1 | false -> yield! 1..10 ] +let f1: int list = [ match true with | true -> yield 1 | false -> yield! [ 1..10 ] ] +let g: int seq = seq { match true with | true -> yield 1 | false -> yield! seq { 1..10 } } + +check "a" oneToTen a +check "a1" oneToTen a1 +check "b" oneToTen (List.ofSeq b) +check "c" [ 1 ] c +check "c1" oneToTen c1 +check "d" [ 1 ] d +check "d1" oneToTen d1 +check "e" [ 1 ] (List.ofSeq e) +check "e1" oneToTen (List.ofSeq e1) +check "f" [ 1 ] f +check "f1" [ 1 ] f1 +check "g" [ 1 ] (List.ofSeq g) + +let nestedIf p q r = + [ + if p then + if q then 3 + if p then + if q then + if r then 3 + ] + +let nestedIfWithRange p q r = + [ + if p then + if q then yield! 1..10 + if p then + if q then + if r then yield! 1..10 + ] + +let nestedMatch x (|P|_|) (|Q|_|) (|R|_|) = + [ + match x with + | P _ -> 3 + | _ -> () + + match x with + | P y -> + match y with + | Q z -> + match z with + | R -> 3 + | _ -> () + | _ -> () + | _ -> () + ] + +let nestedMatchWithRange x (|P|_|) (|Q|_|) (|R|_|) = + [ + match x with + | P _ -> yield! 1..10 + | _ -> () + + match x with + | P y -> + match y with + | Q z -> + match z with + | R -> yield! 1..10 + | _ -> () + | _ -> () + | _ -> () + ] +""" + |> runWithPreview [] let ``A range splices in every element position`` () = diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs deleted file mode 100644 index a0a66b83526..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02a.fs +++ /dev/null @@ -1,8 +0,0 @@ -module MixedRangeListTest01 - -let test = [-3; 1..10] -let expected = [-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs deleted file mode 100644 index 98384aaed9d..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02b.fs +++ /dev/null @@ -1,8 +0,0 @@ -module MixedRangeListTest02 - -let test = [-3; 1..10; 19] -let expected = [-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs deleted file mode 100644 index 89fca24084a..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02c.fs +++ /dev/null @@ -1,8 +0,0 @@ -module MixedRangeListTest03 - -let test = [1..3; 5..7; 10] -let expected = [1; 2; 3; 5; 6; 7; 10] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs deleted file mode 100644 index bd95c934f10..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions02d.fs +++ /dev/null @@ -1,8 +0,0 @@ -module MixedRangeListTest04 - -let test = [0; 2..2..10; 15] -let expected = [0; 2; 4; 6; 8; 10; 15] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs deleted file mode 100644 index f835c06052f..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions03.fs +++ /dev/null @@ -1,27 +0,0 @@ -module MixedRangeArrayTests - -let test1 = [|-3; 1..10|] -let expected1 = [|-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10|] -if test1 <> expected1 then failwith $"test1 failed: got {test1}" - -let test2 = [|-3; 1..10; 19|] -let expected2 = [|-3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19|] -if test2 <> expected2 then failwith $"test2 failed: got {test2}" - -let test3 = [|1..3; 5..7; 10|] -let expected3 = [|1; 2; 3; 5; 6; 7; 10|] -if test3 <> expected3 then failwith $"test3 failed: got {test3}" - -let test4 = [|0; 2..2..10; 15|] -let expected4 = [|0; 2; 4; 6; 8; 10; 15|] -if test4 <> expected4 then failwith $"test4 failed: got {test4}" - -let test5 = [|1; 5..4; 10|] -let expected5 = [|1; 10|] -if test5 <> expected5 then failwith $"test5 failed: got {test5}" - -let test6 = [|0; 5..5; 10|] -let expected6 = [|0; 5; 10|] -if test6 <> expected6 then failwith $"test6 failed: got {test6}" - -printfn "All array tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs deleted file mode 100644 index 46b6ccaef15..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions04.fs +++ /dev/null @@ -1,25 +0,0 @@ -module MixedRangeSeqTests - -let test1 = seq { 1..10; 19 } -let expected1 = seq { 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 } -if List.ofSeq test1 <> List.ofSeq expected1 then failwith $"test1 failed got {test1}" - -let test2 = seq { -3; 1..5; 10..12 } -let expected2 = seq { -3; 1; 2; 3; 4; 5; 10; 11; 12 } -if List.ofSeq test2 <> List.ofSeq expected2 then failwith $"test2 failed got {test2}" - -let test3 = seq { 0; 2..2..10; 15 } -let expected3 = seq { 0; 2; 4; 6; 8; 10; 15 } -if List.ofSeq test3 <> List.ofSeq expected3 then failwith $"test3 failed got {test3}" - -let test4 = seq { - yield 1 - yield! seq { 2..5 } - yield 10 - yield! seq { 15..2..20 } -} - -let expected4 = seq { 1; 2; 3; 4; 5; 10; 15; 17; 19 } -if List.ofSeq test4 <> List.ofSeq expected4 then failwith $"test4 failed got {test4}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs deleted file mode 100644 index 0a7e119714b..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions05.fs +++ /dev/null @@ -1,21 +0,0 @@ -module MixedRangeSeqTests - -let test1 = seq { 1..10; 19 } -let expected1 = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19] -if List.ofSeq test1 <> expected1 then failwith $"test1 failed" - -let test2 = seq { 0; 2..2..10; 15 } -let expected2 = [|0; 2; 4; 6; 8; 10; 15|] -if Array.ofSeq test2 <> expected2 then failwith $"test2 failed" - -let test3 = seq { - yield 1 - yield! [2..5] - yield 10 - yield! [| 15..2..20 |] -} - -let expected3 = [1; 2; 3; 4; 5; 10; 15; 17; 19] -if List.ofSeq test3 <> expected3 then failwith $"test3 failed" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs deleted file mode 100644 index 8a9bce2ed58..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions06.fs +++ /dev/null @@ -1,18 +0,0 @@ -module TypeInferenceTests - -let test1: int list = [1..5; 10] -let test2: int array = [|1..5; 10|] -let test3: seq = seq { 1..5; 10 } - -let inline mixedRange start finish extra = - [start..finish; extra] - -let test4 = mixedRange 1 5 10 -let test5 = mixedRange 1.0 5.0 10.0 -let test6 = mixedRange 'a' 'e' 'z' - -if test4 <> [1; 2; 3; 4; 5; 10] then failwith "int range failed" -if test5 <> [1.0; 2.0; 3.0; 4.0; 5.0; 10.0] then failwith "float range failed" -if test6 <> ['a'; 'b'; 'c'; 'd'; 'e'; 'z'] then failwith "char range failed" - -printfn "Type inference tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs deleted file mode 100644 index 7989a0ace81..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions07.fs +++ /dev/null @@ -1,25 +0,0 @@ -module CustomCEWithRanges - -type ArrayBuilder() = - member _.Yield(x) = [| x |] - member _.YieldFrom(xs: seq<_>) = Seq.toArray xs - member _.Zero() = [||] - member _.Combine(a: int array, b: unit -> int array) = Array.append a (b()) - member _.Delay(f) = f - member _.Run(f) = f() - -let array = ArrayBuilder() - -let test1 = array { -3; 1..10; 19 } -let expected1 = [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] -if test1 <> expected1 then failwith $"test1 failed: got {test1}" - -let test2 = array { 1..3; 5..7; 10 } -let expected2 = [| 1; 2; 3; 5; 6; 7; 10 |] -if test2 <> expected2 then failwith $"test2 failed: got {test2}" - -let test3 = array { 0; 2..2..10; 15 } -let expected3 = [| 0; 2; 4; 6; 8; 10; 15 |] -if test3 <> expected3 then failwith $"test3 failed: got {test3}" - -printfn "Custom CE with ranges tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs deleted file mode 100644 index 6090432cf8c..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions08.fs +++ /dev/null @@ -1,17 +0,0 @@ -module MixedRangeVersionTest - -let a = seq { yield! seq { 1..10 }; 19 } -let b = [-3; yield! [1..10]] -let c = [|-3; yield! [|1..10|]; 19|] - -let d = seq { 1..10; 19 } -let e = [-3; 1..10] -let f = [|-3; 1..10; 19|] - -if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" - -if b <> e then failwithf $"b = e failed got {b}" - -if c <> f then failwithf $"c = f failed got {c}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs deleted file mode 100644 index 0434fd0a57b..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions10.fs +++ /dev/null @@ -1,63 +0,0 @@ -module MixedRangeVersionTest - -let a = seq { - 0 - 1..5 - 10 - 11..15 - 20 - 21..25 -} - -let b = [ - 0 - 1..5 - 10 - 11..15 - 20 - 21..25 -] -let c = [| - 0 - 1..5 - 10 - 11..15 - 20 - 21..25 -|] - -let d = seq { - 0 - yield! [1..5] - 10 - yield! [11..15] - 20 - yield! [21..25] - -} - -let e = [ - 0 - yield! [1..5] - 10 - yield! [11..15] - 20 - yield! [21..25] -] - -let f = [| - 0 - yield! [1..5] - 10 - yield! [11..15] - 20 - yield! [21..25] -|] - -if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" - -if b <> e then failwithf $"b = e failed got {b}" - -if c <> f then failwithf $"c = f failed got {c}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs deleted file mode 100644 index b85c0e03565..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions11.fs +++ /dev/null @@ -1,63 +0,0 @@ -module MixedRangeVersionTest - -let a = seq { - 0 - 1..2..10 - 10 - 11..15 - 20 - 21..1..30 -} - -let b = [ - 0 - 1..2..10 - 10 - 11..15 - 20 - 21..1..30 -] -let c = [| - 0 - 1..2..10 - 10 - 11..15 - 20 - 21..1..30 -|] - -let d = seq { - 0 - yield! [1..2..10] - 10 - yield! [11..15] - 20 - yield! [21..1..30] - -} - -let e = [ - 0 - yield! [1..2..10] - 10 - yield! [11..15] - 20 - yield! [21..1..30] -] - -let f = [| - 0 - yield! [1..2..10] - 10 - yield! [11..15] - 20 - yield! [21..1..30] -|] - -if List.ofSeq a <> List.ofSeq d then failwithf $"a = d failed got {List.ofSeq a}" - -if b <> e then failwithf $"b = e failed got {b}" - -if c <> f then failwithf $"c = f failed got {c}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs deleted file mode 100644 index a3c010c5fbe..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions12.fs +++ /dev/null @@ -1,17 +0,0 @@ -module MixedRangeVersionTest - -let a = [ yield! 1..10 ] - -let b = [ yield! [1..10] ] - -let c = seq { yield! seq { 1..10 } } - -let d = [ 1..10 ] -let e = seq { 1..10 } -let f = [| 1..10 |] - -if a <> d then failwithf $"a = d failed got {a}" -if b <> List.ofSeq e then failwithf $"b = d failed got {b}" -if Array.ofSeq c <> f then failwithf $"f = d failed got {f}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs deleted file mode 100644 index e7e713f8a9c..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions13.fs +++ /dev/null @@ -1,17 +0,0 @@ -module MixedRangeVersionTest - -let a = [ yield! 1..10; 19; yield! [20..30] ] - -let b = [ yield! [1..10]; 19; yield! 20..30 ] - -let c = seq { yield! seq { 1..10 }; 19; yield! seq { 20..30 } } - -let d = [ 1..10; 19; yield! 20..30 ] -let e = seq { yield! 1..10; 19; yield! 20..30 } -let f = [| yield! 1..10; 19; yield! 20..30 |] - -if a <> d then failwithf $"a = d failed got {a}" -if b <> List.ofSeq e then failwithf $"b = d failed got {b}" -if Array.ofSeq c <> f then failwithf $"f = d failed got {f}" - -printfn "All sequence tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs deleted file mode 100644 index 2995b7d4fbd..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions14.fs +++ /dev/null @@ -1,16 +0,0 @@ -module BackwardCompatTest - -let makeCustomAttributes() = - [| - if true then - yield "attr1" - yield! ["attr2"; "attr3"] - yield! ["attr4"] - |] - -let attrs = makeCustomAttributes() -let expected = [|"attr1"; "attr2"; "attr3"; "attr4"|] - -if attrs <> expected then failwithf $"attrs failed: got {attrs}" - -printfn "Backward compatibility test passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs deleted file mode 100644 index 8b85e5dfc46..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions15.fs +++ /dev/null @@ -1,22 +0,0 @@ -module YieldVsYieldBang - -let a1 = [ yield! 1..5 ] -let a2 = seq { yield! 1..5 } -let a3 = [| yield! 1..5 |] - -// let b1: int list = [ yield 1..5 ] // Would give type error - -let c1: int list = [ yield! 1..5 ] -let c2: int seq = seq { yield! 1..5 } -let c3: int array = [| yield! 1..5 |] - -let expected = [1; 2; 3; 4; 5] - -if a1 <> expected then failwithf $"a1 failed: got {a1}" -if List.ofSeq a2 <> expected then failwithf $"a2 failed: got {List.ofSeq a2}" -if List.ofArray a3 <> expected then failwithf $"a3 failed: got {List.ofArray a3}" -if c1 <> expected then failwithf $"c1 failed: got {c1}" -if List.ofSeq c2 <> expected then failwithf $"c2 failed: got {List.ofSeq c2}" -if List.ofArray c3 <> expected then failwithf $"c3 failed: got {List.ofArray c3}" - -printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs deleted file mode 100644 index 98466908767..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions16.fs +++ /dev/null @@ -1,39 +0,0 @@ -module MixedRangeVersionTest - -let a: int list = [ yield! 1..10 ] -let a1: int list = [ yield! [1..10] ] - -let b: int seq = seq { yield! seq { 1..10 } } - -let c: int list = [ if true then yield 1 else yield! 1..10] -let c1: int list = [ if true then yield! 1..10] - -let d: int list = [if true then yield 1 else yield! [1..10]] -let d1 : int list = [if true then yield! [1..10]] - -let e: int seq = seq { if true then yield 1 else yield! seq { 1..10 } } -let e1: int seq = seq { if true then yield! seq { 1..10 } } - -let f: int list = [ match true with | true -> yield 1 | false -> yield! 1..10] - -let f1: int list = [ match true with | true -> yield 1 | false -> yield! [1..10]] - -let g: int seq = seq { match true with | true -> yield 1 | false -> yield! seq { 1..10 } } - -let expected = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] -let expectedOne = [1] - -if a <> expected then failwithf $"a failed: got {a}" -if a1 <> expected then failwithf $"a1 failed: got {a1}" -if List.ofSeq b <> expected then failwithf $"b failed: got {List.ofSeq b}" -if c <> expectedOne then failwithf $"c failed: got {c}" -if c1 <> expected then failwithf $"c1 failed: got {c1}" -if d <> expectedOne then failwithf $"d failed: got {d}" -if d1 <> expected then failwithf $"d1 failed: got {d1}" -if List.ofSeq e <> expectedOne then failwithf $"e failed: got {List.ofSeq e}" -if List.ofSeq e1 <> expected then failwithf $"e1 failed: got {List.ofSeq e1}" -if f <> expectedOne then failwithf $"f failed: got {f}" -if f1 <> expectedOne then failwithf $"f1 failed: got {f1}" -if List.ofSeq g <> expectedOne then failwithf $"g failed: got {List.ofSeq g}" - -printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs deleted file mode 100644 index 39fbaa196b8..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions17.fs +++ /dev/null @@ -1,55 +0,0 @@ -module MixedRangeVersionTest - -let f p q r = - [ - if p then - if q then 3 - if p then - if q then - if r then 3 - ] - -let f1 p q r = - [ - if p then - if q then yield! 1..10 - if p then - if q then - if r then yield! 1..10 - ] - -let f2 x (|P|_|) (|Q|_|) (|R|_|) = - [ - match x with - | P _ -> 3 - | _ -> () - - match x with - | P y -> - match y with - | Q z -> - match z with - | R -> 3 - | _ -> () - | _ -> () - | _ -> () - ] - -let f3 x (|P|_|) (|Q|_|) (|R|_|) = - [ - match x with - | P _ -> yield! 1..10 - | _ -> () - - match x with - | P y -> - match y with - | Q z -> - match z with - | R -> yield! 1..10 - | _ -> () - | _ -> () - | _ -> () - ] - -printfn "yield vs yield! tests passed!" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs deleted file mode 100644 index c05b2893c6a..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions18.fs +++ /dev/null @@ -1,8 +0,0 @@ -module CharRangesTest - -let test = seq { 'a'..'c'; 'Z' } |> Seq.toArray -let expected = [| 'a'; 'b'; 'c'; 'Z' |] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs deleted file mode 100644 index 4e7863d73fc..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions19.fs +++ /dev/null @@ -1,9 +0,0 @@ -module BigIntRangesTest -open System.Numerics - -let test = [ 0I..2I..6I; 10I ] -let expected = [0I;2I;4I;6I;10I] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs deleted file mode 100644 index 928797117ea..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/SequenceExpressions20.fs +++ /dev/null @@ -1,8 +0,0 @@ -module EmptyRangesTest - -let test = [ 0; 1..0; 2..2; 3..1; 4 ] -let expected = [0;2;4] - -if test <> expected then failwith $"test failed: got {test}" - -printfn "All list tests passed!" From 33776520aa6b2bb4aaa92c43af694496f25135c8 Mon Sep 17 00:00:00 2001 From: XperiAndri Date: Thu, 15 Oct 2026 03:53:15 +0100 Subject: [PATCH 5/5] Shrink the mixed range change and fix the range lowering it exposed Review asked for a smaller diff without code comments. The intent now sits in names (BareRangeExpr, RangeAsSingleValue, mkRangeSplice, rewriteYieldFromRange, rejectRangeOperand, SimpleSemicolonSequenceWithRanges); one rejectRangeOperand replaces the recovery arms for yield and return of a range; the tests are four compilations instead of a fixture per scenario. Fixes found on the way: - a unativeint range with a single element was treated as empty (EmptyRange), which also dropped the element of [ 2un..2un ] and skipped for _ in 2un..2un on main - convertSimpleReturnToExpr declines BindReturn for a returned range and stays free of diagnostics, so FS3925 is reported once by the return arm - the counted loop reads a mutable range operand before the later operands run and no longer counts extra elements for a nativeint range with the smallest step - a then sequence is not split into the elements of a flat literal - query { return 1..3 } reports FS3144 only Co-Authored-By: Claude Sonnet 5.5 --- .../.FSharp.Compiler.Service/11.0.200.md | 1 + .../CheckArrayOrListComputedExpressions.fs | 9 +- .../CheckComputationExpressions.fs | 18 +- .../Expressions/CheckExpressionsOps.fs | 79 +- .../Expressions/CheckSequenceExpressions.fs | 8 +- .../Optimize/LowerComputedCollections.fs | 2 - .../TypedTree/TypedTreeOps.Transforms.fs | 44 +- .../MixedSequenceExpressionTests.fs | 699 +++++------------- 8 files changed, 233 insertions(+), 627 deletions(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md index d4fc180ecb5..21a81956797 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.200.md @@ -12,6 +12,7 @@ * Fix `NativePtr.stackalloc` used inside the `with` handler, filter or `finally` block of a `try` expression producing an assembly that throws `InvalidProgramException` at method load. Direct or mandatory-inline allocations are now reported at compile time as error FS3924; calls to non-inline allocation helpers, including handler-local helpers, remain legal and retain their method boundary during inlining and let-binding elimination. ([Issue #20295](https://github.com/dotnet/fsharp/issues/20295), [PR #20491](https://github.com/dotnet/fsharp/pull/20491)) * Preserve SRTP code generation when extension constraint solutions are disabled. ([PR #20773](https://github.com/dotnet/fsharp/pull/20773)) * `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 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` 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)) diff --git a/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs b/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs index 2a34fdda72e..68537f7f8f1 100644 --- a/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpressions.fs @@ -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 @@ -99,12 +98,8 @@ let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallT | _ -> SynExpr.ArrayOrList(isArray, elems, m) TcExprUndelayed cenv overallTy env tpenv replacementExpr - | _ -> - - let comp = - match comp with - | SimpleSemicolonSequenceWithRanges cenv valuesAndRanges -> valuesAndRanges - | _ -> comp + | SimpleSemicolonSequenceWithRanges cenv comp + | comp -> let genCollElemTy = NewInferenceType g diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index d71bbc23a4d..44b1d336786 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -2447,27 +2447,13 @@ let rec TryTranslateComputationExpression Some(translatedCtxt returnFromCall) - // A range is not yielded or returned as one value (FS-1031). Recover 'yield e1..e2' as 'yield! e1..e2' when the - // builder can splice; otherwise 'Yield' or 'Return' takes an argument of any type, so no type error follows. - | SynExpr.YieldOrReturn(((isYield, _) as flags), (BareRangeExpr _ as synRange), mFull, ({ YieldOrReturnKeyword = m } as trivia)) when - cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions - -> - errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synRange.Range)) - - let recovery = - if isYield && hasBuilderMethod ceenv m "YieldFrom" then - mkRangeSplice synRange - else - SynExpr.YieldOrReturn(flags, arbExpr ("rangeAsSingleValue", synRange.Range), mFull, trivia) - - TryTranslateComputationExpression ceenv firstTry q varSpace recovery translatedCtxt - | SynExpr.YieldOrReturn((isYield, _), synYieldOrReturnExpr, mFull, { YieldOrReturnKeyword = m }) -> let methName = (if isYield then "Yield" else "Return") if ceenv.isQuery && not isYield then error (Error(FSComp.SR.tcReturnMayNotBeUsedInQueries (), m)) + let synYieldOrReturnExpr = rejectRangeOperand cenv synYieldOrReturnExpr requireBuilderMethod methName ceenv m m let yieldOrReturnCall = @@ -2765,6 +2751,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) diff --git a/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs b/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs index 3c4307a6255..49d4e1abf05 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressionsOps.fs @@ -182,30 +182,33 @@ let RewriteRangeExpr synExpr = Some otherExpr | _ -> None -/// A closed range 'e1..e2' or 'e1..e2..e3' that is not parenthesized, as a call to '(..)' or '(.. ..)'. [] let (|BareRangeExpr|_|) synExpr = + RewriteRangeExpr synExpr |> ValueOption.ofOption + +let (|RangeAsSingleValue|_|) (cenv: TcFileState) synExpr = match synExpr with - | SynExpr.IndexRange _ -> - match RewriteRangeExpr synExpr with - | Some rangeExpr -> ValueSome rangeExpr - | None -> ValueNone - | _ -> ValueNone + | BareRangeExpr _ -> cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions + | _ -> false -/// A range in element position of a list, array, sequence or computation expression -/// splices its elements, as 'yield! e1..e2' does (FS-1031). let mkRangeSplice (synRange: SynExpr) = let m = synRange.Range SynExpr.YieldOrReturnFrom((true, false), synRange, m, { YieldOrReturnFromKeyword = m }) -/// The source of 'yield! e1..e2' is '(..) e1 e2' (FS-1031). let rewriteYieldFromRange (cenv: TcFileState) synExpr = match synExpr with - | BareRangeExpr rangeExpr -> + | BareRangeExpr rangeCall -> checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions synExpr.Range - rangeExpr + 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 = @@ -273,35 +276,37 @@ let rec TryGetSimpleSemicolonSequenceOfComprehension expr acc cenv acceptDepreca let (|SimpleSemicolonSequence|_|) cenv acceptDeprecated cexpr = TryGetSimpleSemicolonSequenceOfComprehension cexpr [] cenv acceptDeprecated -/// A flat list or array of values with at least one range, such as '[ 0; 1..3; 10 ]' (FS-1031), -/// as '[ yield 0; 1..3; yield 10 ]': every value stays an element, as in a list literal. [] let (|SimpleSemicolonSequenceWithRanges|_|) cenv cexpr = - let yieldElement (e: SynExpr) = - SynExpr.YieldOrReturn((true, false), e, e.Range, { YieldOrReturnKeyword = e.Range }) - - let rec collectReversed expr acc hasRange = + 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 = BareRangeExpr _ as e1; expr2 = e2) -> collectReversed e2 (e1 :: acc) true - | SynExpr.Sequential(isTrueSeq = true; expr1 = e1; expr2 = e2) when IsSimpleSemicolonSequenceElement e1 cenv false -> - collectReversed e2 (yieldElement e1 :: acc) hasRange - | BareRangeExpr _ -> ValueSome(expr :: acc) - | _ when hasRange && IsSimpleSemicolonSequenceElement expr cenv false -> ValueSome(yieldElement expr :: acc) - | _ -> ValueNone - - match collectReversed cexpr [] false with - | ValueSome(last :: before) -> - let mkSequential (tail: SynExpr) (e: SynExpr) = - SynExpr.Sequential( - DebugPointAtSequential.SuppressNeither, - true, - e, - tail, - unionRanges e.Range tail.Range, - SynExprSequentialTrivia.Zero - ) - - ValueSome(List.fold mkSequential last before) + | 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) = diff --git a/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs b/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs index 2a2de2780e8..a1a96d19faf 100644 --- a/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckSequenceExpressions.fs @@ -7,7 +7,6 @@ open FSharp.Compiler.CheckBasics open FSharp.Compiler.CheckExpressions open FSharp.Compiler.CheckExpressionsOps open FSharp.Compiler.ConstraintSolver -open FSharp.Compiler.Features open FSharp.Compiler.NameResolution open FSharp.Compiler.PatternMatchCompilation open FSharp.Compiler.Syntax @@ -405,12 +404,9 @@ let TcSequenceExpression (cenv: TcFileState) env tpenv comp (overallTy: OverallT Some(resultExpr, tpenv) - // A range is not yielded or returned as one value (FS-1031); recover as 'yield! e1..e2'. - | SynExpr.YieldOrReturn(flags = (isYield, _); expr = BareRangeExpr _ as synRange; range = m) when - g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions - -> + | SynExpr.YieldOrReturn(flags = (isYield, _); expr = RangeAsSingleValue cenv as synRange; range = m) -> if isYield then - errorR (Error(FSComp.SR.tcRangeIsNotASingleValue (), synRange.Range)) + rejectRangeOperand cenv synRange |> ignore else errorR (Error(FSComp.SR.tcSeqResultsUseYield (), m)) diff --git a/src/Compiler/Optimize/LowerComputedCollections.fs b/src/Compiler/Optimize/LowerComputedCollections.fs index a8080797fc1..4f5634232be 100644 --- a/src/Compiler/Optimize/LowerComputedCollections.fs +++ b/src/Compiler/Optimize/LowerComputedCollections.fs @@ -199,8 +199,6 @@ let LowerComputedListOrArraySeqExpr tcVal g amap m collectorTy overallSeqExpr = Result.Ok (flag, exprR) | Result.Error msg -> Result.Error msg - // yield! start..step..finish over an integral type: a counted loop adding each element, - // instead of AddMany over the range's enumerator. | IntegralRange g (rangeTy, (start, step, finish)) when not isUninteresting && g.langVersion.SupportsFeature LanguageFeature.LowerIntegralRangesToFastLoops diff --git a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs index 7ee5f290b23..542572ef77f 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Transforms.fs @@ -1468,7 +1468,7 @@ module internal TupleCompilation = start <= 0xffffffffUL && step <= 0xffffffffUL && finish <= 0xffffffffUL - && finish <= start + && finish < start -> ValueSome EmptyRange @@ -1520,12 +1520,6 @@ module internal TupleCompilation = ValueSome(Const.UInt64((uint64 finish - uint64 start) / (uint64 Int64.MaxValue + 1UL) + 1UL)) | Expr.Const(value = Const.Int64 start), Expr.Const(value = Const.Int64 Int64.MinValue), Expr.Const(value = Const.Int64 finish) -> ValueSome(Const.UInt64((uint64 start - uint64 finish) / (uint64 Int64.MaxValue + 1UL) + 1UL)) - | Expr.Const(value = Const.IntPtr start), Expr.Const(value = Const.IntPtr Int64.MinValue), Expr.Const(value = Const.IntPtr finish) when - start <= finish - -> - ValueSome(Const.UIntPtr((uint64 start - uint64 finish) / (uint64 Int64.MaxValue + 1UL) + 1UL)) - | Expr.Const(value = Const.IntPtr start), Expr.Const(value = Const.IntPtr Int64.MinValue), Expr.Const(value = Const.IntPtr finish) -> - ValueSome(Const.UIntPtr((uint64 start - uint64 finish) / (uint64 Int64.MaxValue + 1UL) + 1UL)) | Expr.Const(value = Const.Int64 start), Expr.Const(value = Const.Int64 step), Expr.Const(value = Const.Int64 finish) when start <= finish @@ -1998,34 +1992,14 @@ module internal TupleCompilation = (buildLoop: Count -> ((Idx -> Elem -> Body) -> Loop) -> Expr) = let inline mkLetBindingsIfNeeded f = - match start, step, finish with - | (Expr.Const _ | Expr.Val _), (Expr.Const _ | Expr.Val _), (Expr.Const _ | Expr.Val _) -> f start step finish - - | (Expr.Const _ | Expr.Val _), (Expr.Const _ | Expr.Val _), _ -> - mkCompGenLetIn finish.Range (nameof finish) rangeTy finish (fun (_, finish) -> f start step finish) - - | _, (Expr.Const _ | Expr.Val _), (Expr.Const _ | Expr.Val _) -> - mkCompGenLetIn start.Range (nameof start) rangeTy start (fun (_, start) -> f start step finish) - - | (Expr.Const _ | Expr.Val _), _, (Expr.Const _ | Expr.Val _) -> - mkCompGenLetIn step.Range (nameof step) rangeTy step (fun (_, step) -> f start step finish) - - | _, (Expr.Const _ | Expr.Val _), _ -> - mkCompGenLetIn start.Range (nameof start) rangeTy start (fun (_, start) -> - mkCompGenLetIn finish.Range (nameof finish) rangeTy finish (fun (_, finish) -> f start step finish)) - - | (Expr.Const _ | Expr.Val _), _, _ -> - mkCompGenLetIn step.Range (nameof step) rangeTy step (fun (_, step) -> - mkCompGenLetIn finish.Range (nameof finish) rangeTy finish (fun (_, finish) -> f start step finish)) - - | _, _, (Expr.Const _ | Expr.Val _) -> - mkCompGenLetIn start.Range (nameof start) rangeTy start (fun (_, start) -> - mkCompGenLetIn step.Range (nameof step) rangeTy step (fun (_, step) -> f start step finish)) - - | _, _, _ -> - mkCompGenLetIn start.Range (nameof start) rangeTy start (fun (_, start) -> - mkCompGenLetIn step.Range (nameof step) rangeTy step (fun (_, step) -> - mkCompGenLetIn finish.Range (nameof finish) rangeTy finish (fun (_, finish) -> f start step finish))) + let bindIfNeeded name operand k = + match operand with + | Expr.Const _ -> k operand + | Expr.Val(valRef, _, _) when not valRef.IsMutable -> k operand + | _ -> mkCompGenLetIn operand.Range name rangeTy operand (fun (_, bound) -> k bound) + + bindIfNeeded (nameof start) start (fun start -> + bindIfNeeded (nameof step) step (fun step -> bindIfNeeded (nameof finish) finish (fun finish -> f start step finish))) mkLetBindingsIfNeeded (fun start step finish -> /// Start at 0 and count up through count - 1. diff --git a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs index a7c29ff761f..245becb8e4c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/SequenceExpressions/MixedSequenceExpressionTests.fs @@ -1,293 +1,29 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. -namespace Language +module Language.MixedSequenceExpressionTests open Xunit open FSharp.Test.Compiler -// FS-1031: ranges mixed with values in list, array, sequence and computation expressions. -module MixedSequenceExpressionTests = +let private diagnostic kind code message line col endCol = + (kind code, Line line, Col col, Line line, Col endCol, message) - let private runWithPreview compilation = - compilation - |> asExe - |> withLangVersionPreview - |> withOptions [ "--nowarn:988" ] - |> compileAndRun - |> shouldSucceed - - let private typecheckWithPreview compilation = - compilation |> asLibrary |> withLangVersionPreview |> typecheck - - let private featureNotAvailable = - "Feature 'Allow mixed ranges and values in sequence expressions, e.g. seq { 1..10; 20 }' is not available in F# 11.0. Please use language version 'PREVIEW' or greater." - - let private rangeIsNotASingleValue = +let private rangeAsValue = + diagnostic Error 3925 "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." - [] - let ``Ranges mixed with values give the spliced elements`` () = - FSharp - """ -module MixedRanges - -let check name expected actual = - if actual <> expected then failwith $"{name}: {actual}" - -check "list: value, range" [ -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 ] [ -3; 1..10 ] -check "list: value, range, value" [ -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 ] [ -3; 1..10; 19 ] -check "list: two ranges, value" [ 1; 2; 3; 5; 6; 7; 10 ] [ 1..3; 5..7; 10 ] -check "list: stepped range" [ 0; 2; 4; 6; 8; 10; 15 ] [ 0; 2..2..10; 15 ] -check "list: empty and one-element ranges" [ 0; 2; 4 ] [ 0; 1..0; 2..2; 3..1; 4 ] -check "list: bigint" [ 0I; 2I; 4I; 6I; 10I ] [ 0I..2I..6I; 10I ] - -check "array: value, range" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |] [| -3; 1..10 |] -check "array: value, range, value" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] [| -3; 1..10; 19 |] -check "array: two ranges, value" [| 1; 2; 3; 5; 6; 7; 10 |] [| 1..3; 5..7; 10 |] -check "array: stepped range" [| 0; 2; 4; 6; 8; 10; 15 |] [| 0; 2..2..10; 15 |] -check "array: empty range" [| 1; 10 |] [| 1; 5..4; 10 |] -check "array: one-element range" [| 0; 5; 10 |] [| 0; 5..5; 10 |] - -check "seq: range, value" [ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 ] (List.ofSeq (seq { 1..10; 19 })) -check "seq: value, two ranges" [ -3; 1; 2; 3; 4; 5; 10; 11; 12 ] (List.ofSeq (seq { -3; 1..5; 10..12 })) -check "seq: stepped range" [ 0; 2; 4; 6; 8; 10; 15 ] (List.ofSeq (seq { 0; 2..2..10; 15 })) -check "seq: char" [| 'a'; 'b'; 'c'; 'Z' |] (Seq.toArray (seq { 'a'..'c'; 'Z' })) - -type ArrayBuilder() = - member _.Yield(x) = [| x |] - member _.YieldFrom(xs: seq<_>) = Seq.toArray xs - member _.Zero() = [||] - member _.Combine(a: int array, b: unit -> int array) = Array.append a (b ()) - member _.Delay(f) = f - member _.Run(f) = f () - -let array = ArrayBuilder() - -check "builder: value, range, value" [| -3; 1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 19 |] (array { -3; 1..10; 19 }) -check "builder: two ranges, value" [| 1; 2; 3; 5; 6; 7; 10 |] (array { 1..3; 5..7; 10 }) -check "builder: stepped range" [| 0; 2; 4; 6; 8; 10; 15 |] (array { 0; 2..2..10; 15 }) - -let annotatedList: int list = [ 1..5; 10 ] -let annotatedArray: int array = [| 1..5; 10 |] -let annotatedSeq: seq = seq { 1..5; 10 } - -check "annotated list" [ 1; 2; 3; 4; 5; 10 ] annotatedList -check "annotated array" [| 1; 2; 3; 4; 5; 10 |] annotatedArray -check "annotated seq" [ 1; 2; 3; 4; 5; 10 ] (List.ofSeq annotatedSeq) - -let inline mixedRange start finish extra = [ start..finish; extra ] - -check "inline: int" [ 1; 2; 3; 4; 5; 10 ] (mixedRange 1 5 10) -check "inline: float" [ 1.0; 2.0; 3.0; 4.0; 5.0; 10.0 ] (mixedRange 1.0 5.0 10.0) -check "inline: char" [ 'a'; 'b'; 'c'; 'd'; 'e'; 'z' ] (mixedRange 'a' 'e' 'z') -""" - |> runWithPreview - - [] - [] - [] - let ``Elements on separate lines mix values and ranges`` (first: string) (last: string) = - FSharp( - """ -module SeparateLines - -let a = seq { - 0 - FIRST - 10 - 11..15 - 20 - LAST -} - -let b = [ - 0 - FIRST - 10 - 11..15 - 20 - LAST -] - -let c = [| - 0 - FIRST - 10 - 11..15 - 20 - LAST -|] - -let d = seq { - 0 - yield! [FIRST] - 10 - yield! [11..15] - 20 - yield! [LAST] -} - -let e = [ - 0 - yield! [FIRST] - 10 - yield! [11..15] - 20 - yield! [LAST] -] - -let f = [| - 0 - yield! [FIRST] - 10 - yield! [11..15] - 20 - yield! [LAST] -|] - -if List.ofSeq a <> List.ofSeq d then failwith $"seq: {List.ofSeq a}" -if b <> e then failwith $"list: {b}" -if c <> f then failwith $"array: {c}" -""" - .Replace("FIRST", first) - .Replace("LAST", last) - ) - |> runWithPreview - - [] - let ``yield! takes a range and gives the elements of the same collection`` () = - FSharp - """ -module YieldBang - -let check name expected actual = - if actual <> expected then failwith $"{name}: {actual}" - -let oneToFive = [ 1; 2; 3; 4; 5 ] -let oneToTen = [ 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 ] - -check "list: yield! range" [ 1..10 ] [ yield! 1..10 ] -check "list: yield! list" (List.ofSeq (seq { 1..10 })) [ yield! [ 1..10 ] ] -check "seq: yield! seq" [| 1..10 |] (Array.ofSeq (seq { yield! seq { 1..10 } })) - -check "list: yield! range, value, yield! list" [ 1..10; 19; yield! 20..30 ] [ yield! 1..10; 19; yield! [ 20..30 ] ] -check "list: yield! list, value, yield! range" (List.ofSeq (seq { yield! 1..10; 19; yield! 20..30 })) [ yield! [ 1..10 ]; 19; yield! 20..30 ] -check "array: yield! ranges" (Array.ofSeq (seq { yield! seq { 1..10 }; 19; yield! seq { 20..30 } })) [| yield! 1..10; 19; yield! 20..30 |] - -check "seq: range as yield! seq" (List.ofSeq (seq { yield! seq { 1..10 }; 19 })) (List.ofSeq (seq { 1..10; 19 })) -check "list: range as yield! list" [ -3; yield! [ 1..10 ] ] [ -3; 1..10 ] -check "array: range as yield! array" [| -3; yield! [| 1..10 |]; 19 |] [| -3; 1..10; 19 |] - -check "seq: yield and yield! seq" [ 1; 2; 3; 4; 5; 10; 15; 17; 19 ] (List.ofSeq (seq { yield 1; yield! seq { 2..5 }; yield 10; yield! seq { 15..2..20 } })) -check "seq: yield and yield! list, array" [ 1; 2; 3; 4; 5; 10; 15; 17; 19 ] (List.ofSeq (seq { yield 1; yield! [ 2..5 ]; yield 10; yield! [| 15..2..20 |] })) - -let attributes = - [| - if true then - yield "attr1" - yield! [ "attr2"; "attr3" ] - yield! [ "attr4" ] - |] - -check "explicit yields without ranges" [| "attr1"; "attr2"; "attr3"; "attr4" |] attributes - -check "yield! 1..5: list" oneToFive [ yield! 1..5 ] -check "yield! 1..5: seq" oneToFive (List.ofSeq (seq { yield! 1..5 })) -check "yield! 1..5: array" oneToFive (List.ofArray [| yield! 1..5 |]) - -let annotatedList: int list = [ yield! 1..5 ] -let annotatedSeq: int seq = seq { yield! 1..5 } -let annotatedArray: int array = [| yield! 1..5 |] - -check "annotated list" oneToFive annotatedList -check "annotated seq" oneToFive (List.ofSeq annotatedSeq) -check "annotated array" oneToFive (List.ofArray annotatedArray) - -let a: int list = [ yield! 1..10 ] -let a1: int list = [ yield! [ 1..10 ] ] -let b: int seq = seq { yield! seq { 1..10 } } -let c: int list = [ if true then yield 1 else yield! 1..10 ] -let c1: int list = [ if true then yield! 1..10 ] -let d: int list = [ if true then yield 1 else yield! [ 1..10 ] ] -let d1: int list = [ if true then yield! [ 1..10 ] ] -let e: int seq = seq { if true then yield 1 else yield! seq { 1..10 } } -let e1: int seq = seq { if true then yield! seq { 1..10 } } -let f: int list = [ match true with | true -> yield 1 | false -> yield! 1..10 ] -let f1: int list = [ match true with | true -> yield 1 | false -> yield! [ 1..10 ] ] -let g: int seq = seq { match true with | true -> yield 1 | false -> yield! seq { 1..10 } } - -check "a" oneToTen a -check "a1" oneToTen a1 -check "b" oneToTen (List.ofSeq b) -check "c" [ 1 ] c -check "c1" oneToTen c1 -check "d" [ 1 ] d -check "d1" oneToTen d1 -check "e" [ 1 ] (List.ofSeq e) -check "e1" oneToTen (List.ofSeq e1) -check "f" [ 1 ] f -check "f1" [ 1 ] f1 -check "g" [ 1 ] (List.ofSeq g) - -let nestedIf p q r = - [ - if p then - if q then 3 - if p then - if q then - if r then 3 - ] - -let nestedIfWithRange p q r = - [ - if p then - if q then yield! 1..10 - if p then - if q then - if r then yield! 1..10 - ] - -let nestedMatch x (|P|_|) (|Q|_|) (|R|_|) = - [ - match x with - | P _ -> 3 - | _ -> () - - match x with - | P y -> - match y with - | Q z -> - match z with - | R -> 3 - | _ -> () - | _ -> () - | _ -> () - ] - -let nestedMatchWithRange x (|P|_|) (|Q|_|) (|R|_|) = - [ - match x with - | P _ -> yield! 1..10 - | _ -> () +let private featureUnavailable = + diagnostic Error 3350 + "Feature 'Allow mixed ranges and values in sequence expressions, e.g. seq { 1..10; 20 }' is not available in F# 11.0. Please use language version 'PREVIEW' or greater." - match x with - | P y -> - match y with - | Q z -> - match z with - | R -> yield! 1..10 - | _ -> () - | _ -> () - | _ -> () - ] -""" - |> runWithPreview +let private indexerSyntax = + diagnostic Error 751 "Incomplete expression or invalid use of indexer syntax" - [] - let ``A range splices in every element position`` () = - FSharp - """ -module Positions +[] +let ``Ranges mixed with values splice their elements`` () = + FSharp + """ +module Runtime let check name expected actual = if actual <> expected then failwith $"{name}: {actual}" @@ -295,278 +31,191 @@ let check name expected actual = type ListBuilder() = member _.Yield(x) = [ x ] member _.YieldFrom(xs: seq<_>) = List.ofSeq xs - member _.Zero() = [] member _.Combine(a, b) = a @ b member _.Delay(f: unit -> _ list) = f () member _.Bind(x: _ option, f) = match x with Some v -> f v | None -> [] let list = ListBuilder() -let disposable = { new System.IDisposable with member _.Dispose() = () } +check "value, range, value" [ -3; 1; 2; 3; 19 ] [ -3; 1..3; 19 ] +check "two ranges" [ 1; 2; 5; 6; 10 ] [ 1..2; 5..6; 10 ] +check "stepped" [ 0; 2; 4; 15 ] [ 0; 2..2..4; 15 ] +check "negative step" [ 0; 3; 2; 1 ] [ 0; 3 .. -1 .. 1 ] +let down start = [ 0; start .. -1 .. 1 ] +check "negative step from a runtime start" [ 0; 3; 2; 1 ] (down 3) +check "empty and singleton" [ 0; 2; 4 ] [ 0; 1..0; 2..2; 3..1; 4 ] +check "separate lines" [ 0; 1; 2; 10; 11; 12 ] [ + 0 + 1..2 + 10 + 11..12 ] +check "array" [ 0; 1; 2; 9 ] (List.ofArray [| 0; 1..2; 9 |]) +check "seq" [ 0; 1; 2; 9 ] (List.ofSeq (seq { 0; 1..2; 9 })) +check "seq with a stepped tail" [ 0; 2; 4 ] (List.ofSeq (seq { 0; 2..2..4 })) +check "builder" [ 0; 1; 2; 9 ] (list { 0; 1..2; 9 }) + +check "char" [ 'a'; 'b'; 'Z' ] [ 'a'..'b'; 'Z' ] +check "bigint" [ 0I; 2I; 10I ] [ 0I..2I..2I; 10I ] +check "int64" [ 0L; 1L; 2L ] [ 0L; 1L..2L ] +check "byte at the limit" [ 0uy; 254uy; 255uy ] [ 0uy; 254uy..255uy ] +check "uint64 at the limit" [ 0UL; System.UInt64.MaxValue ] [ 0UL; System.UInt64.MaxValue..System.UInt64.MaxValue ] +check "singleton nativeint" [ 0n; 2n ] [ 0n; 2n..2n ] +check "singleton unativeint" [ 0un; 2un ] [ 0un; 2un..2un ] +check "singleton unativeint alone" [ 2un ] [ 2un..2un ] +check "singleton unativeint yield!" [ 0un; 2un ] [ yield 0un; yield! (..) 2un 2un ] +let mutable visited = 0 +for _ in 2un..2un do visited <- visited + 1 +check "singleton unativeint for loop" 1 visited +check "zero step" true (try ignore [ 0; 1..0..5 ]; false with :? System.ArgumentException -> true) +check "nativeint with the smallest step" [ 7n ] [ 7n; -1n .. -9223372036854775808n .. 0n ] +let mutable first = 1 +let bump () = first <- 100; 3 +check "operands are read in order" [ 0; 1; 2; 3 ] [ 0; first..bump () ] + +let inline mixed a b c = [ a..b; c ] +let flat c = [ 1; 2..3; c ] +let flatArray c = [| 0..1; c |] +check "annotated" [ 1; 2; 5 ] ([ 1..2; 5 ]: int list) +check "inline" [ 1.0; 2.0; 5.0 ] (mixed 1.0 2.0 5.0) +check "inline int" [ 1; 2; 5 ] (mixed 1 2 5) +check "flat list keeps the value" [ 1; 2; 3; 5 ] (flat 5) +check "flat array keeps the value" [| 0; 1; 7 |] (flatArray 7) + +let counter = ref 0 check "if" [ 2; 3 ] [ if false then 1 else 2..3 ] check "match" [ 1; 2 ] [ match Some 2 with Some n -> 1..n | None -> 0 ] check "for" [ 1; 2; 2; 3 ] [ for x in [ 1; 2 ] do x..x + 1 ] -check "while" [ 0; 1; 1; 2 ] [ - let i = ref 0 - while i.Value < 2 do - i.Value..i.Value + 1 - i.Value <- i.Value + 1 ] +check "while" [ 0; 1; 1; 2 ] [ while counter.Value < 2 do counter.Value..counter.Value + 1; counter.Value <- counter.Value + 1 ] check "try/with" [ 1; 2 ] [ try 1..2 with _ -> () ] check "try/finally" [ 1; 2 ] [ try 1..2 finally () ] check "let" [ 1; 2; 3; 10 ] [ let lo = 1 in lo..3; 10 ] -check "use" [ 1; 2 ] [ use _d = disposable in 1..2 ] -check "seq" [ 0; 1; 2 ] (List.ofSeq (seq { 0; 1..2 })) -check "array" [| 0; 1; 2 |] [| 0; 1..2 |] -check "computation expression" [ 0; 1; 2; 9 ] (list { 0; 1..2; 9 }) +check "use" [ 1; 2 ] [ use _d = new System.IO.StringReader("") in 1..2 ] check "let!" [ 1; 2; 3 ] (list { let! n = Some 3 in 1..n }) -""" - |> runWithPreview - - [] - let ``A range neither counts as an explicit yield nor disables implicit yields`` () = - FSharp - """ -module ImplicitYields - -let xs = [ 0 ] -if [ yield! xs; 2..3; 4 ] <> [ 0; 2; 3; 4 ] then failwith "yield!" -if [ yield 1; 2..3 ] <> [ 1; 2; 3 ] then failwith "yield" -""" - |> runWithPreview - [] - let ``In a flat list or array every value stays an element`` () = - FSharp - """ -module FlatLiterals - -let withRange c = [ 1; 2..3; c ] -let withoutRange c = [ 1; c ] -if withRange 5 <> [ 1; 2; 3; 5 ] || withoutRange 5 <> [ 1; 5 ] then failwith "list" -let array c = [| 0..1; c |] -if array 7 <> [| 0; 1; 7 |] then failwith "array" -""" - |> runWithPreview - - [] - let ``A plain value next to a range keeps the explicit yield rule`` () = - FSharp - """ -module M - -let a = [ yield 1; 2..3; 4 ] -""" - |> typecheckWithPreview - |> shouldFail - |> withDiagnostics - [ (Warning 3221, - Line 4, - Col 26, - Line 4, - Col 27, - "This expression returns a value of type 'int' but is implicitly discarded. Consider using 'let' to bind the result to a name, e.g. 'let result = expression'. If you intended to use the expression as a value in the sequence then use an explicit 'yield'.") ] - - [] - let ``A range is not yielded or returned as one value`` () = - FSharp - """ -module M - -type B() = - member _.Yield(x) = [ x ] - member _.Return(x) = [ x ] - member _.Delay(f) = f () - -let b = B() -let a = [ yield 1..3 ] -let c = [ for x in [ 1 ] -> 1..3 ] -let d = b { yield 1..3 } -let e = b { return 1..3 } -let f = [ return 1..3 ] -let g = [ 1; (2..3) ] -""" - |> typecheckWithPreview - |> shouldFail - |> withDiagnostics - [ (Error 3925, Line 10, Col 17, Line 10, Col 21, rangeIsNotASingleValue) - (Error 3925, Line 11, Col 29, Line 11, Col 33, rangeIsNotASingleValue) - (Error 3925, Line 12, Col 19, Line 12, Col 23, rangeIsNotASingleValue) - (Error 3925, Line 13, Col 20, Line 13, Col 24, rangeIsNotASingleValue) - (Error 635, Line 14, Col 11, Line 14, Col 22, "In sequence expressions, results are generated using 'yield'") - (Error 751, Line 15, Col 15, Line 15, Col 19, "Incomplete expression or invalid use of indexer syntax") ] - - [] - let ``A yielded or returned range gives no type error for a builder that takes an element`` () = - FSharp - """ -module M - -type WithYieldFrom() = +check "yield! range" [ 1; 2; 3 ] [ yield! 1..3 ] +check "yield! range in seq" [ 1; 2; 3 ] (List.ofSeq (seq { yield! 1..3 })) +check "yield! range in array" [ 1; 2; 3 ] (List.ofArray [| yield! 1..3 |]) +check "yield! range, value, yield! list" [ 1; 2; 3; 9; 20; 21 ] [ yield! 1..3; 9; yield! [ 20..21 ] ] +check "yield! in if" [ 1; 2; 3 ] [ if false then yield 1 else yield! 1..3 ] +check "yield! in match" [ 1; 2; 3 ] [ match false with true -> yield 1 | false -> yield! 1..3 ] +check "after yield!" [ 0; 2; 3; 4 ] [ yield! [ 0 ]; 2..3; 4 ] +check "after yield" [ 1; 2; 3 ] [ yield 1; 2..3 ] + +check "quotation" (string <@ [ yield 0; yield! (..) 1 2 ] @>) (string <@ [ 0; 1..2 ] @>) + +module CustomRange = + let (..) (a: int) (b: int) = seq { a; b } + check "range operator in scope" [ 0; 1; 3 ] [ 0; 1..3 ] + +module TailPosition = + let calls = ResizeArray() + + type Builder() = + member _.YieldFrom(xs: seq) = calls.Add "YieldFrom"; List.ofSeq xs + member _.YieldFromFinal(xs: seq) = calls.Add "YieldFromFinal"; List.ofSeq xs + member _.Combine(a, b) = a @ b + member _.Delay(f: unit -> _ list) = f () + + let builder = Builder() + check "tail position elements" [ 1; 2; 3; 4 ] (builder { 1..2; 3..4 }) + check "tail position calls" [ "YieldFrom"; "YieldFromFinal" ] (List.ofSeq calls) +""" + |> asExe + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + +[] +let ``A range is not a single value and needs YieldFrom to splice`` () = + FSharp + """ +module Diagnostics + +type Builder() = member _.Yield(x: int) = [ x ] member _.YieldFrom(xs: seq) = List.ofSeq xs member _.Return(x: int) = [ x ] - member _.Delay(f: unit -> int list) = f () + member _.Bind(x: int, f: int -> int list) = f x + member _.BindReturn(x: int, f: int -> int) = [ f x ] type WithoutYieldFrom() = member _.Yield(x: int) = [ x ] - member _.Delay(f: unit -> int list) = f () - -let splicing = WithYieldFrom() -let single = WithoutYieldFrom() -let a = splicing { yield 1..3 } -let b = splicing { return 1..3 } -let c = single { yield 1..3 } -""" - |> typecheckWithPreview - |> shouldFail - |> withDiagnostics - [ (Error 3925, Line 16, Col 26, Line 16, Col 30, rangeIsNotASingleValue) - (Error 3925, Line 17, Col 27, Line 17, Col 31, rangeIsNotASingleValue) - (Error 3925, Line 18, Col 24, Line 18, Col 28, rangeIsNotASingleValue) ] - - [] - let ``A splice uses the range operator in scope`` () = - FSharp - """ -module CustomRange - -let (..) (a: int) (b: int) = seq { a; b } -if [ 0; 1..3 ] <> [ 0; 1; 3 ] then failwith "custom (..)" -""" - |> runWithPreview - - [] - let ``A builder without YieldFrom cannot splice a range`` () = - FSharp - """ -module M - -type B() = - member _.Yield(x) = [ x ] - member _.Combine(a, b) = a @ b - member _.Delay(f: unit -> _ list) = f () -let b = B() -let r = b { 1; 2..3 } -""" - |> typecheckWithPreview - |> shouldFail - |> withDiagnostics - [ (Error 708, - Line 10, - Col 16, - Line 10, - Col 20, - "This control construct may only be used if the computation expression builder defines a 'YieldFrom' method") ] - - [] - let ``A splice in tail position uses YieldFromFinal`` () = - FSharp - """ -module Final - -let mutable calls = [] - -type B() = +let builder = Builder() +let withoutYieldFrom = WithoutYieldFrom() +let a = [ yield 1..3 ] +let b = [ for x in [ 1 ] -> 1..3 ] +let c = [ return 1..3 ] +let d = [ 1; (2..3) ] +let e = builder { yield 1..3 } +let f = builder { return 1..3 } +let g = builder { let! x = 1 in return x..3 } +let h = withoutYieldFrom { yield 1..3 } +let i = withoutYieldFrom { 1..3 } +let j = [ yield 1; 2..3; 4 ] +let k = [ 1..2; 3 then 4 ] +let l = query { return 1..3 } +""" + |> asLibrary + |> withLangVersionPreview + |> typecheck + |> shouldFail + |> withDiagnostics + [ rangeAsValue 16 17 21 + rangeAsValue 17 29 33 + (Error 635, Line 18, Col 11, Line 18, Col 22, "In sequence expressions, results are generated using 'yield'") + indexerSyntax 19 15 19 + rangeAsValue 20 25 29 + rangeAsValue 21 26 30 + rangeAsValue 22 40 44 + rangeAsValue 23 34 38 + (Error 708, Line 24, Col 28, Line 24, Col 32, "This control construct may only be used if the computation expression builder defines a 'YieldFrom' method") + (Warning 3221, Line 25, Col 26, Line 25, Col 27, "This expression returns a value of type 'int' but is implicitly discarded. Consider using 'let' to bind the result to a name, e.g. 'let result = expression'. If you intended to use the expression as a value in the sequence then use an explicit 'yield'.") + (Error 743, Line 26, Col 17, Line 26, Col 25, "The expression form 'expr then expr' may only be used as part of an explicit object constructor") + (Warning 20, Line 26, Col 24, Line 26, Col 25, "The result of this expression has type 'int' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'.") + (Error 3144, Line 27, Col 17, Line 27, Col 23, "'return' and 'return!' may not be used in queries") ] + +[] +let ``Before the feature each new form reports FS3350`` () = + FSharp + """ +module Gated + +type Builder() = member _.Yield(x) = [ x ] - member _.YieldFrom(xs: seq) = - calls <- "YieldFrom" :: calls - List.ofSeq xs - member _.YieldFromFinal(xs: seq) = - calls <- "YieldFromFinal" :: calls - List.ofSeq xs + member _.YieldFrom(xs: seq<_>) = List.ofSeq xs member _.Combine(a, b) = a @ b member _.Delay(f: unit -> _ list) = f () -let b = B() -let r = b { 1..2; 3..4 } -if r <> [ 1; 2; 3; 4 ] || calls <> [ "YieldFromFinal"; "YieldFrom" ] then failwith $"{r} {calls}" -""" - |> runWithPreview - - [] - let ``Quotations show the splice as a range call`` () = - FSharp - """ -module Quoted - -open Microsoft.FSharp.Quotations -open Microsoft.FSharp.Quotations.Patterns - -let rec callsRange (e: Expr) = - match e with - | Call(_, mi, _) when mi.Name = "op_Range" -> true - | ExprShape.ShapeVar _ -> false - | ExprShape.ShapeLambda(_, body) -> callsRange body - | ExprShape.ShapeCombination(_, args) -> List.exists callsRange args - -if not (callsRange <@ [ 0; 1..2 ] @>) then failwith "op_Range" -""" - |> runWithPreview - - [] - let ``An integral splice in a list or array is a counted loop`` () = - FSharp - """ -module M +let builder = Builder() +let a = [ 0; 1..3 ] +let b = seq { 0; 1..3 } +let c = [| yield! 1..3 |] +let d = builder { 0; 1..3 } +let e = [ yield 1..3 ] +""" + |> asLibrary + |> withLangVersion11 + |> typecheck + |> shouldFail + |> withDiagnostics + [ featureUnavailable 11 14 18 + featureUnavailable 12 18 22 + featureUnavailable 13 19 23 + featureUnavailable 14 22 26 + indexerSyntax 15 17 21 ] + +[] +let ``An integral splice in a list or array is a counted loop`` () = + FSharp + """ +module Loops let list n = [ 0; 1..n ] let array n = [| 0; 1..2..n |] """ - |> asLibrary - |> withLangVersionPreview - |> compile - |> shouldSucceed - |> verifyILNotPresent [ "GetEnumerator"; "AddMany" ] - - [] - let ``A counted splice keeps the range semantics`` () = - FSharp - """ -module CountedSplices - -let check name expected actual = - if actual <> expected then failwith $"{name}: {actual}" - -let n = 3 -check "list" [ 0; 1; 2; 3; 9 ] [ 0; 1..n; 9 ] -check "array" [| 0; 3; 2; 1 |] [| 0; n .. -1 .. 1 |] -check "empty" [ 0 ] [ 0; n..1 ] -check "int64" [ 0L; 1L; 2L ] [ 0L; 1L..2L ] -check "byte" [ 0uy; 254uy; 255uy ] [ 0uy; 254uy..255uy ] -check "uint64" [ 0UL; System.UInt64.MaxValue ] [ 0UL; System.UInt64.MaxValue..System.UInt64.MaxValue ] -check "char" [ 'a'; 'b'; 'c' ] [ 'a'; 'b'..'c' ] -let threw = try ignore [ 0; 1..0..5 ]; false with :? System.ArgumentException -> true -if not threw then failwith "zero step" -""" - |> runWithPreview - - [] - let ``Before the feature each new form reports FS3350 once`` () = - FSharp - """ -module M - -type B() = - member _.Yield(x) = [ x ] - member _.YieldFrom(xs: seq<_>) = List.ofSeq xs - member _.Combine(a, b) = a @ b - member _.Delay(f: unit -> _ list) = f () - -let b = B() -let a = [ 0; 1..3 ] -let c = seq { 0; 1..3 } -let d = [| yield! 1..3 |] -let e = b { 0; 1..3 } -let f = [ yield 1..3 ] -""" - |> asLibrary - |> withLangVersion11 - |> typecheck - |> shouldFail - |> withDiagnostics - [ (Error 3350, Line 11, Col 14, Line 11, Col 18, featureNotAvailable) - (Error 3350, Line 12, Col 18, Line 12, Col 22, featureNotAvailable) - (Error 3350, Line 13, Col 19, Line 13, Col 23, featureNotAvailable) - (Error 3350, Line 14, Col 16, Line 14, Col 20, featureNotAvailable) - (Error 751, Line 15, Col 17, Line 15, Col 21, "Incomplete expression or invalid use of indexer syntax") ] + |> asLibrary + |> withLangVersionPreview + |> compile + |> shouldSucceed + |> verifyILNotPresent [ "GetEnumerator"; "AddMany"; "RangeInt32" ]