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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403))
* Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408)))
* Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440))
* Fix `let ... in` with explicit `in` keyword in light syntax: body is now scoped to the same line, preventing the parser from greedily capturing subsequent lines as part of the `let` body. ([Issue #7741](https://github.com/dotnet/fsharp/issues/7741), [PR #19501](https://github.com/dotnet/fsharp/pull/19501))

### Added

Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

### Fixed

* Fix `let ... in` with explicit `in` keyword in light syntax: body is now scoped to the same line, preventing the parser from greedily capturing subsequent lines. ([Issue #7741](https://github.com/dotnet/fsharp/issues/7741), [PR #19501](https://github.com/dotnet/fsharp/pull/19501))

### Changed
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1809,6 +1809,7 @@ featureWarnWhenFunctionValueUsedAsInterpolatedStringArg,"Warn when a function va
featureMethodOverloadsCache,"Support for caching method overload resolution results for improved compilation performance."
featureImplicitDIMCoverage,"Implicit dispatch slot coverage for default interface member implementations"
featurePreprocessorElif,"#elif preprocessor directive"
featureLetInBodyScoping,"Scope 'let ... in' body to the same line in sequence expressions"
3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s"
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
3882,lexHashElifMustBeFirst,"#elif directive must appear as the first non-whitespace character on a line"
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ type LanguageFeature =
| MethodOverloadsCache
| ImplicitDIMCoverage
| PreprocessorElif
| LetInBodyScoping

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -251,6 +252,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
// Put stabilized features here for F# 11.0 previews via .NET SDK preview channels
LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg, languageVersion110
LanguageFeature.PreprocessorElif, languageVersion110
LanguageFeature.LetInBodyScoping, languageVersion110

// Difference between languageVersion110 and preview - 11.0 gets turned on automatically by picking a preview .NET 11 SDK
// previewVersion is only when "preview" is specified explicitly in project files and users also need a preview SDK
Expand Down Expand Up @@ -453,6 +455,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.MethodOverloadsCache -> FSComp.SR.featureMethodOverloadsCache ()
| LanguageFeature.ImplicitDIMCoverage -> FSComp.SR.featureImplicitDIMCoverage ()
| LanguageFeature.PreprocessorElif -> FSComp.SR.featurePreprocessorElif ()
| LanguageFeature.LetInBodyScoping -> FSComp.SR.featureLetInBodyScoping ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type LanguageFeature =
| MethodOverloadsCache
| ImplicitDIMCoverage
| PreprocessorElif
| LetInBodyScoping

/// LanguageVersion management
type LanguageVersion =
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1532,7 +1532,7 @@ type internal FsiConsoleInput

/// Try to get the first line, if we snarfed it while probing.
member _.TryGetFirstLine() =
let r = firstLine in
let r = firstLine
firstLine <- None
r

Expand Down
20 changes: 18 additions & 2 deletions src/Compiler/SyntaxTree/LexFilter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1680,8 +1680,24 @@ type LexFilterImpl (
if debug then dprintf "IN at %a (becomes %s)\n" outputPos tokenStartPos (if blockLet then "ODECLEND" else "IN")
if tokenStartCol < offsidePos.Column then warn tokenTup (FSComp.SR.lexfltIncorrentIndentationOfIn())
popCtxt()
// Make sure we queue a dummy token at this position to check if any other pop rules apply
delayToken(pool.UseLocation(tokenTup, ODUMMY token))

if blockLet && lexbuf.SupportsFeature LanguageFeature.LetInBodyScoping then
let nextTokenTup = peekNextTokenTup()
let nextTokenStartPos = startPosOfTokenTup nextTokenTup

if nextTokenStartPos.Line = tokenStartPos.Line then
// When the body expression starts on the same line as the 'in' keyword in light syntax,
// push a new seq block to limit the body scope to that line. This prevents the parser
// from greedily capturing all subsequent lines as part of the let body.
pushCtxtSeqBlock tokenTup AddBlockEnd
else
// Body starts on a new line after 'in' — the user intentionally placed the body
// on the next line, so use standard behavior.
delayToken(pool.UseLocation(tokenTup, ODUMMY token))
else
// Make sure we queue a dummy token at this position to check if any other pop rules apply
delayToken(pool.UseLocation(tokenTup, ODUMMY token))

returnToken tokenLexbufState (if blockLet then ODECLEND(mkSynRange tokenTup.StartPos tokenTup.EndPos, true) else token)

// Balancing rule. Encountering a 'done' balances with a 'do'. i.e. even a non-offside 'done' closes a 'do'
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Utilities/HashMultiMap.fs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ type internal HashMultiMap<'Key, 'Value when 'Key: not null>(size: int, comparer
| _ -> false

member s.Remove(k: 'Key) =
let res = s.ContainsKey(k) in
let res = s.ContainsKey(k)
s.Remove(k)
res

Expand Down
15 changes: 13 additions & 2 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,12 @@ fileModuleSpec:
{ let m = (rhs parseState 1)
(fun (mNamespaceOpt, isRec, path, xml) ->
match path with
| [] -> ParsedSigFileFragment.AnonModule($1, m)
| [] ->
let m =
match List.tryLast $1 with
| Some lastDecl -> Range.withEnd lastDecl.Range.End m
| None -> m
ParsedSigFileFragment.AnonModule($1, m)
| _ ->
let lastDeclRange = List.tryLast $1 |> Option.map (fun decl -> decl.Range) |> Option.defaultValue (rhs parseState 1)
let m = withStart (lhs parseState).Start lastDeclRange
Expand Down Expand Up @@ -1200,7 +1205,13 @@ fileModuleImpl:
{ let m = (rhs parseState 1)
(fun (mNamespaceOpt, isRec, path, xml) ->
match path, mNamespaceOpt with
| [], None -> ParsedImplFileFragment.AnonModule($1, m)
| [], None ->
// Use last declaration's end to avoid the range being extended to the OBLOCKEND/EOF position
let m =
match List.tryLast $1 with
| Some lastDecl -> Range.withEnd lastDecl.Range.End m
| None -> m
ParsedImplFileFragment.AnonModule($1, m)
| _ ->
let lastDeclRange = List.tryLast $1 |> Option.map (fun decl -> decl.Range) |> Option.defaultValue (rhs parseState 1)
let m = withStart (lhs parseState).Start lastDeclRange
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,85 @@ let main _argv =
"""
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/7091
[<Fact>]
let ``Warn when let-in has multi-line sequential body in do block``() =
FSharp """
module Test
let x = 42
do
let x = 1 in x + 1
x
"""
|> withLangVersionPreview
|> typecheck
|> shouldFail
|> withDiagnostics [
(Warning 20, Line 5, Col 5, Line 5, Col 23,
"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'.")
(Warning 20, Line 5, Col 5, Line 6, Col 6,
"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'.")
]

// https://github.com/dotnet/fsharp/issues/7091
[<Fact>]
let ``No warning for single-line let-in``() =
FSharp """
module Test
let result = let x = 1 in x + 1
"""
|> withLangVersionPreview
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/7091
[<Fact>]
let ``No warning for let without in keyword``() =
FSharp """
module Test
let x = 42
do
let x = 1
printfn "%d" x
"""
|> withLangVersionPreview
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/7091
[<Fact>]
let ``No warning for let-in without langversion preview``() =
FSharp """
module Test
let x = 42
do
let x = 1 in x + 1
x
"""
|> withLangVersion "9.0"
|> typecheck
|> shouldFail
|> withDiagnostics [
(Warning 20, Line 5, Col 18, Line 5, Col 23,
"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'.")
(Warning 20, Line 5, Col 5, Line 6, Col 6,
"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'.")
]

// https://github.com/dotnet/fsharp/issues/7091
[<Fact>]
let ``Warn when let-in extends scope in function body``() =
FSharp """
module Test
let f () =
let x = 1 in x + 1
printfn "hello"
"""
|> withLangVersionPreview
|> typecheck
|> shouldFail
|> withDiagnostics [
(Warning 20, Line 4, Col 5, Line 4, Col 23,
"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'.")
]
Loading
Loading