From d5409b7ce4472122d8aaa305502a54cd7835a850 Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Wed, 17 Jun 2026 23:30:54 +0100 Subject: [PATCH 1/2] Update FSharp.Core to 10.1 --- .config/dotnet-tools.json | 2 +- .paket/Paket.Restore.targets | 19 +++++++--- CHANGELOG.md | 4 ++ paket.dependencies | 2 +- paket.lock | 12 +++--- src/FsAutoComplete.Core/FCSPatches.fs | 19 ++++++++-- src/FsAutoComplete.Core/InlayHints.fs | 2 +- src/FsAutoComplete.Core/TestAdapter.fs | 16 ++++---- .../UnionPatternMatchCaseGenerator.fs | 10 ++--- src/FsAutoComplete.Core/UntypedAstUtils.fs | 10 ++--- .../LspServers/AdaptiveServerState.fs | 6 +-- .../CodeFixTests/Tests.fs | 1 - .../CodeFixTests/ToInterpolatedStringTests.fs | 38 ++++++++----------- 13 files changed, 80 insertions(+), 61 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 269f6baa5..7acb9146f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -24,7 +24,7 @@ "rollForward": false }, "fsharp-analyzers": { - "version": "0.35.0", + "version": "0.37.2", "commands": [ "fsharp-analyzers" ], diff --git a/.paket/Paket.Restore.targets b/.paket/Paket.Restore.targets index 712cd7713..cb218acb6 100644 --- a/.paket/Paket.Restore.targets +++ b/.paket/Paket.Restore.targets @@ -241,8 +241,9 @@ $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[6]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[7]) $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[8]) + $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[9]) - + %(PaketReferencesFileLinesInfo.PackageVersion) All runtime @@ -251,10 +252,8 @@ %(PaketReferencesFileLinesInfo.Aliases) true true - - - + %(PaketReferencesFileLinesInfo.PackageVersion) @@ -319,7 +318,17 @@ - + + + + + <_DefinedConditionProperties Include="@(_ConditionProperties)" Condition="$(%(Identity)) == 'true'"/> + + + <_ConditionsParameter> + <_ConditionsParameter Condition="@(_DefinedConditionProperties) != ''">--conditions @(_DefinedConditionProperties) + + diff --git a/CHANGELOG.md b/CHANGELOG.md index c234ad8d2..a240c6212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Update FSharp.Analyzers.SDK to `0.37.2`, which bumps FSharp.Core to `10.1.201` and FSharp.Compiler.Service to `43.12.201`, and adapt to the corresponding FCS syntax-tree API changes. + ### Fixed - [Disable inline values by default to restore pipeline hints](https://github.com/ionide/FsAutoComplete/pull/1456) - `InlineValueProvider` is no longer advertised by default, restoring pipeline hints during normal editing (closes [#1214](https://github.com/ionide/FsAutoComplete/issues/1214)) diff --git a/paket.dependencies b/paket.dependencies index ccbffe48d..fbf43e345 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -24,7 +24,7 @@ nuget Microsoft.Build.Utilities.Core >= 17.4 copy_local:false nuget Microsoft.Build.Tasks.Core >= 17.4 copy_local: false nuget Nuget.Frameworks >= 6.3 copy_local: false nuget Microsoft.CodeAnalysis -nuget FSharp.Analyzers.SDK 0.35.0 +nuget FSharp.Analyzers.SDK 0.37.2 nuget ICSharpCode.Decompiler nuget Mono.Cecil >= 0.11.4 nuget FSharpLint.Core diff --git a/paket.lock b/paket.lock index d7704d4f3..47bbcfd87 100644 --- a/paket.lock +++ b/paket.lock @@ -52,13 +52,13 @@ NUGET FParsec (1.1.1) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0)) FSharp.Core (>= 4.3.4) FSharp.Analyzers.Build (0.3) - FSharp.Analyzers.SDK (0.35) - FSharp.Compiler.Service (43.10.101) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) - FSharp.Core (10.0.101) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) + FSharp.Analyzers.SDK (0.37.2) + FSharp.Compiler.Service (>= 43.12.201) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) + FSharp.Core (10.1.201) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) McMaster.NETCore.Plugins (>= 2.0) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) Microsoft.Extensions.Logging.Abstractions (>= 6.0) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) - FSharp.Compiler.Service (43.10.101) - FSharp.Core (10.0.101) + FSharp.Compiler.Service (43.12.201) + FSharp.Core (10.1.201) System.Buffers (>= 4.6) System.Collections.Immutable (>= 9.0) System.Diagnostics.DiagnosticSource (>= 9.0) @@ -72,7 +72,7 @@ NUGET Fsharp.Control.Reactive (5.0.5) - restriction: || (== net10.0) (== net8.0) (== net9.0) (&& (== netstandard2.0) (>= net8.0)) (&& (== netstandard2.1) (>= net8.0)) FSharp.Core (>= 4.7.2) System.Reactive (>= 5.0 < 6.0) - FSharp.Core (10.0.101) + FSharp.Core (10.1.201) FSharp.Data.Adaptive (1.2.18) FSharp.Core (>= 4.7) System.Reflection.Emit.Lightweight (>= 4.6) diff --git a/src/FsAutoComplete.Core/FCSPatches.fs b/src/FsAutoComplete.Core/FCSPatches.fs index d964a637a..17568ce49 100644 --- a/src/FsAutoComplete.Core/FCSPatches.fs +++ b/src/FsAutoComplete.Core/FCSPatches.fs @@ -135,7 +135,7 @@ module SyntaxTreeOps = | SynExpr.Match(expr = e; clauses = cl) -> walkExpr e || walkMatchClauses cl - | SynExpr.LetOrUse(bindings = bs; body = e) -> walkBinds bs || walkExpr e + | SynExpr.LetOrUse letOrUse -> walkBinds letOrUse.Bindings || walkExpr letOrUse.Body | SynExpr.TryWith(tryExpr = e; withCases = cl) -> walkExpr e || walkMatchClauses cl @@ -243,7 +243,15 @@ type LanguageVersionShim(versionText: string) = static let LanguageVersionTy = lazy (Type.GetType("FSharp.Compiler.Features+LanguageVersion, FSharp.Compiler.Service")) - static let ctor = lazy (LanguageVersionTy.Value.GetConstructor([| typeof |])) + // FCS 43.12 changed the LanguageVersion constructor from `(string)` to + // `(string, disabledFeaturesArray: _ option)`, so we can't look it up by an exact + // signature. Find the constructor whose first parameter is the version string instead. + static let ctor = + lazy + (LanguageVersionTy.Value.GetConstructors() + |> Array.find (fun c -> + let ps = c.GetParameters() + ps.Length >= 1 && ps.[0].ParameterType = typeof)) static let isPreviewEnabled = lazy (ReflectionDelegates.createGetter LanguageVersionTy.Value "IsPreviewEnabled") @@ -251,7 +259,12 @@ type LanguageVersionShim(versionText: string) = static let supportsFeature = lazy (ReflectionDelegates.createFuncArity1 LanguageVersionTy.Value LanguageFeatureShim.Type "SupportsFeature") - let realLanguageVersion = ctor.Value.Invoke([| versionText |]) + // Pass the version text as the first argument and null (= None / default) for any + // additional optional parameters the constructor may have gained. + let realLanguageVersion = + let c = ctor.Value + let args = c.GetParameters() |> Array.mapi (fun i _ -> if i = 0 then box versionText else null) + c.Invoke(args) member x.IsPreviewEnabled = isPreviewEnabled.Value realLanguageVersion diff --git a/src/FsAutoComplete.Core/InlayHints.fs b/src/FsAutoComplete.Core/InlayHints.fs index 82171eae1..f2fe33f1d 100644 --- a/src/FsAutoComplete.Core/InlayHints.fs +++ b/src/FsAutoComplete.Core/InlayHints.fs @@ -593,7 +593,7 @@ let tryGetExplicitTypeInfo (text: IFSACSourceText, ast: ParsedInput) (pos: Posit member visitor.VisitPat(path, defaultTraverse, pat) = let invalidPositionForTypeAnnotation (path: SyntaxNode list) = match path with - | SyntaxNode.SynExpr(SynExpr.LetOrUse(isUse = true)) :: _ -> + | SyntaxNode.SynExpr(SynExpr.LetOrUse letOrUse) :: _ when letOrUse.IsUse -> // use! value = true | _ -> false diff --git a/src/FsAutoComplete.Core/TestAdapter.fs b/src/FsAutoComplete.Core/TestAdapter.fs index 1967ec4ec..2bb056733 100644 --- a/src/FsAutoComplete.Core/TestAdapter.fs +++ b/src/FsAutoComplete.Core/TestAdapter.fs @@ -218,9 +218,9 @@ let getExpectoTests (ast: ParsedInput) : TestAdapterEntry list = visitExpr parent cond visitExpr parent trueBranch falseBranchOpt |> Option.iter (visitExpr parent) - | SynExpr.LetOrUse(bindings = bindings; body = body) -> - visitBindings parent bindings - visitExpr parent body + | SynExpr.LetOrUse letOrUse -> + visitBindings parent letOrUse.Bindings + visitExpr parent letOrUse.Body | SynExpr.Record(_, _, fields, _) -> fields |> List.choose (fun (SynExprRecordField(expr = expr)) -> expr) @@ -237,7 +237,7 @@ let getExpectoTests (ast: ParsedInput) : TestAdapterEntry list = let rec visitDeclarations prefix decls = for declaration in decls do match declaration with - | SynModuleDecl.Let(_, bindings, _) -> visitBindings prefix bindings + | SynModuleDecl.Let(bindings = bindings) -> visitBindings prefix bindings | SynModuleDecl.NestedModule(decls = decls) -> visitDeclarations prefix decls | _ -> () @@ -289,7 +289,7 @@ let getNUnitTest (ast: ParsedInput) : TestAdapterEntry list = let rec visitMember (parent: TestAdapterEntry) = function | SynMemberDefn.Member(b, _) -> visitBinding parent b - | SynMemberDefn.LetBindings(bindings, _, _, _) -> + | SynMemberDefn.LetBindings(bindings = bindings) -> for b in bindings do visitBinding parent b | SynMemberDefn.NestedType(typeDef, _, _) -> visitTypeDef parent typeDef @@ -366,7 +366,7 @@ let getNUnitTest (ast: ParsedInput) : TestAdapterEntry list = for declaration in decls do match declaration with - | SynModuleDecl.Let(_, bindings, _) -> + | SynModuleDecl.Let(bindings = bindings) -> for b in bindings do visitBinding parent b | SynModuleDecl.NestedModule(moduleInfo = ci; decls = decls) -> @@ -463,7 +463,7 @@ let getXUnitTest ast : TestAdapterEntry list = let rec visitMember (parent: TestAdapterEntry) = function | SynMemberDefn.Member(b, _) -> visitBinding parent b - | SynMemberDefn.LetBindings(bindings, _, _, _) -> + | SynMemberDefn.LetBindings(bindings = bindings) -> for b in bindings do visitBinding parent b | SynMemberDefn.NestedType(typeDef, _, _) -> visitTypeDef parent typeDef @@ -540,7 +540,7 @@ let getXUnitTest ast : TestAdapterEntry list = for declaration in decls do match declaration with - | SynModuleDecl.Let(_, bindings, _) -> + | SynModuleDecl.Let(bindings = bindings) -> for b in bindings do visitBinding parent b | SynModuleDecl.NestedModule(moduleInfo = ci; decls = decls) -> diff --git a/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs b/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs index 15466f138..dc432d5f4 100644 --- a/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs +++ b/src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs @@ -85,7 +85,7 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P getIfPosInRange decl.Range (fun () -> match decl with | SynModuleDecl.Exception(SynExceptionDefn(members = synMembers), _) -> List.tryPick walkSynMemberDefn synMembers - | SynModuleDecl.Let(_isRecursive, bindings, _range) -> List.tryPick walkBinding bindings + | SynModuleDecl.Let(bindings = bindings) -> List.tryPick walkBinding bindings | SynModuleDecl.ModuleAbbrev(_lhs, _rhs, _range) -> None | SynModuleDecl.NamespaceFragment(fragment) -> walkSynModuleOrNamespace fragment | SynModuleDecl.NestedModule(decls = modules) -> List.tryPick walkSynModuleDecl modules @@ -119,7 +119,7 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P | SynMemberDefn.Member(binding, _range) -> walkBinding binding | SynMemberDefn.NestedType(typeDef, _access, _range) -> walkSynTypeDefn typeDef | SynMemberDefn.ValField(_field, _range) -> None - | SynMemberDefn.LetBindings(bindings, _isStatic, _isRec, _range) -> List.tryPick walkBinding bindings + | SynMemberDefn.LetBindings(bindings = bindings) -> List.tryPick walkBinding bindings | SynMemberDefn.GetSetMember(_get, _set, _range, _) -> None | SynMemberDefn.Open _ | SynMemberDefn.ImplicitInherit _ @@ -230,9 +230,9 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P | SynExpr.TypeApp(synExpr, _, _synTypeList, _commas, _, _, _range) -> walkExpr synExpr - | SynExpr.LetOrUse(body = synExpr; bindings = synBindingList) -> - walkExpr synExpr - |> Option.orElseWith (fun _ -> List.tryPick walkBinding synBindingList) + | SynExpr.LetOrUse letOrUse -> + walkExpr letOrUse.Body + |> Option.orElseWith (fun _ -> List.tryPick walkBinding letOrUse.Bindings) | SynExpr.TryWith(tryExpr = synExpr) -> walkExpr synExpr diff --git a/src/FsAutoComplete.Core/UntypedAstUtils.fs b/src/FsAutoComplete.Core/UntypedAstUtils.fs index c0097ce02..2cef8924d 100644 --- a/src/FsAutoComplete.Core/UntypedAstUtils.fs +++ b/src/FsAutoComplete.Core/UntypedAstUtils.fs @@ -310,9 +310,9 @@ module Syntax = | SynExpr.TypeApp(e, _, tys, _, _, _, _) -> List.iter walkType tys walkExpr e - | SynExpr.LetOrUse(bindings = bindings; body = e; range = _) -> - List.iter walkBinding bindings - walkExpr e + | SynExpr.LetOrUse letOrUse -> + List.iter walkBinding letOrUse.Bindings + walkExpr letOrUse.Body | SynExpr.TryWith(tryExpr = e; withCases = clauses; range = _) -> List.iter walkClause clauses walkExpr e @@ -445,7 +445,7 @@ module Syntax = | SynMemberDefn.ImplicitInherit(inheritType = t; inheritArgs = e) -> walkType t walkExpr e - | SynMemberDefn.LetBindings(bindings, _, _, _) -> List.iter walkBinding bindings + | SynMemberDefn.LetBindings(bindings = bindings) -> List.iter walkBinding bindings | SynMemberDefn.Interface(t, _, members, _) -> walkType t members |> Option.iter (List.iter walkMember) @@ -531,7 +531,7 @@ module Syntax = | SynModuleDecl.NestedModule(info, _, modules, _, _, _) -> walkComponentInfo info List.iter walkSynModuleDecl modules - | SynModuleDecl.Let(_, bindings, _) -> List.iter walkBinding bindings + | SynModuleDecl.Let(bindings = bindings) -> List.iter walkBinding bindings | SynModuleDecl.Expr(expr, _) -> walkExpr expr | SynModuleDecl.Types(types, _) -> List.iter walkTypeDefn types | SynModuleDecl.Attributes(attributes = AllAttrs attrs; range = _) -> List.iter walkAttribute attrs diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs index 9871ed621..6800afb11 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs @@ -261,7 +261,7 @@ type AdaptiveState if enableAnalyzers then let mutable assemblyLoadStats = { AnalyzerAssemblies = 0 - Analyzers = 0 + AnalyzerNames = [] FailedAssemblies = 0 } let excludeInclude = @@ -287,7 +287,7 @@ type AdaptiveState assemblyLoadStats <- { AnalyzerAssemblies = assemblyLoadStats.AnalyzerAssemblies + stats.AnalyzerAssemblies - Analyzers = assemblyLoadStats.Analyzers + stats.Analyzers + AnalyzerNames = assemblyLoadStats.AnalyzerNames @ stats.AnalyzerNames FailedAssemblies = assemblyLoadStats.FailedAssemblies + stats.FailedAssemblies } @@ -298,7 +298,7 @@ type AdaptiveState assemblyLoadStats <- { AnalyzerAssemblies = assemblyLoadStats.AnalyzerAssemblies + stats.AnalyzerAssemblies - Analyzers = assemblyLoadStats.Analyzers + stats.Analyzers + AnalyzerNames = assemblyLoadStats.AnalyzerNames @ stats.AnalyzerNames FailedAssemblies = assemblyLoadStats.FailedAssemblies + stats.FailedAssemblies } ) diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index 1f03e238b..36c61fd52 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -3684,7 +3684,6 @@ let tests textFactory state = AddExplicitTypeAnnotationTests.tests state AdjustConstantTests.tests state ToInterpolatedStringTests.tests state - ToInterpolatedStringTests.unavailableTests state addMissingEqualsToTypeDefinitionTests state addMissingFunKeywordTests state addMissingInstanceMemberTests state diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/ToInterpolatedStringTests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/ToInterpolatedStringTests.fs index 5e70b6fd4..2d393f5a1 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/ToInterpolatedStringTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/ToInterpolatedStringTests.fs @@ -7,12 +7,14 @@ open Utils.CursorbasedTests open FsAutoComplete.CodeFix open FsAutoComplete.FCSPatches -let langVersion60Config = +// NOTE: language versions <= 7.0 are "out of support" in FCS 43.12+ and cause the +// project to fail to load, so we pin to 8.0 (LTS), which still supports string interpolation. +let langVersion80Config = { defaultConfigDto with - FSIExtraSharedParameters = Some [| "--langversion:6.0" |] } + FSIExtraSharedParameters = Some [| "--langversion:8.0" |] } let tests state = - serverTestList (nameof ToInterpolatedString) state langVersion60Config None (fun server -> + serverTestList (nameof ToInterpolatedString) state langVersion80Config None (fun server -> [ let selectCodeFix = CodeFix.withTitle ToInterpolatedString.title testCaseAsync "simple integer string format" @@ -341,6 +343,17 @@ let tests state = Expect.equal true (languageVersion.SupportsFeature LanguageFeatureShim) "" } + // String interpolation was introduced in F# 5.0, so an older language version must report + // it as unsupported. This replaces the former `unavailableTests` server test: every language + // version still supported by FCS now includes the feature, so the negative case can only be + // exercised at the unit level (constructing the shim does not require the version to compile). + testCaseAsync "Reflecting into LanguageVersion - feature unsupported in older version" + <| async { + let feature = LanguageFeatureShim("StringInterpolation") + let languageVersion = LanguageVersionShim("4.7") + Expect.equal false (languageVersion.SupportsFeature feature) "" + } + testCaseAsync "Multiline applications are not supported" <| CodeFix.checkNotApplicable server @@ -353,22 +366,3 @@ let tests state = selectCodeFix ]) - -let langVersion47Config = - { defaultConfigDto with - FSIExtraSharedParameters = Some [| "--langversion:4.7" |] } - -let unavailableTests state = - serverTestList $"unavailable {(nameof ToInterpolatedString)}" state langVersion47Config None (fun server -> - [ let selectCodeFix = CodeFix.withTitle ToInterpolatedString.title - - testCaseAsync "codefix not available for langversion" - <| CodeFix.checkNotApplicable - server - """ - let a = sprintf$0 "Hey %i" 3 - """ - Diagnostics.acceptAll - selectCodeFix - - ]) From 4a1378b9bdab6c9cde35f76f0107d9b1112f38bf Mon Sep 17 00:00:00 2001 From: Tuomas Hietanen Date: Sat, 20 Jun 2026 11:42:38 +0100 Subject: [PATCH 2/2] Apply Fantomas formatting to FCSPatches.fs The FSharp.Core 10.1 update left a long line in the LanguageVersionShim constructor reflection that failed the CI `dotnet fantomas --check` gate, blocking every build job before the test phase. Reformat to satisfy it. Co-Authored-By: Claude Opus 4.8 --- src/FsAutoComplete.Core/FCSPatches.fs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/FsAutoComplete.Core/FCSPatches.fs b/src/FsAutoComplete.Core/FCSPatches.fs index 17568ce49..af5ad675f 100644 --- a/src/FsAutoComplete.Core/FCSPatches.fs +++ b/src/FsAutoComplete.Core/FCSPatches.fs @@ -263,7 +263,11 @@ type LanguageVersionShim(versionText: string) = // additional optional parameters the constructor may have gained. let realLanguageVersion = let c = ctor.Value - let args = c.GetParameters() |> Array.mapi (fun i _ -> if i = 0 then box versionText else null) + + let args = + c.GetParameters() + |> Array.mapi (fun i _ -> if i = 0 then box versionText else null) + c.Invoke(args) member x.IsPreviewEnabled = isPreviewEnabled.Value realLanguageVersion