Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 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
5bde641
Merge branch 'dotnet:main' into main
edgarfgp Jul 26, 2025
0f7c23c
Merge branch 'dotnet:main' into main
edgarfgp Jul 29, 2025
6a6843c
Merge branch 'dotnet:main' into main
edgarfgp Aug 4, 2025
d11dd4a
Merge branch 'dotnet:main' into main
edgarfgp Aug 4, 2025
30fc9a2
Merge branch 'dotnet:main' into main
edgarfgp Aug 5, 2025
815c51b
Merge branch 'dotnet:main' into main
edgarfgp Aug 11, 2025
7536249
Merge branch 'dotnet:main' into main
edgarfgp Aug 12, 2025
5b1a5aa
Merge branch 'dotnet:main' into main
edgarfgp Aug 27, 2025
0cac8f6
Merge branch 'dotnet:main' into main
edgarfgp Sep 5, 2025
9bd11e8
Parser: Capture multiple BlockSeparator
edgarfgp Sep 12, 2025
9018065
Release notes
edgarfgp Sep 12, 2025
f23dc6d
Merge branch 'dotnet:main' into main
edgarfgp Sep 12, 2025
908b292
Merge branch 'main' into capture-multiple-block-separators
edgarfgp Sep 12, 2025
2b5685c
format
edgarfgp Sep 12, 2025
4324335
IlVerify
edgarfgp Sep 12, 2025
4d19e95
remove unused pattern
edgarfgp Sep 14, 2025
73d6e60
Merge branch 'main' into capture-multiple-block-separators
edgarfgp Sep 15, 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
Expand Up @@ -29,6 +29,7 @@
* Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645))
* Parser: Capture named fields block separators. ([PR #18857](https://github.com/dotnet/fsharp/pull/18857))
* Type checker: use inner expr range in upcast constraints errors ([PR #18850](https://github.com/dotnet/fsharp/pull/18850))
* Parser: Capture multiple block separators ([PR #18899](https://github.com/dotnet/fsharp/pull/18899))

### Breaking Changes

Expand Down
19 changes: 7 additions & 12 deletions src/Compiler/Checking/CheckRecordSyntaxHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,14 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid
let totalRange (origId: Ident) (id: Ident) =
withStartEnd origId.idRange.End id.idRange.Start origId.idRange

let rangeOfBlockSeparator (id: Ident) =
let idEnd = id.idRange.End
let blockSeparatorStartCol = idEnd.Column
let blockSeparatorEndCol = blockSeparatorStartCol + 4
let blockSeparatorStartPos = mkPos idEnd.Line blockSeparatorStartCol
let blockSeparatorEndPos = mkPos idEnd.Line blockSeparatorEndCol

withStartEnd blockSeparatorStartPos blockSeparatorEndPos id.idRange

match withExpr with
| SynExpr.Ident origId, (sepRange, _) ->
let lid, rng = upToId sepRange id (origId :: ids)
Some(SynExpr.LongIdent(false, LongIdentWithDots(lid, rng), None, totalRange origId id), (rangeOfBlockSeparator id, None))
| SynExpr.Ident origId, (blockSep: BlockSeparator) ->
let lid, rng = upToId blockSep.Range id (origId :: ids)

Some(
SynExpr.LongIdent(false, LongIdentWithDots(lid, rng), None, totalRange origId id),
BlockSeparator.Offside(blockSep.Range, None)
)
| _ -> None

let rec synExprRecd copyInfo (outerFieldId: Ident) innerFields exprBeingAssigned =
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ open FSharp.Compiler.TypedTree
val GroupUpdatesToNestedFields:
fields: ((Ident list * Ident) * SynExpr option) list -> ((Ident list * Ident) * SynExpr option) list

val TransformAstForNestedUpdates<'a> :
val TransformAstForNestedUpdates:
cenv: TcFileState ->
env: TcEnv ->
overallTy: TType ->
lid: LongIdent ->
exprBeingAssigned: SynExpr ->
withExpr: SynExpr * (range * 'a) ->
withExpr: SynExpr * BlockSeparator ->
(Ident list * Ident) * SynExpr option

val BindOriginalRecdExpr:
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7757,7 +7757,7 @@ and TcRecdExpr cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, m

match withExprOpt, synLongId.LongIdent, exprBeingAssigned with
| _, [ id ], _ -> ([], id), exprBeingAssigned
| Some withExpr, lid, Some exprBeingAssigned -> TransformAstForNestedUpdates cenv env overallTy lid exprBeingAssigned withExpr
| Some (origExpr, blockSep), lid, Some exprBeingAssigned -> TransformAstForNestedUpdates cenv env overallTy lid exprBeingAssigned (origExpr, blockSep)
| _ -> List.frontAndBack synLongId.LongIdent, exprBeingAssigned)

let flds = if hasOrigExpr then GroupUpdatesToNestedFields flds else flds
Expand Down
24 changes: 12 additions & 12 deletions src/Compiler/Service/ServiceParseTreeWalk.fs
Original file line number Diff line number Diff line change
Expand Up @@ -445,12 +445,12 @@ module SyntaxTraversal =
| SynExpr.AnonRecd(copyInfo = copyOpt; recordFields = fields) ->
[
match copyOpt with
| Some(expr, (withRange, _)) ->
| Some(expr, blockSep) ->
yield dive expr expr.Range traverseSynExpr

yield
dive () withRange (fun () ->
if posGeq pos withRange.End then
dive () blockSep.Range (fun () ->
if posGeq pos blockSep.Range.End then
// special case: caret is after WITH
// { x with $ }
visitor.VisitRecordField(path, Some expr, None)
Expand Down Expand Up @@ -498,24 +498,24 @@ module SyntaxTraversal =
traverseSynExpr expr)

match sepOpt with
| Some(sep, scPosOpt) ->
| Some blockSep ->
yield
dive () sep (fun () ->
dive () blockSep.Range (fun () ->
// special case: caret is below 'inherit' + one or more fields are already defined
// inherit A()
// $
// field1 = 5
diveIntoSeparator inheritRange.StartColumn scPosOpt None)
diveIntoSeparator inheritRange.StartColumn blockSep.Position None)
| None -> ()
| _ -> ()

match copyOpt with
| Some(expr, (withRange, _)) ->
| Some(expr, blockSep) ->
yield dive expr expr.Range traverseSynExpr

yield
dive () withRange (fun () ->
if posGeq pos withRange.End then
dive () blockSep.Range (fun () ->
if posGeq pos blockSep.Range.End then
// special case: caret is after WITH
// { x with $ }
visitor.VisitRecordField(path, Some expr, None)
Expand Down Expand Up @@ -556,14 +556,14 @@ module SyntaxTraversal =
| None -> ()

match sepOpt with
| Some(sep, scPosOpt) ->
| Some blockSep ->
yield
dive () sep (fun () ->
dive () blockSep.Range (fun () ->
// special case: caret is between field bindings
// field1 = 5
// $
// field2 = 5
diveIntoSeparator offsideColumn scPosOpt copyOpt)
diveIntoSeparator offsideColumn blockSep.Position copyOpt)
| _ -> ()

]
Expand Down
18 changes: 17 additions & 1 deletion src/Compiler/SyntaxTree/SyntaxTree.fs
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,23 @@ type DebugPointAtBinding =

type SeqExprOnly = SeqExprOnly of bool

type BlockSeparator = range * pos option
[<NoEquality; NoComparison; RequireQualifiedAccess>]
type BlockSeparator =
| Semicolon of range: range * position: pos option
| Comma of range: range * position: pos option
| Offside of range: range * position: pos option
Comment on lines +310 to +313
Copy link
Member

@auduchinok auduchinok Sep 18, 2025

Choose a reason for hiding this comment

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

@edgarfgp I don't understand how these types are supposed to work in practice. The separator is wrapped in an option in other places in the tree, e.g. in NamePatPairField. Let's assume the value is Some, i.e. there is a separator. How could it ever be that the position is None in such case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah this is fair. I wanted to preserve the shape type BlockSeparator = range * pos option

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let me create a PR to make Semicolon position no optional


member this.Range =
match this with
| Semicolon(range = m)
| Comma(range = m)
| Offside(range = m) -> m

member this.Position =
match this with
| Semicolon(position = p)
| Comma(position = p)
| Offside(position = p) -> p

type RecordFieldName = SynLongIdent * bool

Expand Down
23 changes: 20 additions & 3 deletions src/Compiler/SyntaxTree/SyntaxTree.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,26 @@ type SeqExprOnly =
/// Indicates if a for loop is 'for x in e1 -> e2', only valid in sequence expressions
| SeqExprOnly of bool

/// Represents the location of the separator block + optional position
/// of the semicolon (used for tooling support)
type BlockSeparator = range * pos option
/// Represents the location of the separator block and optional position of the semicolon (used for tooling support)
[<NoEquality; NoComparison; RequireQualifiedAccess>]
type BlockSeparator =
/// A separator consisting of a semicolon ';'
/// range is the range of the semicolon
/// position is the position of the semicolon (if available)
| Semicolon of range: range * position: pos option
/// A separator consisting of a comma ','
/// range is the range of the comma
/// position is the position of the comma (if available)
| Comma of range: range * position: pos option

// A separator consisting of a newline
/// range is the range of the newline
/// position is the position of the newline (if available)
| Offside of range: range * position: pos option
Comment on lines +369 to +372
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to capture this case? And especially the new line? Are there any cases where we rely on it? If yes, let's review them, please. It seems very suspicious. It effectively captures implementation details of how things are parsed and we should not rely on it in any way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because we need this to model OBLOCKSEP

seps_block:
  | OBLOCKSEP
     { (rhs parseState 1), None }

Copy link
Member

Choose a reason for hiding this comment

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

Why would we ever want to model it? If that's an implicit separator created by the unrelated subsequent tokens, we should not be relying on it in any way.

Copy link
Member

Choose a reason for hiding this comment

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

And if OBLOCKSEP can be a real token, this info should be carried in it from the LexFilter to the parser, so we could distinguish these cases.

We should not model the tree for non-existent tokens that are generated by subsequent unrelated ones.


member Range: range

member Position: pos option

/// Represents a record field name plus a flag indicating if given record field name is syntactically
/// correct and can be used in name resolution.
Expand Down
16 changes: 8 additions & 8 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -5709,7 +5709,7 @@ recdExprCore:
| appExpr
{ let mExpr = rhs parseState 1
reportParseErrorAt mExpr (FSComp.SR.parsFieldBinding ())
Some($1, (mExpr.EndRange, None)), [] }
Some($1, BlockSeparator.Offside(mExpr.EndRange, None)), [] }

/*
handles cases when identifier can start from the underscore
Expand Down Expand Up @@ -5743,15 +5743,15 @@ recdExprCore:
| appExpr WITH recdBinding recdExprBindings opt_seps_block
{ let l = List.rev $4
let l = rebindRanges $3 l $5
(Some($1, (rhs parseState 2, None)), l) }
(Some($1, BlockSeparator.Offside(rhs parseState 2, None)), l) }

| appExpr OWITH opt_seps_block OEND
{ (Some($1, (rhs parseState 2, None)), []) }
{ (Some($1, BlockSeparator.Offside(rhs parseState 2, None)), []) }

| appExpr OWITH recdBinding recdExprBindings opt_seps_block OEND
{ let l = List.rev $4
let l = rebindRanges $3 l $5
(Some($1, (rhs parseState 2, None)), l) }
(Some($1, BlockSeparator.Offside(rhs parseState 2, None)), l) }

opt_seps_block:
| seps_block
Expand All @@ -5762,17 +5762,17 @@ opt_seps_block:

seps_block:
| OBLOCKSEP
{ (rhs parseState 1), None }
{ BlockSeparator.Offside((rhs parseState 1), None) }

| SEMICOLON
{ let m = (rhs parseState 1)
m, Some m.End }
BlockSeparator.Semicolon(m, Some m.End) }

| SEMICOLON OBLOCKSEP
{ (rhs2 parseState 1 2), Some (rhs parseState 1).End }
{ BlockSeparator.Semicolon((rhs2 parseState 1 2), Some (rhs parseState 1).End) }

| OBLOCKSEP SEMICOLON
{ (rhs2 parseState 1 2), Some (rhs parseState 2).End }
{ BlockSeparator.Semicolon((rhs2 parseState 1 2), Some (rhs parseState 2).End) }


/* identifier can start from the underscore */
Expand Down
Loading
Loading