Skip to content

Allow mixing ranges and values to construct sequences #18670

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 59 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1799aaf
make attribute targets mismatches a warning and not an error.
edgarfgp Apr 23, 2025
55507e9
release notes
edgarfgp Apr 23, 2025
1738018
update tests
edgarfgp Apr 23, 2025
65f5bb6
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 24, 2025
0c97b9d
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 27, 2025
6f2b706
update baselines
edgarfgp Apr 29, 2025
e8f1bb0
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 29, 2025
75d8f5e
Update baselines
edgarfgp Apr 29, 2025
4f2e97e
Merge branch 'fix-attr-targets' of github.com:edgarfgp/fsharp into fi…
edgarfgp Apr 29, 2025
63be5d5
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 30, 2025
4248f2a
Move attribute form logic to an AP
edgarfgp Apr 30, 2025
cc96217
Merge branch 'main' into fix-attr-targets
edgarfgp May 1, 2025
e270b88
Merge branch 'main' into fix-attr-targets
edgarfgp May 1, 2025
e0cc65a
Merge branch 'main' into fix-attr-targets
edgarfgp May 2, 2025
1e29d58
Merge branch 'main' into fix-attr-targets
edgarfgp May 5, 2025
1470bf9
Merge branch 'main' into fix-attr-targets
edgarfgp May 6, 2025
8988215
Merge branch 'main' into fix-attr-targets
edgarfgp May 7, 2025
74712e8
Merge branch 'main' into fix-attr-targets
edgarfgp May 12, 2025
967c4a9
Merge branch 'main' of github.com:edgarfgp/fsharp
edgarfgp May 13, 2025
a30cef4
Merge branch 'dotnet:main' into main
edgarfgp May 22, 2025
5fa0480
Merge branch 'dotnet:main' into main
edgarfgp May 24, 2025
15e3d34
Merge branch 'dotnet:main' into main
edgarfgp May 29, 2025
b7ffcf8
Merge branch 'dotnet:main' into main
edgarfgp Jun 6, 2025
e465d63
Allow mixing ranges and values to construct sequences
edgarfgp Jun 6, 2025
7999b57
Merge branch 'main' into mixed-ranges-prototype
edgarfgp Jun 6, 2025
433e5da
Add AllowMixedRangesAndValuesInSeqExpressions
edgarfgp Jun 6, 2025
bfc4220
Use AllowMixedRangesAndValuesInSeqExpressions
edgarfgp Jun 6, 2025
c724685
update tests
edgarfgp Jun 6, 2025
b807f2a
Merge branch 'mixed-ranges-prototype' of github.com:edgarfgp/fsharp i…
edgarfgp Jun 6, 2025
8010986
reduce diff
edgarfgp Jun 6, 2025
64c4a05
improve tests
edgarfgp Jun 7, 2025
d505e46
release notes
edgarfgp Jun 7, 2025
77c0e7c
Allow mixing ranges and values to construct sequences in custom defin…
edgarfgp Jun 8, 2025
e535d47
more tests
edgarfgp Jun 9, 2025
e714b5f
Merge branch 'main' into mixed-ranges-prototype
edgarfgp Jun 9, 2025
cc695a8
Merge branch 'main' into mixed-ranges-prototype
edgarfgp Jun 10, 2025
f5f8007
Merge branch 'main' into mixed-ranges-prototype
edgarfgp Jun 10, 2025
a647112
more tests
edgarfgp Jun 11, 2025
a78f4d7
Update src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpr…
edgarfgp Jun 12, 2025
a4f5c16
Update src/Compiler/Checking/Expressions/CheckArrayOrListComputedExpr…
edgarfgp Jun 12, 2025
2b05d76
format code
edgarfgp Jun 12, 2025
8ceb9f6
containsRangeExpressions first and reuse
edgarfgp Jun 13, 2025
5f947fa
Move transformMixedListWithRangesToSeqExpr to CheckExpressionsOps
edgarfgp Jun 13, 2025
17cbb60
update baselines
edgarfgp Jun 13, 2025
aac9a69
Update src/Compiler/Checking/Expressions/CheckExpressionsOps.fs
edgarfgp Jun 13, 2025
4053f40
Update src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
edgarfgp Jun 13, 2025
461fc3f
Update src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
edgarfgp Jun 13, 2025
4de39c7
Update src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
edgarfgp Jun 13, 2025
a2dc4ee
Update src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
edgarfgp Jun 13, 2025
bc8a8f1
make things tail-recursive
edgarfgp Jun 13, 2025
ba9f733
yield! 1..10
edgarfgp Jun 13, 2025
b3e463e
Merge branch 'mixed-ranges-prototype' of github.com:edgarfgp/fsharp i…
edgarfgp Jun 13, 2025
79b2e66
yield! 1..10
edgarfgp Jun 13, 2025
d881686
format
edgarfgp Jun 14, 2025
43babe7
Update src/Compiler/Checking/Expressions/CheckExpressionsOps.fs
edgarfgp Jun 14, 2025
824a033
more test
edgarfgp Jun 14, 2025
2135780
more tests
edgarfgp Jun 16, 2025
297ba93
update tests
edgarfgp Jun 18, 2025
a9261e7
Merge branch 'main' into mixed-ranges-prototype
edgarfgp Jul 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### Added
* Add opt-in warning attribute not valid for union case with fields [PR #18532](https://github.com/dotnet/fsharp/pull/18532))
* Add support for `when 'T : Enum` library-only static optimization constraint. ([PR #18546](https://github.com/dotnet/fsharp/pull/18546))
* Allow mixing ranges and values to construct sequences. ([PR #18670](https://github.com/dotnet/fsharp/pull/18670))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
* Scoped Nowarn: added the #warnon compiler directive ([Language suggestion #278](https://github.com/fsharp/fslang-suggestions/issues/278), [RFC FS-1146 PR](https://github.com/fsharp/fslang-design/pull/782), [PR #18049](https://github.com/dotnet/fsharp/pull/18049))
* Allow `let!` and `use!` type annotations without requiring parentheses. ([PR #18508](https://github.com/dotnet/fsharp/pull/18508))
* Allow mixing ranges and values to construct sequences. ([PR #18670](https://github.com/dotnet/fsharp/pull/18670))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,43 @@ open FSharp.Compiler.CheckBasics
open FSharp.Compiler.ConstraintSolver
open FSharp.Compiler.CheckExpressionsOps
open FSharp.Compiler.CheckExpressions
open FSharp.Compiler.CheckSequenceExpressions
open FSharp.Compiler.NameResolution
open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.Features
open FSharp.Compiler.DiagnosticsLogger
open FSharp.Compiler.Syntax
open FSharp.Compiler.CheckSequenceExpressions

let private tcMixedSequencesWithRanges (cenv: TcFileState) env overallTy tpenv isArray elems m =
let g = cenv.g
let transformedBody = insertImplicitYieldsAndYieldBangs elems m

let genCollElemTy = NewInferenceType g
let genCollTy = (if isArray then mkArrayType else mkListTy) g genCollElemTy

TcPropagatingExprLeafThenConvert cenv overallTy genCollTy env m (fun () ->
let exprTy = mkSeqTy g genCollElemTy

let expr, tpenv' =
TcSequenceExpression cenv env tpenv transformedBody (MustEqual exprTy) m

let expr = mkCoerceIfNeeded g exprTy (tyOfExpr g expr) expr

let expr =
if g.compilingFSharpCore then
expr
else
mkCallSeq g m genCollElemTy expr

let expr = mkCoerceExpr (expr, exprTy, expr.Range, overallTy.Commit)

let expr =
if isArray then
mkCallSeqToArray g m genCollElemTy expr
else
mkCallSeqToList g m genCollElemTy expr

expr, tpenv')

let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv (isArray, comp) m =
let g = cenv.g
Expand Down Expand Up @@ -68,65 +99,113 @@ let TcArrayOrListComputedExpression (cenv: TcFileState) env (overallTy: OverallT
errorR (Deprecated(FSComp.SR.tcExpressionWithIfRequiresParenthesis (), m))
| _ -> ()

let replacementExpr =
if isArray then
// This are to improve parsing/processing speed for parser tables by converting to an array blob ASAP
let nelems = elems.Length

if
nelems > 0
&& List.forall
(function
| SynExpr.Const(SynConst.UInt16 _, _) -> true
| _ -> false)
elems
then
SynExpr.Const(
SynConst.UInt16s(
Array.ofList (
List.map
(function
| SynExpr.Const(SynConst.UInt16 x, _) -> x
| _ -> failwith "unreachable")
elems
)
),
m
)
elif
nelems > 0
&& List.forall
(function
| SynExpr.Const(SynConst.Byte _, _) -> true
| _ -> false)
elems
then
SynExpr.Const(
SynConst.Bytes(
Array.ofList (
List.map
(function
| SynExpr.Const(SynConst.Byte x, _) -> x
| _ -> failwith "unreachable")
elems
let containsRangeExpressions =
elems
|> List.exists (function
| SynExpr.IndexRange _ -> true
| _ -> false)

if
containsRangeExpressions
&& g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
then
tcMixedSequencesWithRanges cenv env overallTy tpenv isArray elems m
else
if containsRangeExpressions then
checkLanguageFeatureAndRecover cenv.g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions m

let replacementExpr =
// These are to improve parsing/processing speed for parser tables by converting to an array blob ASAP
if isArray then
let nelems = elems.Length

if
nelems > 0
&& List.forall
(function
| SynExpr.Const(SynConst.UInt16 _, _) -> true
| _ -> false)
elems
then
SynExpr.Const(
SynConst.UInt16s(
Array.ofList (
List.map
(function
| SynExpr.Const(SynConst.UInt16 x, _) -> x
| _ -> failwith "unreachable")
elems
)
),
SynByteStringKind.Regular,
m
),
m
)
else
)
elif
nelems > 0
&& List.forall
(function
| SynExpr.Const(SynConst.Byte _, _) -> true
| _ -> false)
elems
then
SynExpr.Const(
SynConst.Bytes(
Array.ofList (
List.map
(function
| SynExpr.Const(SynConst.Byte x, _) -> x
| _ -> failwith "unreachable")
elems
),
SynByteStringKind.Regular,
m
),
m
)
else
SynExpr.ArrayOrList(isArray, elems, m)
else if cenv.g.langVersion.SupportsFeature(LanguageFeature.ReallyLongLists) then
SynExpr.ArrayOrList(isArray, elems, m)
else if cenv.g.langVersion.SupportsFeature(LanguageFeature.ReallyLongLists) then
SynExpr.ArrayOrList(isArray, elems, m)
else
if elems.Length > 500 then
error (Error(FSComp.SR.tcListLiteralMaxSize (), m))
else
if elems.Length > 500 then
error (Error(FSComp.SR.tcListLiteralMaxSize (), m))

SynExpr.ArrayOrList(isArray, elems, m)
SynExpr.ArrayOrList(isArray, elems, m)

TcExprUndelayed cenv overallTy env tpenv replacementExpr
TcExprUndelayed cenv overallTy env tpenv replacementExpr
| _ ->
let containsRangeMixedWithYields =
let rec hasRangeAndYield expr hasRange hasYield cont =
match expr with
| SynExpr.IndexRange _ -> cont true hasYield
| SynExpr.YieldOrReturnFrom _ -> cont hasRange true
| SynExpr.Sequential(_, _, e1, e2, _, _) ->
hasRangeAndYield e1 hasRange hasYield (fun r1 y1 -> hasRangeAndYield e2 r1 y1 cont)
| _ -> cont hasRange hasYield

hasRangeAndYield comp false false (fun r y -> r && y)

// Transform mixed expressions with explicit yields to ensure all elements are properly yielded
let comp =
if
containsRangeMixedWithYields
&& g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
then
match comp with
| SynExpr.Sequential _ ->
// Extract the elements from the sequential expression
let rec getElems expr acc =
match expr with
| SynExpr.Sequential(_, true, e1, e2, _, _) -> getElems e2 (e1 :: acc)
| e -> List.rev (e :: acc)

let elems = getElems comp []
insertImplicitYieldsAndYieldBangs elems m
| _ -> comp
else if containsRangeMixedWithYields then
checkLanguageFeatureAndRecover g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions m
comp
else
comp

let genCollElemTy = NewInferenceType g

Expand Down
121 changes: 120 additions & 1 deletion src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,10 @@ let requireBuilderMethod methodName m1 cenv env ad builderTy m2 =
if isNil (TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env m1 ad methodName builderTy) then
error (Error(FSComp.SR.tcRequireBuilderMethod methodName, m2))

/// Checks if a builder method exists (without reporting an error)
let hasBuilderMethod methodName cenv env ad builderTy m =
not (isNil (TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env m ad methodName builderTy))

/// <summary>
/// Try translate the syntax sugar
/// </summary>
Expand Down Expand Up @@ -1559,12 +1563,70 @@ let rec TryTranslateComputationExpression
Some(ConsumeCustomOpClauses ceenv comp q varSpace dataCompPriorToOp comp false mClause)

| SynExpr.Sequential(sp, true, innerComp1, innerComp2, m, _) ->
let containsRangeExpressions expr =
let rec loop exprs =
match exprs with
| SynExpr.IndexRange _ :: _ -> true
| SynExpr.Sequential(_, true, e1, e2, _, _) :: exprs -> loop (e1 :: e2 :: exprs)
| _ -> false

loop [ expr ]

let containsRangeExpressions = containsRangeExpressions comp

/// Report language feature error for each range expression in a sequence
let reportRangeExpressionsNotSupported ceenv expr =
let rec loop exprs =
match exprs with
| [] -> ()
| SynExpr.IndexRange(_, _, _, _, _, m) :: exprs ->
checkLanguageFeatureAndRecover ceenv.cenv.g.langVersion LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions m
loop exprs
| SynExpr.Sequential(_, true, e1, e2, _, _) :: exprs -> loop (e1 :: e2 :: exprs)
| _ :: exprs -> loop exprs

loop [ expr ]

if
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
&& containsRangeExpressions
then
let builderSupportsMixedRanges ceenv m =
hasBuilderMethod "Yield" ceenv.cenv ceenv.env ceenv.ad ceenv.builderTy m
&& hasBuilderMethod "Combine" ceenv.cenv ceenv.env ceenv.ad ceenv.builderTy m
&& hasBuilderMethod "Delay" ceenv.cenv ceenv.env ceenv.ad ceenv.builderTy m

if builderSupportsMixedRanges ceenv m then
let transformSequenceWithRanges ceenv expr =
let rec loop expr cont =
match expr with
| SynExpr.Sequential(sp, true, e1, e2, m, trivia) ->
// Transform each part to yield/yieldFrom
let e1Transformed = TransformExprToYieldOrYieldFrom ceenv e1
// Create a new sequential expression with the transformed parts
loop
e2
(cont
<< fun e2Transformed -> SynExpr.Sequential(sp, true, e1Transformed, e2Transformed, m, trivia))
| e -> cont (TransformExprToYieldOrYieldFrom ceenv e)

loop expr id

let transformed = transformSequenceWithRanges ceenv comp
Some(TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace transformed translatedCtxt)
else
None
// Check for 'where x > y' and other mis-applications of infix operators. If detected, give a good error message, and just ignore innerComp1
if ceenv.isQuery && checkForBinaryApp ceenv innerComp1 then
elif ceenv.isQuery && checkForBinaryApp ceenv innerComp1 then
if containsRangeExpressions then
reportRangeExpressionsNotSupported ceenv comp

Some(TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace innerComp2 translatedCtxt)

else
if containsRangeExpressions then
reportRangeExpressionsNotSupported ceenv comp

if ceenv.isQuery && not (innerComp1.IsArbExprAndThusAlreadyReportedError) then
match innerComp1 with
| SynExpr.JoinIn _ -> ()
Expand Down Expand Up @@ -2206,6 +2268,29 @@ let rec TryTranslateComputationExpression
Some(translatedCtxt callExpr)

| SynExpr.YieldOrReturnFrom((true, _), synYieldExpr, _, { YieldOrReturnFromKeyword = m }) ->
let isRangeExpr =
match synYieldExpr with
| SynExpr.IndexRange _ -> true
| _ -> false

if
isRangeExpr
&& not (ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions)
then
checkLanguageFeatureAndRecover
ceenv.cenv.g.langVersion
LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
synYieldExpr.Range

// Rewrite range expressions in yield! to their sequence form
let synYieldExpr =
if isRangeExpr then
match RewriteRangeExpr synYieldExpr with
| Some rewrittenExpr -> rewrittenExpr
| None -> synYieldExpr
else
synYieldExpr

let yieldFromExpr =
mkSourceExpr synYieldExpr ceenv.sourceMethInfo ceenv.builderValName

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

let synYieldOrReturnExpr =
match synYieldOrReturnExpr with
| SynExpr.IndexRange _ when isYield ->
if not (ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions) then
checkLanguageFeatureAndRecover
ceenv.cenv.g.langVersion
LanguageFeature.AllowMixedRangesAndValuesInSeqExpressions
synYieldOrReturnExpr.Range

match RewriteRangeExpr synYieldOrReturnExpr with
| Some rewrittenExpr -> rewrittenExpr
| None -> synYieldOrReturnExpr
| _ -> synYieldOrReturnExpr

requireBuilderMethod methName m cenv ceenv.env ceenv.ad ceenv.builderTy m

let yieldOrReturnCall =
Expand Down Expand Up @@ -2641,6 +2740,26 @@ and isSimpleExpr ceenv comp =
| SynExpr.DoBang _ -> false
| _ -> true

/// Transform a single expression to Yield or YieldFrom based on whether it's a range
and TransformExprToYieldOrYieldFrom ceenv expr =
let m = expr.Range

let ``yield!`` rewrittenRange =
SynExpr.YieldOrReturnFrom((true, false), rewrittenRange, m, { YieldOrReturnFromKeyword = m })

let ``yield`` rewrittenRange =
SynExpr.YieldOrReturn((true, false), rewrittenRange, m, { YieldOrReturnKeyword = m })

// If there is no YieldFrom defined on the builder, use Yield;
// create a YieldOrReturn expression and let the CE machinery handle it.
match RewriteRangeExpr expr with
| Some rewrittenRange ->
if hasBuilderMethod "YieldFrom" ceenv.cenv ceenv.env ceenv.ad ceenv.builderTy m then
``yield!`` rewrittenRange
else
``yield`` rewrittenRange
| None -> ``yield`` expr

and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) firstTry q varSpace comp translatedCtxt =

ceenv.cenv.stackGuard.Guard
Expand Down
Loading
Loading