diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index d5c2087765..fdec281a01 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -22,3 +22,4 @@ * Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289)) * Add `#version;;` directive to F# Interactive to display version and environment information. ([Issue #13307](https://github.com/dotnet/fsharp/issues/13307), [PR #19332](https://github.com/dotnet/fsharp/pull/19332)) +* Added warning FS3885 when `let ... in` with explicit `in` keyword has a body that extends to subsequent lines, which causes all subsequent lines to become part of the `let` body due to the parser's greedy behavior. ([Issue #7741](https://github.com/dotnet/fsharp/issues/7741), [PR #19501](https://github.com/dotnet/fsharp/pull/19501)) diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index d97ef29412..074af45dc5 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -2,6 +2,7 @@ * Warn (FS3884) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289)) * Added `MethodOverloadsCache` language feature (preview) that caches overload resolution results for repeated method calls, significantly improving compilation performance. ([PR #19072](https://github.com/dotnet/fsharp/pull/19072)) +* Warn (FS3885) when `let ... in` with explicit `in` keyword has a body that extends to subsequent lines, causing unexpected scoping. ([Issue #7741](https://github.com/dotnet/fsharp/issues/7741), [PR #19501](https://github.com/dotnet/fsharp/pull/19501)) ### Fixed diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index a4007147b9..8835f84144 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1809,8 +1809,10 @@ 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" +featureWarnOnLetInSequenceExpression,"Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines" 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" 3883,lexHashElifMustHaveIdent,"#elif directive should be immediately followed by an identifier" 3884,tcFunctionValueUsedAsInterpolatedStringArg,"This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments." +3885,tcLetExpressionWithInHasMultiLineBody,"The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 1bc3183705..0cc80ce454 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -108,6 +108,7 @@ type LanguageFeature = | MethodOverloadsCache | ImplicitDIMCoverage | PreprocessorElif + | WarnOnLetInSequenceExpression /// LanguageVersion management type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) = @@ -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.WarnOnLetInSequenceExpression, 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 @@ -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.WarnOnLetInSequenceExpression -> FSComp.SR.featureWarnOnLetInSequenceExpression () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index fd6182c9d3..1a247cda15 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -99,6 +99,7 @@ type LanguageFeature = | MethodOverloadsCache | ImplicitDIMCoverage | PreprocessorElif + | WarnOnLetInSequenceExpression /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 1ff957e77a..d1d78a19f6 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -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 diff --git a/src/Compiler/SyntaxTree/LexFilter.fs b/src/Compiler/SyntaxTree/LexFilter.fs index ed5da9fd04..ffa3072e21 100644 --- a/src/Compiler/SyntaxTree/LexFilter.fs +++ b/src/Compiler/SyntaxTree/LexFilter.fs @@ -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.WarnOnLetInSequenceExpression 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' diff --git a/src/Compiler/Utilities/HashMultiMap.fs b/src/Compiler/Utilities/HashMultiMap.fs index 2688869136..e2e0832e2f 100644 --- a/src/Compiler/Utilities/HashMultiMap.fs +++ b/src/Compiler/Utilities/HashMultiMap.fs @@ -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 diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 6896c33a6a..de788ad9d5 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 3966a59a85..1bc0060ec2 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 08828606dd..bccd16c9ca 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 5a28ec1595..f05e7f960e 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 5dead052c6..4feeff4e63 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index f491fb0c4c..b287d73b04 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index f8185fb2a2..b91da235c9 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 7e81e135ee..85f725c94a 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 22a5db4504..ae82e469b4 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 8f5220ba47..fc3715134d 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 1750982712..d48b90024c 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index bf26625f8e..b0e5c25315 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 2a63e5ad75..5bfa69c98a 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -8962,6 +8962,16 @@ This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments. + + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + Warn when 'let ... in' is used in a sequence expression and the body extends to subsequent lines + + + + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + The use of 'in' in 'let ... in' on a single line followed by additional code on subsequent lines causes all subsequent lines to be part of the 'let' body. This may lead to unexpected scoping. Either remove the 'in' keyword and rely on indentation, or add parentheses to clarify the intended scope. + + \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs index 942ab64dfc..2c053b6ad4 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/WarnExpressionTests.fs @@ -220,3 +220,85 @@ let main _argv = """ |> typecheck |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/7091 + [] + 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 + [] + 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 + [] + 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 + [] + 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 + [] + 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'.") + ] diff --git a/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl b/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl index 4fb6595cbd..b65ca5c4e1 100644 --- a/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl +++ b/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseContainsTheRangeOfInKeyword.fs.bsl @@ -23,7 +23,7 @@ ImplFile Range = (2,0--2,15) Trivia = { InKeyword = Some (2,10--2,12) } IsFromSource = true }, (2,0--2,15))], PreXmlDocEmpty, [], None, - (2,0--2,15), { LeadingKeyword = None })], (true, true), + (2,0--3,0), { LeadingKeyword = None })], (true, true), { ConditionalDirectives = [] WarnDirectives = [] CodeComments = [] }, set [])) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index 7901df8880..eb5a2de9f5 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -2724,10 +2724,10 @@ module Patterns = let sb = StringBuilder() for original, expected in pairs -> - (let original = string (SynPat.fmt sb original) in + (let original = string (SynPat.fmt sb original) ignore <| sb.Clear() original), - (let expected = string (SynPat.fmt sb expected) in + (let expected = string (SynPat.fmt sb expected) ignore <| sb.Clear() expected) }