Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b52972e
port fsih to fsi as a hash directive
dawedawe May 12, 2024
6a14c98
add PR number
dawedawe May 12, 2024
01d8ded
update xlf files
dawedawe May 12, 2024
a8b39e9
update core printing baselines
dawedawe May 12, 2024
42f479d
rename module to FsiHelp
dawedawe May 14, 2024
909913c
rewrite Help.Print() to return a string to be independent of FsiConso…
dawedawe May 14, 2024
d3c4daf
move fsihelp module to dedicated file
dawedawe May 14, 2024
cea11e9
use shims for the filesystem
dawedawe May 14, 2024
6c38121
- Use fsi.h as an user interface.
dawedawe May 15, 2024
fdbedae
use a fsi printer for nicer output
dawedawe May 15, 2024
a7a2d4a
update baselines
dawedawe May 15, 2024
c0e6bcd
remove suppressItPrint parameter
dawedawe May 15, 2024
c063b89
Update src/FSharp.Compiler.Interactive.Settings/fsihelp.fs
dawedawe May 15, 2024
db6a446
Update src/FSharp.Compiler.Interactive.Settings/fsihelp.fs
dawedawe May 15, 2024
c8994eb
use voption to let the printer work in the (v)none case
dawedawe May 15, 2024
9f7203f
format
dawedawe May 15, 2024
d605fd8
remove doubled assembly attributes
dawedawe May 16, 2024
eacde80
refactor
dawedawe May 16, 2024
37506de
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 16, 2024
cbc2d99
add some tests
dawedawe May 16, 2024
46f8844
- let xpath queries work with single quotes in names like "shouldn't"
dawedawe May 16, 2024
6a5aa1b
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 17, 2024
65bb24c
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 17, 2024
af88e74
Trigger Build
dawedawe May 17, 2024
dcfe6b7
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 18, 2024
0689e5c
adjust changelog entry
dawedawe May 18, 2024
1bded2a
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 20, 2024
a48ca3d
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 22, 2024
dbc32e4
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 22, 2024
3b330d6
Merge branch 'main' into port_fsih_to_fsi
dawedawe May 23, 2024
ddf9b0e
Merge branch 'main' into port_fsih_to_fsi
dawedawe Jun 6, 2024
4631ac2
move back to #h interface
dawedawe Jun 6, 2024
c6a511f
rename tryGetDocumentation to tryGetHelp
dawedawe Jun 6, 2024
e8db338
use #help "expr";;
dawedawe Jun 6, 2024
9f7fa7a
append a newline if we don't find docs to position cursor correctly
dawedawe Jun 6, 2024
ef5d1a3
format
dawedawe Jun 6, 2024
72071c9
adjust release notes again
dawedawe Jun 6, 2024
35392f4
update surfacearea baselines
dawedawe Jun 6, 2024
11b00f2
Merge branch 'main' into port_fsih_to_fsi
dawedawe Jun 8, 2024
e12ae7f
adjust help text to use "identifier" instead of "expression"
dawedawe Jun 10, 2024
6894681
add unit test for non-identifier to show unhappy path
dawedawe Jun 11, 2024
c13ca67
add bsl entries for help output inside of fsi
dawedawe Jun 11, 2024
29b3ad6
update line numbers in bsl files
dawedawe Jun 12, 2024
1c238b5
Merge branch 'main' into port_fsih_to_fsi
KevinRansom Jun 12, 2024
01fbdf2
update line numbers in err bsl files
dawedawe Jun 12, 2024
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/8.0.400.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Fixed

* Extended #help directive in fsi to show documentation in the REPL. ([PR #17140](https://github.com/dotnet/fsharp/pull/17140))
* Fix internal error when dotting into delegates with multiple type parameters. ([PR #17227](https://github.com/dotnet/fsharp/pull/17227))
* Error for partial implementation of interface with static and non-static abstract members. ([Issue #17138](https://github.com/dotnet/fsharp/issues/17138), [PR #17160](https://github.com/dotnet/fsharp/pull/17160))
* Optimize simple mappings with preludes in computed collections. ([PR #17067](https://github.com/dotnet/fsharp/pull/17067))
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/FSharp.Compiler.Service.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@
<Compile Include="Interactive\FSharpInteractiveServer.fsi" />
<Compile Include="Interactive\FSharpInteractiveServer.fs" />
<Compile Include="Interactive\ControlledExecution.fs" />
<Compile Include="Interactive\fsihelp.fsi" />
<Compile Include="Interactive\fsihelp.fs" />
<Compile Include="Interactive\fsi.fsi" />
<Compile Include="Interactive\fsi.fs" />
<!-- A legacy resolver used to help with scripting diagnostics in the Visual Studio tools -->
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Interactive/FSIstrings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fsiIntroPackageSourceUriInfo,"Include package source uri when searching for pack
fsiIntroTextHashloadInfo,"Load the given file(s) as if compiled and referenced"
fsiIntroTextHashtimeInfo,"Toggle timing on/off"
fsiIntroTextHashhelpInfo,"Display help"
fsiIntroTextHashhelpdocInfo,"Display documentation for an identifier, e.g. #help \"List.map\";;"
fsiIntroTextHashquitInfo,"Exit"
fsiIntroTextHashclearInfo,"Clear screen"
fsiIntroTextHeader2commandLine," F# Interactive command line options:"
Expand Down
41 changes: 37 additions & 4 deletions src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,10 @@ type internal FsiCommandLineOptions(fsi: FsiEvaluationSessionHostConfig, argv: s
fsiConsoleOutput.uprintfn """ #time ["on"|"off"];; // %s""" (FSIstrings.SR.fsiIntroTextHashtimeInfo ())
fsiConsoleOutput.uprintfn """ #help;; // %s""" (FSIstrings.SR.fsiIntroTextHashhelpInfo ())

fsiConsoleOutput.uprintfn
""" #help "idn";; // %s"""
(FSIstrings.SR.fsiIntroTextHashhelpdocInfo ())

if tcConfigB.langVersion.SupportsFeature(LanguageFeature.PackageManagement) then
for msg in
dependencyProvider.GetRegisteredDependencyManagerHelpText(
Expand Down Expand Up @@ -2499,7 +2503,7 @@ type internal FsiDynamicCompiler
processContents newState declaredImpls

/// Evaluate the given expression and produce a new interactive state.
member fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger: DiagnosticsLogger, istate, expr: SynExpr) =
member fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger: DiagnosticsLogger, istate, expr: SynExpr, suppressItPrint) =
let tcConfig = TcConfig.Create(tcConfigB, validate = false)
let itName = "it"

Expand All @@ -2513,7 +2517,7 @@ type internal FsiDynamicCompiler
// Snarf the type for 'it' via the binding
match istate.tcState.TcEnvFromImpls.NameEnv.FindUnqualifiedItem itName with
| Item.Value vref ->
if not tcConfig.noFeedback then
if not tcConfig.noFeedback && not suppressItPrint then
let infoReader = InfoReader(istate.tcGlobals, istate.tcImports.GetImportMap())

valuePrinter.InvokeExprPrinter(
Expand Down Expand Up @@ -3724,6 +3728,31 @@ type FsiInteractionProcessor
stopProcessingRecovery e range0
None

let runhDirective diagnosticsLogger ctok istate source =
let lexbuf =
UnicodeLexing.StringAsLexbuf(true, tcConfigB.langVersion, tcConfigB.strictIndentation, $"<@@ {source} @@>")

let tokenizer =
fsiStdinLexerProvider.CreateBufferLexer("hdummy.fsx", lexbuf, diagnosticsLogger)

let parsedInteraction = ParseInteraction tokenizer

match parsedInteraction with
| Some(ParsedScriptInteraction.Definitions([ SynModuleDecl.Expr(e, _) ], _)) ->

let _state, status =
fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger, istate, e, true)

match status with
| Completed(Some compStatus) ->
match compStatus.ReflectionValue with
| :? FSharp.Quotations.Expr as qex ->
let s = FsiHelp.Logic.Quoted.h qex
fsiConsoleOutput.uprintf "%s" s
| _ -> ()
| _ -> ()
| _ -> ()

/// Partially process a hash directive, leaving state in packageManagerLines and required assemblies
let PartiallyProcessHashDirective (ctok, istate, hash, diagnosticsLogger: DiagnosticsLogger) =
match hash with
Expand Down Expand Up @@ -3820,6 +3849,10 @@ type FsiInteractionProcessor
fsiOptions.ShowHelp(m)
istate, Completed None

| ParsedHashDirective("help", ParsedHashDirectiveArguments [ source ], _m) ->
runhDirective diagnosticsLogger ctok istate source
istate, Completed None

| ParsedHashDirective(c, ParsedHashDirectiveArguments arg, m) ->
warning (Error((FSComp.SR.fsiInvalidDirective (c, String.concat " " arg)), m))
istate, Completed None
Expand Down Expand Up @@ -3866,7 +3899,7 @@ type FsiInteractionProcessor
| InteractionGroup.HashDirectives [] -> istate, Completed None

| InteractionGroup.Definitions([ SynModuleDecl.Expr(expr, _) ], _) ->
fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger, istate, expr)
fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger, istate, expr, false)

| InteractionGroup.Definitions(defs, _) ->
fsiDynamicCompiler.EvalParsedDefinitions(ctok, diagnosticsLogger, istate, true, false, defs)
Expand Down Expand Up @@ -4060,7 +4093,7 @@ type FsiInteractionProcessor
|> InteractiveCatch diagnosticsLogger (fun istate ->
istate
|> mainThreadProcessAction ctok (fun ctok istate ->
fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger, istate, expr)))
fsiDynamicCompiler.EvalParsedExpression(ctok, diagnosticsLogger, istate, expr, false)))

let commitResult (istate, result) =
match result with
Expand Down
272 changes: 272 additions & 0 deletions src/Compiler/Interactive/fsihelp.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
module FSharp.Compiler.Interactive.FsiHelp

[<assembly: System.Runtime.InteropServices.ComVisible(false)>]
[<assembly: System.CLSCompliant(true)>]
do ()

open System
open System.Collections.Generic
open System.IO
open System.Text
open System.Reflection
open FSharp.Compiler.IO

module Parser =

open System.Xml

type Help =
{
Summary: string
Remarks: string option
Parameters: (string * string) list
Returns: string option
Exceptions: (string * string) list
Examples: (string * string) list
FullName: string
Assembly: string
}

member this.ToDisplayString() =
let sb = StringBuilder()

let parameters =
this.Parameters
|> List.map (fun (name, description) -> sprintf "- %s: %s" name description)
|> String.concat "\n"

sb.AppendLine().AppendLine("Description:").AppendLine(this.Summary) |> ignore

match this.Remarks with
| Some r -> sb.AppendLine $"\nRemarks:\n%s{r}" |> ignore
| None -> ()

if not (String.IsNullOrWhiteSpace(parameters)) then
sb.AppendLine $"\nParameters:\n%s{parameters}" |> ignore

match this.Returns with
| Some r -> sb.AppendLine $"Returns:\n%s{r}" |> ignore
| None -> ()

if not this.Exceptions.IsEmpty then
sb.AppendLine "\nExceptions:" |> ignore

for (exType, exDesc) in this.Exceptions do
sb.AppendLine $"%s{exType}: %s{exDesc}" |> ignore

if not this.Examples.IsEmpty then
sb.AppendLine "\nExamples:" |> ignore

for example, desc in this.Examples do
sb.AppendLine example |> ignore

if not (String.IsNullOrWhiteSpace(desc)) then
sb.AppendLine $"""// {desc.Replace("\n", "\n// ")}""" |> ignore

sb.AppendLine "" |> ignore

sb.AppendLine $"Full name: %s{this.FullName}" |> ignore
sb.AppendLine $"Assembly: %s{this.Assembly}" |> ignore

sb.ToString()

let cleanupXmlContent (s: string) = s.Replace("\n ", "\n").Trim() // some stray whitespace from the XML

// remove any leading `X:` and trailing `N
let trimDotNet (s: string) =
let s = if s.Length > 2 && s[1] = ':' then s.Substring(2) else s
let idx = s.IndexOf('`')
let s = if idx > 0 then s.Substring(0, idx) else s
s

let xmlDocCache = Dictionary<string, string>()

let tryGetXmlDocument xmlPath =
try
match xmlDocCache.TryGetValue(xmlPath) with
| true, value ->
let xmlDocument = XmlDocument()
xmlDocument.LoadXml(value)
Some xmlDocument
| _ ->
use stream = FileSystem.OpenFileForReadShim(xmlPath)
let rawXml = stream.ReadAllText()
let xmlDocument = XmlDocument()
xmlDocument.LoadXml(rawXml)
xmlDocCache.Add(xmlPath, rawXml)
Some xmlDocument
with _ ->
None

let getTexts (node: Xml.XmlNode) =
seq {
for child in node.ChildNodes do
if child.Name = "#text" then
yield child.Value

if child.Name = "c" then
yield child.InnerText

if child.Name = "see" then
let cref = child.Attributes.GetNamedItem("cref")

if not (isNull cref) then
yield cref.Value |> trimDotNet
}
|> String.concat ""

let tryMkHelp (xmlDocument: XmlDocument option) (assembly: string) (modName: string) (implName: string) (sourceName: string) =
let sourceName = sourceName.Replace('.', '#') // for .ctor
let implName = implName.Replace('.', '#') // for .ctor
let xmlName = $"{modName}.{implName}"

let toTry =
[
$"""/doc/members/member[contains(@name, ":{xmlName}`")]"""
$"""/doc/members/member[contains(@name, ":{xmlName}(")]"""
$"""/doc/members/member[contains(@name, ":{xmlName}")]"""
]

xmlDocument
|> Option.bind (fun xmlDocument ->
seq {
for t in toTry do
let node = xmlDocument.SelectSingleNode(t)
if not (isNull node) then Some node else None
}
|> Seq.tryPick id)
|> function
| None -> ValueNone
| Some n ->
let summary =
n.SelectSingleNode("summary")
|> Option.ofObj
|> Option.map getTexts
|> Option.map cleanupXmlContent

let remarks =
n.SelectSingleNode("remarks")
|> Option.ofObj
|> Option.map getTexts
|> Option.map cleanupXmlContent

let parameters =
n.SelectNodes("param")
|> Seq.cast<XmlNode>
|> Seq.map (fun n -> n.Attributes.GetNamedItem("name").Value.Trim(), n.InnerText.Trim())
|> List.ofSeq

let returns =
n.SelectSingleNode("returns")
|> Option.ofObj
|> Option.map (fun n -> getTexts(n).Trim())

let exceptions =
n.SelectNodes("exception")
|> Seq.cast<XmlNode>
|> Seq.map (fun n ->
let exType = n.Attributes.GetNamedItem("cref").Value
let idx = exType.IndexOf(':')
let exType = if idx >= 0 then exType.Substring(idx + 1) else exType
exType.Trim(), n.InnerText.Trim())
|> List.ofSeq

let examples =
n.SelectNodes("example")
|> Seq.cast<XmlNode>
|> Seq.map (fun n ->
let codeNode = n.SelectSingleNode("code")

let code =
if isNull codeNode then
""
else
n.RemoveChild(codeNode) |> ignore
cleanupXmlContent codeNode.InnerText

code, cleanupXmlContent n.InnerText)
|> List.ofSeq

match summary with
| Some s ->
{
Summary = s
Remarks = remarks
Parameters = parameters
Returns = returns
Exceptions = exceptions
Examples = examples
FullName = $"{modName}.{sourceName}" // the long ident as users see it
Assembly = assembly
}
|> ValueSome
| None -> ValueNone

module Expr =

open Microsoft.FSharp.Quotations.Patterns

let tryGetSourceName (methodInfo: MethodInfo) =
try
let attr = methodInfo.GetCustomAttribute<CompilationSourceNameAttribute>()
Some attr.SourceName
with _ ->
None

let getInfos (declaringType: Type) (sourceName: string option) (implName: string) =
let xmlPath = Path.ChangeExtension(declaringType.Assembly.Location, ".xml")
let xmlDoc = Parser.tryGetXmlDocument xmlPath
let assembly = Path.GetFileName(declaringType.Assembly.Location)

// for FullName cases like Microsoft.FSharp.Core.FSharpOption`1[System.Object]
let fullName =
let idx = declaringType.FullName.IndexOf('[')

if idx >= 0 then
declaringType.FullName.Substring(0, idx)
else
declaringType.FullName

let fullName = fullName.Replace('+', '.') // for FullName cases like Microsoft.FSharp.Collections.ArrayModule+Parallel

(xmlDoc, assembly, fullName, implName, sourceName |> Option.defaultValue implName)

let rec exprNames expr =
match expr with
| Call(exprOpt, methodInfo, _exprList) ->
match exprOpt with
| Some _ -> None
| None ->
let sourceName = tryGetSourceName methodInfo
getInfos methodInfo.DeclaringType sourceName methodInfo.Name |> Some
| Lambda(_param, body) -> exprNames body
| Let(_, _, body) -> exprNames body
| Value(_o, t) -> getInfos t (Some t.Name) t.Name |> Some
| DefaultValue t -> getInfos t (Some t.Name) t.Name |> Some
| PropertyGet(_o, info, _) -> getInfos info.DeclaringType (Some info.Name) info.Name |> Some
| NewUnionCase(info, _exprList) -> getInfos info.DeclaringType (Some info.Name) info.Name |> Some
| NewObject(ctorInfo, _e) -> getInfos ctorInfo.DeclaringType (Some ctorInfo.Name) ctorInfo.Name |> Some
| NewArray(t, _exprs) -> getInfos t (Some t.Name) t.Name |> Some
| NewTuple _ ->
let ty = typeof<_ * _>
getInfos ty (Some ty.Name) ty.Name |> Some
| NewStructTuple _ ->
let ty = typeof<struct (_ * _)>
getInfos ty (Some ty.Name) ty.Name |> Some
| _ -> None

module Logic =

open Expr
open Parser

module Quoted =
let tryGetHelp (expr: Quotations.Expr) =
match exprNames expr with
| Some(xmlDocument, assembly, modName, implName, sourceName) -> tryMkHelp xmlDocument assembly modName implName sourceName
| _ -> ValueNone

let h (expr: Quotations.Expr) =
match tryGetHelp expr with
| ValueNone -> "unable to get documentation\n"
| ValueSome d -> d.ToDisplayString()
Loading