// (c) Microsoft Corporation. All rights reserved //---------------------------------------------------------------------------- // Open up the compiler as an incremental service for lexing, parsing, // type checking and intellisense-like environment-reporting. //-------------------------------------------------------------------------- #light namespace Microsoft.FSharp.Compiler.SourceCodeServices open Internal.Utilities open System open System.IO open System.Text open System.Threading open System.Collections.Generic open Microsoft.FSharp.Text.Printf open Microsoft.FSharp.Compiler.AbstractIL open Microsoft.FSharp.Compiler.AbstractIL.Internal open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler.MSBuildResolver open Microsoft.FSharp.Compiler.AbstractIL.Diagnostics open Microsoft.FSharp.Compiler.PrettyNaming open Internal.Utilities.Collections open Internal.Utilities.Debug open System.Security.Permissions open Microsoft.FSharp.Compiler.Env open Microsoft.FSharp.Compiler.Parser open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.Ast open Microsoft.FSharp.Compiler.ErrorLogger open Microsoft.FSharp.Compiler.Lexhelp open Microsoft.FSharp.Compiler.Build open Microsoft.FSharp.Compiler.Tast open Microsoft.FSharp.Compiler.Tastops open Microsoft.FSharp.Compiler.Lib open Microsoft.FSharp.Compiler.AbstractIL.IL open Microsoft.FSharp.Compiler.Layout open Microsoft.FSharp.Compiler.TypeChecker open Microsoft.FSharp.Compiler.Infos open Microsoft.FSharp.Compiler.Nameres /// Methods for dealing with F# sources files. module SourceFile = /// Source file extensions let private compilableExtensions = Build.sigSuffixes @ Build.implSuffixes @ Build.scriptSuffixes /// Single file projects extensions let private singleFileProjectExtensions = Build.scriptSuffixes /// Whether or not this file is compilable let IsCompilable file = let ext = Path.GetExtension(file) compilableExtensions |> List.exists(fun e->0 = String.Compare(e,ext,StringComparison.OrdinalIgnoreCase)) /// Whether or not this file should be a single-file project let MustBeSingleFileProject file = let ext = Path.GetExtension(file) singleFileProjectExtensions |> List.exists(fun e-> 0 = String.Compare(e,ext,StringComparison.OrdinalIgnoreCase)) /// Whether or not this is an fsi generated by goto metadata. let IsFSharpLanguageServiceGenerated s = let genExtns = [ ".fsi" ] // REVIEW: decide if we like the extension check or if we ought to skip it let extn = Path.GetExtension s if List.mem extn genExtns then let dirName = Path.GetDirectoryName s dirName = FsiGeneration.PathForGeneratedVisualStudioFSharpTempFiles else false /// Additonal #defines that should be in place when editing a file in a file editor /// such as VS. let AdditionalDefinesForUseInEditor(filename) = if Build.IsScript(filename) then ["INTERACTIVE";"EDITING"] else ["COMPILED";"EDITING"] /// Test hooks for tweaking internals module TestHooks = /// Function used to construct memebr info text in data tips. let ConstructMemberInfoText: ((unit->unit)->unit) option ref = ref None let HookScope(command:string,hook:(unit->unit)->unit) : System.IDisposable = match command with | "HookConstructMemberInfoText" -> ConstructMemberInfoText := Some(hook) {new IDisposable with member d.Dispose() = ConstructMemberInfoText := None} | _ -> failwith "Unknown test hook" /// Enable/disable generation of interface file in the 'Find Declaration' /// By default, the feature is turned off in the current version let enableFsiGenerationHook = ref false /// Turns the generation on and returns IDisposable that disables it again let EnableFsiGenerationHook () = let oldFsiGenerationHook = !enableFsiGenerationHook enableFsiGenerationHook := true { new IDisposable with member x.Dispose() = enableFsiGenerationHook := oldFsiGenerationHook } /// This corresponds to a token categorization originally used in Visual Studio 2003. /// /// NOTE: This corresponds to a token categorization originally used in Visual Studio 2003 and the original Babel source code. /// It is not clear it is a primary logical classification that should be being used in the /// more recent language service work. type TokenColorKind = Default = 0 | Text = 0 | Keyword = 1 | Comment = 2 | Identifier = 3 | String = 4 | UpperIdentifier = 5 | InactiveCode = 7 | PreprocessorKeyword = 8 | Number = 9 /// Categorize an action the editor should take in respons to a token, e.g. brace matching /// /// NOTE: This corresponds to a token categorization originally used in Visual Studio 2003 and the original Babel source code. /// It is not clear it is a primary logical classification that should be being used in the /// more recent language service work. type TriggerClass = None = 0x00000000 | MemberSelect = 0x00000001 | MatchBraces = 0x00000002 | ChoiceSelect = 0x00000004 | MethodTip = 0x000000F0 | ParamStart = 0x00000010 | ParamNext = 0x00000020 | ParamEnd = 0x00000040 /// This corresponds to a token categorization originally used in Visual Studio 2003. /// /// NOTE: This corresponds to a token categorization originally used in Visual Studio 2003 and the original Babel source code. /// It is not clear it is a primary logical classification that should be being used in the /// more recent language service work. type TokenCharKind = Default = 0x00000000 | Text = 0x00000000 | Keyword = 0x00000001 | Identifier = 0x00000002 | String = 0x00000003 | Literal = 0x00000004 | Operator = 0x00000005 | Delimiter = 0x00000006 | WhiteSpace = 0x00000008 | LineComment = 0x00000009 | Comment = 0x0000000A /// Information about a particular token from the tokenizer type TokenInformation = { LeftColumn:int; RightColumn:int; ColorClass:TokenColorKind; CharClass:TokenCharKind; TriggerClass:TriggerClass; Tag:int TokenName:string } //---------------------------------------------------------------------------- // Flags //-------------------------------------------------------------------------- module Flags = #if DEBUG let loggingTypes = System.Environment.GetEnvironmentVariable("mFSharp_Logging") let logging = not (String.IsNullOrEmpty(loggingTypes)) let initialLoggingGUITypes = loggingTypes let loggingGUI = not (String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("mFSharp_LogToWinForm"))) let loggingStdOut = not (String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("mFSharp_LogToStdOut"))) #else let loggingTypes = "" let logging = false let initialLoggingGUITypes = "" let loggingGUI = false let loggingStdOut = false #endif let _ = if logging && not loggingGUI && not loggingStdOut then let logFile = ("c:\\fsharp\\log-m"^System.AppDomain.CurrentDomain.FriendlyName^".log") let traceFile = ("c:\\fsharp\\trace-m"^System.AppDomain.CurrentDomain.FriendlyName^".txt") try let log = (File.CreateText logFile :> TextWriter) setDiagnosticsChannel(Some(log)); progress := true; with e-> // Don't kill the language service just because we couldn't log. () if logging then dprintf "Opened log file %s for ML, config follows\n" logFile dprintf "logging types = %s\n" loggingTypes Trace.Log <- loggingTypes Trace.Out <- new StreamWriter(traceFile,append=false,encoding=System.Text.Encoding.UTF8) elif loggingStdOut then Trace.Log <- initialLoggingGUITypes Trace.Out <- System.Console.Out elif loggingGUI then let f = new System.Windows.Forms.Form(Visible=true,TopMost=true,Width=600,Height=600) let rb = new System.Windows.Forms.RichTextBox(Dock=System.Windows.Forms.DockStyle.Fill, Font=new System.Drawing.Font("courier new",8.0f)) f.Controls.Add(rb) rb.DoubleClick.Add(fun _ -> rb.Clear()) let lab = new System.Windows.Forms.Label(Dock=System.Windows.Forms.DockStyle.Top, Font=new System.Drawing.Font("courier new",8.0f)) f.Controls.Add(lab) let tb = new System.Windows.Forms.TextBox(Text=initialLoggingGUITypes,Height=10,Multiline=false,Dock=System.Windows.Forms.DockStyle.Top, Font=new System.Drawing.Font("courier new",8.0f)) f.Controls.Add(tb) tb.TextChanged.Add (fun ev -> Trace.Log <- tb.Text) let log = let addTextOnGuiThread text = if not rb.IsDisposed then rb.AppendText(text); if text.Contains "\n" then rb.ScrollToCaret(); if rb.TextLength > 20000 then let s = rb.Text rb.Text <- s.[s.Length - 15000..s.Length-1] let addText text = if f.InvokeRequired then f.BeginInvoke(new System.Windows.Forms.MethodInvoker(fun () -> addTextOnGuiThread text)) |> ignore else addTextOnGuiThread text { new System.IO.TextWriter() with member x.Write(c:char) = addText (string c) member x.Write(s:string) = addText s member x.Encoding = System.Text.Encoding.Unicode } setDiagnosticsChannel(Some(log)); Trace.Log <- initialLoggingGUITypes Trace.Out <- log else // Would be nice to leave this at whatever channel was originally assigned. // This currently defeats NUnit's ability to capture logging output. setDiagnosticsChannel(None) (* VS does not support stderr! *) /// Reads the flag specifying whether interface generation is enabled in the 'Go To Definition' feature let enableInterfaceGeneration() = !TestHooks.enableFsiGenerationHook //let stripFSharpCoreReferences = not (String.IsNullOrEmpty(System.Environment.GetEnvironmentVariable("mFSharp_StripFSharpCoreReferences"))) let GetEnvInteger e dflt = match System.Environment.GetEnvironmentVariable(e) with null -> dflt | t -> try int t with _ -> dflt let definesCacheSize = GetEnvInteger "mFSharp_DefinesCacheSize" 5 let buildCacheSize = GetEnvInteger "mFSharp_BuildCacheSize" 3 let recentForgroundTypeCheckLookupSize = GetEnvInteger "mFSharp_RecentForegroundTypeCheckCacheSize" 5 let braceMatchCacheSize = GetEnvInteger "mFSharp_BraceMatchCacheSize" 5 let getDataTipTextCache = GetEnvInteger "mFSharp_GetDataTipTextCache" 20 let maxErrorsOutOfProjectContext = GetEnvInteger "mFSharp_MaxErrorsOutOfProjectContext" 3 open Flags //---------------------------------------------------------------------------- // Babel flags //-------------------------------------------------------------------------- module TokenClassifications = //---------------------------------------------------------------------------- //From tokens to flags //-------------------------------------------------------------------------- let tokenInfo token = match token with | IDENT s -> if System.Char.ToUpperInvariant s.[0] = s.[0] then (TokenColorKind.UpperIdentifier,TokenCharKind.Identifier,TriggerClass.None) else (TokenColorKind.Identifier,TokenCharKind.Identifier,TriggerClass.None) | DECIMAL _ | BIGNUM _ | INT8 _ | UINT8 _ | INT16 _ | UINT16 _ | INT32 _ | UINT32 _ | INT64 _ | UINT64 _ | UNATIVEINT _ | NATIVEINT _ | IEEE32 _ | IEEE64 _ -> (TokenColorKind.Number,TokenCharKind.Literal,TriggerClass.None) | INT32_DOT_DOT _ // This will color the whole "1.." expression in a 'number' color // (this isn't entirely correct, but it'll work for now - see bug 3727) -> (TokenColorKind.Number,TokenCharKind.Operator,TriggerClass.None) | INFIX_STAR_DIV_MOD_OP ("mod" | "land" | "lor" | "lxor") | INFIX_STAR_STAR_OP ("lsl" | "lsr" | "asr") -> (TokenColorKind.Keyword,TokenCharKind.Keyword,TriggerClass.None) | LPAREN_STAR_RPAREN | DOLLAR | INFIX_STAR_STAR_OP _ | INFIX_COMPARE_OP _ | COLON_GREATER | COLON_COLON | PERCENT_OP _ | INFIX_AT_HAT_OP _ | INFIX_BAR_OP _ | PLUS_MINUS_OP _ | PREFIX_OP _ | COLON_QMARK_GREATER | INFIX_STAR_DIV_MOD_OP _ | INFIX_AMP_OP _ | AMP | AMP_AMP | BAR_BAR | LESS | GREATER | QMARK | QMARK_QMARK | COLON_QMARK | DOT_DOT | QUOTE | STAR | HIGH_PRECEDENCE_TYAPP | COLON | COLON_EQUALS | LARROW | EQUALS | GREATER_DOT | RQUOTE_DOT _ | MINUS | ADJACENT_PREFIX_PLUS_MINUS_OP _ | FUNKY_OPERATOR_NAME _ -> (TokenColorKind.Text,TokenCharKind.Operator,TriggerClass.None) | COMMA -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.None) | DOT -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.MemberSelect) | BAR -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.None (* TriggerClass.ChoiceSelect *)) | HASH | UNDERSCORE | SEMICOLON | SEMICOLON_SEMICOLON -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.None) | LPAREN // We need 'ParamStart' to trigger the 'GetDeclarations' method to show param info automatically // this is needed even if we don't use MPF for determining information about params -> (TokenColorKind.Text,TokenCharKind.Delimiter, TriggerClass.ParamStart ||| TriggerClass.MatchBraces) | RPAREN -> (TokenColorKind.Text,TokenCharKind.Delimiter,(* TriggerClass.ParamEnd; ||| *) TriggerClass.MatchBraces ) | SPLICE_SYMBOL _ | LBRACK_LESS | LBRACE_LESS -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.None ) | LQUOTE _ | LBRACK | LBRACE | LBRACK_BAR -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.MatchBraces ) | GREATER_RBRACE | GREATER_RBRACK | GREATER_BAR_RBRACK -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.None ) | RQUOTE _ | RBRACK | RBRACE | BAR_RBRACK -> (TokenColorKind.Text,TokenCharKind.Delimiter,TriggerClass.MatchBraces ) | PUBLIC | PRIVATE | INTERNAL | BASE | CONSTRAINT | INSTANCE | DELEGATE | INHERIT|CONSTRUCTOR|DEFAULT|OVERRIDE|ABSTRACT|VIRTUAL|CLASS | MEMBER | STATIC | NAMESPACE | ODECLEND | OBLOCKSEP | OEND | OBLOCKBEGIN | ORIGHT_BLOCK_END | OBLOCKEND | OTHEN | OELSE | OLET(_) | OBINDER _ | BINDER _ | ODO | OWITH | OFUNCTION | OFUN | ORESET | ODUMMY _ | DO_BANG | ODO_BANG | YIELD _ | YIELD_BANG _ | OINTERFACE_MEMBER | ELIF | RARROW | RARROW2 | SIG | STRUCT | UPCAST | DOWNCAST | NULL | RESERVED | MODULE | AND | AS | ASSERT | ASR | DOWNTO | EXCEPTION | FALSE | FOR | FUN | FUNCTION | FINALLY | LAZY | MATCH | METHOD | MUTABLE | NEW | OF | OPEN | OR | VOID | EXTERN | INTERFACE | REC | TO | TRUE | TRY | TYPE | VAL | INLINE | WHEN | WHILE | WITH | IF | THEN | ELSE | DO | DONE | LET(_) | IN (*| NAMESPACE*) | HIGH_PRECEDENCE_APP -> (TokenColorKind.Keyword,TokenCharKind.Keyword,TriggerClass.None) | BEGIN -> (TokenColorKind.Keyword,TokenCharKind.Keyword,TriggerClass.None) | END -> (TokenColorKind.Keyword,TokenCharKind.Keyword,TriggerClass.None) | HASH_LIGHT _ | HASH_LINE _ | HASH_IF _ | HASH_ELSE _ | HASH_ENDIF _ -> (TokenColorKind.PreprocessorKeyword,TokenCharKind.WhiteSpace,TriggerClass.None) | INACTIVECODE _ -> (TokenColorKind.InactiveCode,TokenCharKind.WhiteSpace,TriggerClass.None) | LEX_FAILURE _ | WHITESPACE _ -> (TokenColorKind.Default,TokenCharKind.WhiteSpace,TriggerClass.None) | COMMENT _ -> (TokenColorKind.Comment,TokenCharKind.Comment,TriggerClass.None) | LINE_COMMENT _ -> (TokenColorKind.Comment,TokenCharKind.LineComment,TriggerClass.None) | STRING_TEXT _ -> (TokenColorKind.String,TokenCharKind.String,TriggerClass.None) | BYTEARRAY _ | STRING _ | CHAR _ (* bug://2863 asks to color 'char' as "string" *) -> (TokenColorKind.String,TokenCharKind.String,TriggerClass.None) | EOF _ -> failwith "tokenInfo" module TestExpose = let TokenInfo tok = TokenClassifications.tokenInfo tok //---------------------------------------------------------------------------- // Lexer states encoded to/from integers //-------------------------------------------------------------------------- type LexState = int64 type ColorState = | Token = 1 | IfDefSkip = 3 | String = 4 | Comment = 5 | CommentString = 6 | CommentVString = 7 | CamlOnly = 8 | VString = 9 | TokenizedComment = 10 | EndLineThenSkip = 11 | EndLineThenToken = 12 | InitialState = 0 module LexerStateEncoding = let computeNextLexState token (prevLexcont:lexcont) = match token with | HASH_LINE s | HASH_LIGHT s | HASH_IF(_, _, s) | HASH_ELSE(_, _, s) | HASH_ENDIF(_, _, s) | INACTIVECODE s | WHITESPACE s | COMMENT s | LINE_COMMENT s | STRING_TEXT s | EOF s -> s | BYTEARRAY _ | STRING _ -> AT_token(prevLexcont.IfdefStack) | _ -> prevLexcont // Note that this will discard all lexcont state, including the ifdefStack. let revert_to_default_lexcont = AT_token [] let resize32 (i:int32) : LexState = ((int64)i) let lexstate_nbits = 4 let ncomments_nbits = 2 let start_pos_nbits = Range.pos_nbits let hardwhite_nbits = 1 let ifdefstack_count_nbits = 4 let ifdefstack_nbits = 16 // 0 means if, 1 means else let _ = assert (lexstate_nbits + ncomments_nbits + start_pos_nbits + hardwhite_nbits + ifdefstack_count_nbits + ifdefstack_nbits <= 64) let lexstate_start = 0 let ncomments_start = lexstate_nbits let start_pos_start = lexstate_nbits+ncomments_nbits let hardwhite_pos_start = lexstate_nbits+ncomments_nbits+start_pos_nbits let ifdefstack_count_start = lexstate_nbits+ncomments_nbits+start_pos_nbits+hardwhite_nbits let ifdefstack_start = lexstate_nbits+ncomments_nbits+start_pos_nbits+hardwhite_nbits+ifdefstack_count_nbits let lexstate_mask = Bits.mask64 lexstate_start lexstate_nbits let ncomments_mask = Bits.mask64 ncomments_start ncomments_nbits let start_pos_mask = Bits.mask64 start_pos_start start_pos_nbits let hardwhite_pos_mask = Bits.mask64 hardwhite_pos_start hardwhite_nbits let ifdefstack_count_mask = Bits.mask64 ifdefstack_count_start ifdefstack_count_nbits let ifdefstack_mask = Bits.mask64 ifdefstack_start ifdefstack_nbits let bits_of_bool b = if b then 1 else 0 let bool_of_bits n = (n = 1L) let encode_lexcont (colorState:ColorState) ncomments (b:pos) ifdefStack light = let mutable ifdefStackCount = 0 let mutable ifdefStackBits = 0 for ifOrElse in ifdefStack do match ifOrElse with | (IfDefIf,_) -> () | (IfDefElse,_) -> ifdefStackBits <- (ifdefStackBits ||| (1 <<< ifdefStackCount)) ifdefStackCount <- ifdefStackCount + 1 let lexstate = int64 colorState ((lexstate <<< lexstate_start) &&& lexstate_mask) ||| ((ncomments <<< ncomments_start) &&& ncomments_mask) ||| ((resize32 (bits_of_pos b) <<< start_pos_start) &&& start_pos_mask) ||| ((resize32 (bits_of_bool light) <<< hardwhite_pos_start) &&& hardwhite_pos_mask) ||| ((resize32 ifdefStackCount <<< ifdefstack_count_start) &&& ifdefstack_count_mask) ||| ((resize32 ifdefStackBits <<< ifdefstack_start) &&& ifdefstack_mask) let decode_lexcont (state:LexState) = let mutable ifDefs = [] let ifdefStackCount = (int32) ((state &&& ifdefstack_count_mask) >>> ifdefstack_count_start) if ifdefStackCount>0 then let ifdefStack = (int32) ((state &&& ifdefstack_mask) >>> ifdefstack_start) for i in 1..ifdefStackCount do let bit = ifdefStackCount-i let mask = 1 <<< bit let ifDef = (if ifdefStack &&& mask = 0 then IfDefIf else IfDefElse) ifDefs<-(ifDef,range0)::ifDefs enum (int32 ((state &&& lexstate_mask) >>> lexstate_start)), (int32) ((state &&& ncomments_mask) >>> ncomments_start), pos_of_bits ((int32)((state &&& start_pos_mask) >>> start_pos_start)), ifDefs, bool_of_bits ((state &&& hardwhite_pos_mask) >>> hardwhite_pos_start) let encode_lexint lightSyntaxStatus (lexcont:lexcont) = let tag,n1,p1,ifd = match lexcont with | AT_token ifd -> ColorState.Token,0L,pos0,ifd | AT_ifdef_skip (ifd,n,m) -> ColorState.IfDefSkip,resize32 n,start_of_range m,ifd | AT_endline(ENDL_skip(ifd,n,m)) -> ColorState.EndLineThenSkip,resize32 n,start_of_range m,ifd | AT_endline(ENDL_token(ifd)) -> ColorState.EndLineThenToken,0L,pos0,ifd | AT_string (ifd,m) -> ColorState.String,0L,start_of_range m,ifd | AT_comment (ifd,n,m) -> ColorState.Comment,resize32 n,start_of_range m,ifd | AT_tokenized_comment (ifd,n,m) -> ColorState.TokenizedComment,resize32 n,start_of_range m,ifd | AT_comment_string (ifd,n,m) -> ColorState.CommentString,resize32 n,start_of_range m,ifd | AT_comment_vstring (ifd,n,m) -> ColorState.CommentVString,resize32 n,start_of_range m,ifd | AT_camlonly (ifd,m) -> ColorState.CamlOnly,0L,start_of_range m,ifd | AT_vstring (ifd,m) -> ColorState.VString,0L,start_of_range m,ifd encode_lexcont tag n1 p1 ifd lightSyntaxStatus let decode_lexint (state:LexState) = let tag,n1,p1,ifd,lightSyntaxStatusInital = decode_lexcont state let lexcont = match tag with | ColorState.Token -> AT_token ifd | ColorState.IfDefSkip -> AT_ifdef_skip (ifd,n1,mk_range "file" p1 p1) | ColorState.String -> AT_string (ifd,mk_range "file" p1 p1) | ColorState.Comment -> AT_comment (ifd,n1,mk_range "file" p1 p1) | ColorState.TokenizedComment -> AT_tokenized_comment (ifd,n1,mk_range "file" p1 p1) | ColorState.CommentString -> AT_comment_string (ifd,n1,mk_range "file" p1 p1) | ColorState.CommentVString -> AT_comment_vstring (ifd,n1,mk_range "file" p1 p1) | ColorState.CamlOnly -> AT_camlonly (ifd,mk_range "file" p1 p1) | ColorState.VString -> AT_vstring (ifd,mk_range "file" p1 p1) | ColorState.EndLineThenSkip -> AT_endline(ENDL_skip(ifd,n1,mk_range "file" p1 p1)) | ColorState.EndLineThenToken -> AT_endline(ENDL_token(ifd)) | _ -> AT_token [] lightSyntaxStatusInital,lexcont let call_lexcont lexcont args skip lexbuf = let argsWithIfDefs ifd = {args with ifdefStack = ref ifd} match lexcont with | AT_endline(cont) -> Lexer.endline cont args skip lexbuf | AT_token ifd -> Lexer.token (argsWithIfDefs ifd) skip lexbuf | AT_ifdef_skip (ifd,n,m) -> Lexer.ifdef_skip n m (argsWithIfDefs ifd) skip lexbuf //v-- What's this magic number for? // answer: it's just an initial buffer size. | AT_string (ifd,m) -> Lexer.string (Bytes.Bytebuf.create 100,default_string_finish,m,(argsWithIfDefs ifd)) skip lexbuf | AT_comment (ifd,n,m) -> Lexer.comment(n,m,(argsWithIfDefs ifd)) skip lexbuf | AT_tokenized_comment (ifd,n,m)-> // The first argument is 'None' because we don't need XML comments when called from VS Lexer.tokenized_comment(None,n,m,(argsWithIfDefs ifd)) skip lexbuf | AT_comment_string (ifd,n,m) -> Lexer.comment_string n m (argsWithIfDefs ifd) skip lexbuf | AT_comment_vstring (ifd,n,m) -> Lexer.comment_vstring n m (argsWithIfDefs ifd) skip lexbuf | AT_camlonly (ifd,m) -> Lexer.camlonly m (argsWithIfDefs ifd) skip lexbuf | AT_vstring (ifd,m) -> Lexer.vstring (Bytes.Bytebuf.create 100,default_string_finish,m,(argsWithIfDefs ifd)) skip lexbuf //---------------------------------------------------------------------------- // Colorization //---------------------------------------------------------------------------- // Information beyond just tokens that can be derived by looking at just a single line. // For example metacommands like #load. type SingleLineTokenState = | BeforeHash = 0 | NoFurtherMatchPossible = 1 /// Split a line into tokens and attach information about the tokens. This information is used by Visual Studio. [] type Tokenizer(text:string, defineConstants : string list, filename : string) = let skip = false // don't skip whitespace in the lexer let concreteSyntaxSink = None let lexResourceManager = new Lexhelp.LexResourceManager() let lexbuf = UnicodeLexing.StringAsLexbuf text let mutable singleLineTokenState = SingleLineTokenState.BeforeHash let fsx = Build.IsScript(filename) // ---------------------------------------------------------------------------------- // This implements post-processing of #directive tokens - not very elegant, but it works... // We get the whole " #if IDENT // .. .. " thing as a single token from the lexer, // so we need to split it into tokens that are used by VS for colorization // Stack for tokens that are split during postrpocessing let mutable tokenStack = new Stack<_>() let delayToken tok = tokenStack.Push(tok) // Process: anywhite* # let processDirective (str:string) directiveLength delay cont = let hashIdx = str.IndexOf("#") if (hashIdx <> 0) then delay(WHITESPACE cont, 0, hashIdx - 1) delay(HASH_IF(range0, "", cont), hashIdx, hashIdx + directiveLength) hashIdx + directiveLength + 1 // Process: anywhite* ("//" [^'\n''\r']*)? let processWhiteAndComment (str:string) offset delay cont = let rest = str.Substring(offset, str.Length - offset) let comment = rest.IndexOf('/') let spaceLength = if comment = -1 then rest.Length else comment if (spaceLength > 0) then delay(WHITESPACE cont, offset, offset + spaceLength - 1) if (comment <> -1) then delay(COMMENT(cont), offset + comment, offset + rest.Length - 1) // Split a directive line from lexer into tokens usable in VS let processDirectiveLine ofs f = let delayed = new ResizeArray<_>() f (fun (tok, s, e) -> delayed.Add (tok, s + ofs, e + ofs) ) // delay all the tokens and return the remaining one for i = delayed.Count - 1 downto 1 do delayToken delayed.[i] delayed.[0] // Split the following line: // anywhite* ("#else"|"#endif") anywhite* ("//" [^'\n''\r']*)? let processHashEndElse ofs (str:string) length cont = processDirectiveLine ofs (fun delay -> // Process: anywhite* "#else" / anywhite* "#endif" let offset = processDirective str length delay cont // Process: anywhite* ("//" [^'\n''\r']*)? processWhiteAndComment str offset delay cont ) // Split the following line: // anywhite* "#if" anywhite+ ident anywhite* ("//" [^'\n''\r']*)? let processHashIfLine ofs (str:string) cont = let ( $? ) n m = if (n < 0) then m else n processDirectiveLine ofs (fun delay -> // Process: anywhite* "#if" let offset = processDirective str 2 delay cont // Process: anywhite+ ident let rest, spaces = (let w = str.Substring(offset) let r = w.TrimStart([| ' '; '\t' |]) r, w.Length - r.Length) let beforeIdent = offset + spaces let identLength = rest.IndexOfAny([| '/'; '\t'; ' ' |]) $? rest.Length delay(WHITESPACE cont, offset, beforeIdent - 1) delay(IDENT(rest.Substring(0, identLength)), beforeIdent, beforeIdent + identLength - 1) // Process: anywhite* ("//" [^'\n''\r']*)? let offset = beforeIdent + identLength processWhiteAndComment str offset delay cont ) // ---------------------------------------------------------------------------------- // We don't need any error reporting for tokenzier here - // the errors are reported with the wrong range anyway! let noReportLogger = { new ErrorLogger with member x.WarnSink (e:exn) = () member x.ErrorSink (e:exn) = () member x.ErrorCount = 0 } do resetLexbufPos filename lexbuf member t.StartNewLine() = singleLineTokenState <- SingleLineTokenState.BeforeHash member x.ScanToken(lexintInitial) : Option * LexState = // Crack the whitespace syntax flag // Ideally we would explicitly install DiscardErrorsLogger here. However different threads are using the error logger! // Indeed we really have to move to a model where the error logger is passed explicitly everywhere in the codebase. // For the moment we jsut make the default global error logger discard errors. //use unwind = InstallGlobalErrorLogger (fun _ -> DiscardErrorsLogger) let lightSyntaxStatusInital, lexcontInitial = LexerStateEncoding.decode_lexint lexintInitial let lightSyntaxStatus = LightSyntaxStatus(lightSyntaxStatusInital,false) // Build the arguments to the lexer function let dummySourceDirectoryFun = fun()->"C:\\" // the tokenizer doesn't need the value of __SOURCE_DIRECTORY__, so any value is fine let lexargs = mkLexargs(dummySourceDirectoryFun,filename,defineConstants,lightSyntaxStatus,lexResourceManager, ref [],noReportLogger) let GetTokenWithPosition(lexcontInitial) = // Column of token let ColumnsOfCurrentToken() = let leftp = lexbuf.StartPos let rightp = lexbuf.EndPos let leftc = leftp.Column let rightc = if rightp.Line > leftp.Line then text.Length else rightp.Column let rightc = rightc - 1 leftc,rightc // Get the token & position - either from a stack or from the lexer try if (tokenStack.Count > 0) then true, tokenStack.Pop() else // Choose which lexer entrypoint to call and call it let token = LexerStateEncoding.call_lexcont lexcontInitial lexargs skip lexbuf let leftc, rightc = ColumnsOfCurrentToken() // Splits tokens like ">." into multiple tokens - this duplicates behavior from the 'lexfilter' // which cannot be (easily) used from the langauge service. The rules here are not always valid, // because sometimes token shouldn't be split. However it is just for colorization & // for VS (which needs to recognize when user types ".") - note, this doesn't show any intellisense // if you have custom operator (e.g. ">.>", because there won't be any info available) match token with | HASH_IF(m, lineStr, cont) when lineStr <> "" -> false, processHashIfLine (start_col_of_range m) lineStr cont | HASH_ELSE(m, lineStr, cont) when lineStr <> "" -> false, processHashEndElse (start_col_of_range m) lineStr 4 cont | HASH_ENDIF(m, lineStr, cont) when lineStr <> "" -> false, processHashEndElse (start_col_of_range m) lineStr 5 cont | GREATER_DOT -> delayToken(DOT, leftc + 1, rightc) false, (GREATER, leftc, rightc - 1) | RQUOTE_DOT (s,raw) -> delayToken(DOT, rightc, rightc) false, (RQUOTE (s,raw), leftc, rightc - 1) | INFIX_COMPARE_OP (Lexfilter.TyparsCloseOp(greaters,afterOp) as opstr) -> match afterOp with | None -> () | Some tok -> delayToken(tok, leftc + greaters.Length, rightc) for i = greaters.Length - 1 downto 1 do delayToken(greaters.[i], leftc + i, rightc - opstr.Length + i + 1) false, (greaters.[0], leftc, rightc - opstr.Length + 1) | _ -> false, (token, leftc, rightc) with | e -> false, (EOF LexerStateEncoding.revert_to_default_lexcont, 0, 0) // REVIEW: report lex failure here // Grab a token let isCached, (token, leftc, rightc) = GetTokenWithPosition(lexcontInitial) // Check for end-of-string and failure let tokenDataOption, lexcontFinal, tokenTag = match token with | EOF lexcont -> // End of text! No more tokens. None,lexcont,0 | LEX_FAILURE s -> // REVIEW: report this error Trace.PrintLine("Lexing", fun _ -> sprintf "LEX_FAILURE:%s\n" s) None, LexerStateEncoding.revert_to_default_lexcont, 0 | _ -> // Get the information about the token let (colorClass,charClass,triggerClass) = TokenClassifications.tokenInfo token let lexcontFinal = // If we're using token from cache, we don't move forward with lexing if isCached then lexcontInitial else LexerStateEncoding.computeNextLexState token lexcontInitial let tokenTag = tagOfToken token let name = (token_to_string token).ToLower(Globalization.CultureInfo.InvariantCulture); let tokenData = {TokenName = name; LeftColumn=leftc; RightColumn=rightc;ColorClass=colorClass;CharClass=charClass;TriggerClass=triggerClass;Tag=tokenTag} Some(tokenData), lexcontFinal, tokenTag // Get the final lex int and color state let FinalState(lexcontFinal) = LexerStateEncoding.encode_lexint lightSyntaxStatus.Status lexcontFinal // Check for patterns like #-IDENT and see if they look like meta commands for .fsx files. If they do then merge them into a single token. let tokenDataOption,lexintFinal = let lexintFinal = FinalState(lexcontFinal) match tokenDataOption, singleLineTokenState, tokenTagToTokenId tokenTag with | Some(tokenData), SingleLineTokenState.BeforeHash, TOKEN_HASH -> // Don't allow further matches. singleLineTokenState <- SingleLineTokenState.NoFurtherMatchPossible // Peek at the next token let isCached, (nextToken, _, rightc) = GetTokenWithPosition(lexcontInitial) match nextToken with | IDENT possibleMetacommand -> match fsx,possibleMetacommand with // These are for script (.fsx and .fsscript) files. | true,"r" | true,"reference" | true,"I" | true,"load" | true,"time" | true,"cd" #if DEBUG | true,"terms" | true,"types" | true,"savedll" | true,"nosavedll" #endif | true,"silentCd" | true,"q" | true,"quit" | true,"help" // These are for script and non-script | _,"nowarn" -> // Merge both tokens into one. let lexcontFinal = if (isCached) then lexcontInitial else LexerStateEncoding.computeNextLexState token lexcontInitial let tokenTag = tagOfToken token let tokenData = {tokenData with RightColumn=rightc;ColorClass=TokenColorKind.PreprocessorKeyword;CharClass=TokenCharKind.Keyword;TriggerClass=TriggerClass.None} let lexintFinal = FinalState(lexcontFinal) Some(tokenData),lexintFinal | _ -> tokenDataOption,lexintFinal | _ -> tokenDataOption,lexintFinal | _, SingleLineTokenState.BeforeHash, TOKEN_WHITESPACE -> // Allow leading whitespace. tokenDataOption,lexintFinal | _ -> singleLineTokenState <- SingleLineTokenState.NoFurtherMatchPossible tokenDataOption,lexintFinal tokenDataOption, lexintFinal type Severity = Warning | Error type ErrorInfo = { StartLine:int EndLine:int StartColumn:int EndColumn:int Severity:Severity Message:string } with override e.ToString()= sprintf "(%d,%d)-(%d,%d) %s %s" e.StartLine e.StartColumn e.EndLine e.EndColumn (if e.Severity=Warning then "warning" else "error") e.Message /// Decompose a warning or error into parts: position, severity, message static member CreateFromExceptionAndAdjustEof(exn,warn,trim:bool,fallbackRange:range, (linesCount:int, lastLength:int)) = let r = ErrorInfo.CreateFromException(exn,warn,trim,fallbackRange) // Adjust to make sure that errors reported at Eof are shown at the linesCount let startline, schange = min (r.StartLine, false) (linesCount, true) let endline, echange = min (r.EndLine, false) (linesCount, true) if not (schange || echange) then r else let r = if schange then { r with StartLine = startline; StartColumn = lastLength } else r if echange then { r with EndLine = endline; EndColumn = 1 + lastLength } else r /// Decompose a warning or error into parts: position, severity, message static member CreateFromException(exn,warn,trim:bool,fallbackRange:range) = let adjust p = (line_of_pos p - 1),(col_of_pos p) let m = match RangeOfError exn with Some m -> m | None -> fallbackRange let (s1:int),(s2:int) = adjust (start_of_range m) let (s3:int),(s4:int) = adjust (if trim then start_of_range m else end_of_range m); let msg = bufs (fun buf -> OutputException buf exn false) {StartLine=s1; StartColumn=s2; EndLine=s3; EndColumn=s4; Severity=(if warn then Warning else Error); Message=msg} /// Create an error info from individual parts static member CreateFromMessage(severity, message) = {StartLine=0; StartColumn=0; EndLine=0; EndColumn=0; Severity=severity; Message=message} /// Use to reset error and warning handlers type ErrorScope() as x = let mutable errors = [] static let mutable mostRecentError = None let unwind = InstallGlobalErrorLogger (fun oldLogger -> { new ErrorLogger with member x.WarnSink(exn) = errors <- ErrorInfo.CreateFromException(exn,true,false,0L):: errors member x.ErrorSink(exn) = let err = ErrorInfo.CreateFromException(exn,false,false,0L) errors <- err :: errors mostRecentError <- Some(err) member x.ErrorCount = errors.Length }) member x.Errors = errors |> List.filter (fun error -> error.Severity = Error) member x.Warnings = errors |> List.filter (fun error -> error.Severity = Warning) member x.ErrorsAndWarnings = errors member x.TryGetFirstErrorText() = match x.Errors with | error :: rest -> Some(error.Message) | [] -> None interface IDisposable with member d.Dispose() = unwind.Dispose() static member MostRecentError = mostRecentError static member Protect<'a> (m:range) (f:unit->'a) (err:string->'a) : 'a = use errorScope = new ErrorScope() let res = try Some(f()) with e -> errorRecovery e m; None match res with | Some(res) ->res | None -> match errorScope.TryGetFirstErrorText() with | Some text -> err text | None -> err "" static member ProtectWithDefault m f dflt = ErrorScope.Protect m f (fun _ -> dflt) static member ProtectAndDiscard m f = ErrorScope.Protect m f (fun _ -> ()) //---------------------------------------------------------------------------- // Display characteristics of typechecking items //-------------------------------------------------------------------------- (* First of Pair: text Second of Pair: Option - if present the text for latter is appended by the VS C++ code because it queries a VS C++ API *) type TextWithXmlSig = string * (string * string) option /// Interface that defines methods for comparing objects using partial equality relation type IPartialEqualityComparer<'a> = inherit IEqualityComparer<'a> /// Can the specified object be tested for equality? abstract InEqualityRelation : 'a -> bool type iDeclarationSet = int module ItemDescriptions = // Hardwired constants from older versions of Visual Studio. These constants were used with Babel and VS internals. let iIconGroupClass = 0x0000 let iIconGroupConstant = 0x0001 let iIconGroupDelegate = 0x0002 let iIconGroupEnum = 0x0003 let iIconGroupEnumMember = 0x0004 let iIconGroupEvent = 0x0005 let iIconGroupException = 0x0006 let iIconGroupFieldBlue = 0x0007 let iIconGroupInterface = 0x0008 // Absolute = 48 let iIconGroupTextLine = 0x0009 let iIconGroupScript = 0x000a let iIconGroupScript2 = 0x000b let iIconGroupMethod = 0x000c let iIconGroupMethod2 = 0x000d let iIconGroupModule = 0x000e let iIconGroupNameSpace = 0x000f // Absolute = 90 let iIconGroupFormula = 0x0010 let iIconGroupProperty = 0x00011 let iIconGroupStruct = 0x00012 let iIconGroupTemplate = 0x00013 let iIconGroupTypedef = 0x00014 let iIconGroupType = 0x00015 let iIconGroupUnion = 0x00016 let iIconGroupVariable = 0x00017 let iIconGroupValueType = 0x00018 // Absolute = 144 let iIconGroupIntrinsic = 0x00019 let iIconGroupError = 0x0001f let iIconGroupFieldYellow = 0x0020 let iIconGroupMisc1 = 0x00021 let iIconGroupMisc2 = 0x0022 let iIconGroupMisc3 = 0x00023 let iIconItemPublic = 0x0000 let iIconItemInternal = 0x0001 let iIconItemSpecial = 0x0002 let iIconItemProtected = 0x0003 let iIconItemPrivate = 0x0004 let iIconItemShortCut = 0x0005 let iIconItemNormal = iIconItemPublic let iIconBlackBox = 162 let iIconLibrary = 163 let iIconProgram = 164 let iIconWebProgram = 165 let iIconProgramEmpty = 166 let iIconWebProgramEmpty = 167 let iIconComponents = 168 let iIconEnvironment = 169 let iIconWindow = 170 let iIconFolderOpen = 171 let iIconFolder = 172 let iIconArrowRight = 173 let iIconAmbigious = 174 let iIconShadowClass = 175 let iIconShadowMethodPrivate = 176 let iIconShadowMethodProtected = 177 let iIconShadowMethod = 178 let iIconInCompleteSource = 179 let is_function g typ = let tps,tau = try_dest_forall_typ g typ is_fun_typ g tau let boutput_xmldoc os (XmlDoc s) = match s with | [| |] -> () | l -> bprintf os "\n"; l |> Array.iter (fun (s:string) -> // Bug FSharp1.0:3232: replace some common XmlDoc characters let s = s.Replace("","").Replace("","").Replace("<","<").Replace(">",">").Replace("&","&") bprintf os "\n%s" s) let boutput_fullname ppF fnF os r = match ppF r with | None -> () | Some _ -> bprintf os "\n\nFull name: %s" (fnF r) // Format the supertypes and other useful information about a type to a buffer let OutputUsefulTypeInfo (infoReader:InfoReader) m denv os ty = let g = infoReader.g let amap = infoReader.amap let GetSuperTypes() = let supertypes = AllSuperTypesOfType g amap m ty let supertypes = supertypes |> List.filter (AccessibilityLogic.IsTypeAccessible g AccessibleFromSomewhere) let supertypes = supertypes |> List.filter (type_equiv g g.obj_ty >> not) let selfs,supertypes = supertypes |> List.partition (type_equiv g ty) let supertypesC,supertypesI = supertypes |> List.partition (is_interface_typ g) let supertypes = selfs @ supertypesC @ supertypesI supertypes let ConstructUsefulTypeInfo() = let supertypes = GetSuperTypes() let supertypeLs,_ = NicePrint.typesAndConstraintsL denv supertypes if List.length supertypes > 1 then bprintf os "\n\n"; List.zip supertypes supertypeLs |> List.iter (fun (superty,supertyL) -> if type_equiv g superty ty then bprintf os " type: %a\n" bufferL supertyL elif is_class_typ g superty or is_interface_typ g ty then bprintf os " inherits: %a\n" bufferL supertyL else bprintf os " implements: %a\n" bufferL supertyL) if is_enum_typ g ty then bprintf os "\n"; infoReader.GetILFieldInfosOfType(None,AccessibleFromEverywhere,m,ty) |> List.filter (fun finfo -> finfo.IsStatic ) |> List.filter (AccessibilityLogic.IsILFieldInfoAccessible g amap m AccessibleFromSomewhere) |> List.iter (fun finfo -> bprintf os "\nconst %a.%s" (NicePrint.output_tref denv) finfo.ILTypeRef finfo.FieldName; match finfo.LiteralValue with | None -> () | Some v -> try bprintf os " = %s" (Layout.showL ( NicePrint.constL (TypeChecker.TcFieldInit m v) )) with _ -> ()) ErrorScope.ProtectAndDiscard m (fun () -> ConstructUsefulTypeInfo()) let range_of_pinfo (pinfo:PropInfo) = pinfo.ArbitraryValRef |> Option.map (fun v -> v.Range) let range_of_minfo (minfo:MethInfo) = minfo.ArbitraryValRef |> Option.map (fun v -> v.Range) let range_of_item g isDecl d = // skip all default generated constructors for structs let (|FilterDefaultStructCtors|) ctors = ctors |> List.filter (function DefaultStructCtor _ -> false | _ -> true) match d with | Item_val vref -> Some(if isDecl then vref.Range else vref.DefinitionRange) | Item_ucase(ucr) -> Some(ucr.UnionCase.Range) | Item_apelem(apref) -> Some(apref.ActivePatternVal.Range) | Item_ecref(ecr) -> Some(ecr.Range) | Item_recdfield(rfinfo) -> Some(rfinfo.RecdFieldRef.Range) | Item_event _ | Item_il_field _ -> None | Item_property(_,pinfos) -> (List.hd pinfos) |> range_of_pinfo | Item_meth_group(_,(minfo :: _)) | Item_ctor_group(_,FilterDefaultStructCtors(minfo :: _)) -> range_of_minfo minfo | Item_typs(_,(typ :: _)) -> Some((tcref_of_stripped_typ g typ).Range) | Item_modrefs(modref :: _) -> Some(modref.Range) | Item_apres(APInfo(_, _, m),_, _) -> Some(m) | _ -> None let ccu_of_item g d = match d with | Item_val vref -> ccu_of_vref vref | Item_ucase(ucr) -> ccu_of_tcref ucr.TyconRef | Item_apelem(apref) -> ccu_of_vref apref.ActivePatternVal | Item_ecref(ecr) -> ccu_of_tcref ecr | Item_recdfield(rfinfo) -> ccu_of_tcref rfinfo.RecdFieldRef.TyconRef | Item_event _ | Item_il_field _ -> None | Item_property(_,pinfos) -> (List.hd pinfos).ArbitraryValRef |> Option.bind ccu_of_vref | Item_meth_group(_,(minfo :: _)) | Item_ctor_group(_,(minfo :: _)) -> minfo.ArbitraryValRef |> Option.bind ccu_of_vref | Item_typs(_,(typ :: _)) -> ccu_of_tcref (tcref_of_stripped_typ g typ) | Item_modrefs(modref :: _) -> ccu_of_tcref modref | _ -> None /// Work out the likely source file for an item let filename_of_item g qualProjectDir m h = let file = file_of_range m in dprintf "file stored in metadata is '%s'\n" file if Filename.is_relative file then match (ccu_of_item g h) with | Some ccu -> // Note: For F# library DLLs, the code in build.ml fixes uo the SourceCodeDirectory (compile_time_working_dir) // to be defaultFSharpBinariesDir\..\lib\, i.e. the location of the source for the // file in the F# installation location Filename.concat ccu.SourceCodeDirectory file | None -> match qualProjectDir with | None -> file | Some dir -> Filename.concat dir file else file /// Cut long filenames to make them visually appealing let cutFileName s = if String.length s > 40 then String.sub s 0 10 ^ "..."^String.sub s (String.length s - 27) 27 else s /// Output where an item is defined let boutput_range_of_item os x = () (* let mDefnOpt = range_of_item false x let mDeclOpt = range_of_item true x match mDeclOpt with | None -> () | Some mDecl -> let filenameDecl = filename_of_item None mDecl x |> cutFileName bprintf os "\nDeclared at %s, line %d" filenameDecl (line_of_pos (start_of_range mDecl)); match mDefnOpt with | None -> () | Some mDefn -> if mDefn <> mDecl then let filenameDefn = filename_of_item None mDefn x |> cutFileName bprintf os "\nDefined at %s, line %d" filenameDefn (line_of_pos (start_of_range mDefn)) *) /// Output a method info let FormatMemberInfosToBuffer (infoReader:InfoReader) m denv os d minfos = let g = infoReader.g let amap = infoReader.amap let FormatMethInfoToBuffer_first minfo = FormatMethInfoToBuffer amap m denv os minfo; boutput_xmldoc os minfo.XmlDoc; boutput_range_of_item os d let ConstructMemberInfoText() = if List.length minfos >= 1 then (FormatMethInfoToBuffer_first (List.nth minfos 0)); if List.length minfos >= 2 then (bprintf os "\n"; FormatMethInfoToBuffer amap m denv os (List.nth minfos 1)); if List.length minfos >= 3 then (bprintf os "\n"; FormatMethInfoToBuffer amap m denv os (List.nth minfos 2)); if List.length minfos >= 4 then (bprintf os "\n"; FormatMethInfoToBuffer amap m denv os (List.nth minfos 3)); if List.length minfos >= 5 then (bprintf os "\n"; FormatMethInfoToBuffer amap m denv os (List.nth minfos 4)); if List.length minfos >= 6 then (bprintf os "\n"; bprintf os "and %d other overloads" (List.length minfos - 5)) match !TestHooks.ConstructMemberInfoText with | Some(hook) -> hook(ConstructMemberInfoText) | None -> ConstructMemberInfoText() let pubpath_of_vref (v:ValRef) = v.PublicPath let pubpath_of_tcref (x:TyconRef) = x.PublicPath // Wrapper type for use by the 'partial_distinct_by' function type WrapType<'a> = Wrap of 'a // Like Seq.distinct_by but only filters out duplicates for some of the elements let partial_distinct_by (per:IPartialEqualityComparer<_>) seq = // Wrap a Wrap(_) aroud all keys in case the key type is itself a type using null as a representation let dict = new Dictionary<'a WrapType,obj>(per) seq |> List.filter (fun v -> let v = Wrap(v) if (per.InEqualityRelation(v)) then if dict.ContainsKey(v) then false else (dict.[v] <- null; true) else true) /// Specifies functions for comparing 'NamedItem' objects with respect to the user /// (this means that some values that are not technically equal are treated as equal /// if this is what we want to show to the user, because we're comparing just the name // for some cases e.g. when using 'full_display_text_of_modref') let ItemDisplayPartialEquality g = { new IPartialEqualityComparer<_> with member x.InEqualityRelation item = match item with | Wrap(Item_typs(_,[_])) -> true | Wrap(Item_il_field(ILFieldInfo(_, _))) -> true | Wrap(Item_recdfield(_)) -> true | Wrap(Item_prop_name(_)) -> true | Wrap(Item_modrefs(_ :: _)) -> true | Wrap(Item_meth_group(_, _)) -> true | Wrap(Item_val(_)) -> true | Wrap(Item_apelem(APElemRef(apinfo, vref, idx))) -> true | Wrap(Item_delegate_ctor(_)) -> true | Wrap(Item_ucase(_)) -> true | Wrap(Item_ecref(_)) -> true | Wrap(Item_event(_)) -> true | Wrap(Item_property(_)) -> true | _ -> false member x.Equals(item1, item2) = let equalTypes(ty1, ty2) = if is_stripped_tyapp_typ g ty1 && is_stripped_tyapp_typ g ty2 then tcref_eq g (tcref_of_stripped_typ g ty1) (tcref_of_stripped_typ g ty2) else type_equiv g ty1 ty2 match item1,item2 with | Wrap(Item_delegate_ctor(ty1)), Wrap(Item_delegate_ctor(ty2)) -> equalTypes(ty1, ty2) | Wrap(Item_typs(dn1,[ty1])), Wrap(Item_typs(dn2,[ty2])) -> // Bug 4403: We need to compare names as well, because 'int' and 'Int32' are physically the same type, but we want to show both dn1 = dn2 && equalTypes(ty1, ty2) | Wrap(Item_ecref(tcref1)), Wrap(Item_ecref(tcref2)) -> tcref_eq g tcref1 tcref2 | Wrap(Item_il_field(ILFieldInfo(_, fld1))), Wrap(Item_il_field(ILFieldInfo(_, fld2))) -> fld1 === fld2 // reference equality on the object identity of the AbstractIL metadata blobs for the fields | Wrap(Item_modrefs(modref1 :: _)), Wrap(Item_modrefs(modref2 :: _)) -> full_display_text_of_modref modref1 = full_display_text_of_modref modref2 | Wrap(Item_prop_name(id1)), Wrap(Item_prop_name(id2)) -> (id1.idRange, id1.idText) = (id2.idRange, id2.idText) | Wrap(Item_meth_group(_, meths1)), Wrap(Item_meth_group(_, meths2)) -> Seq.zip meths1 meths2 |> Seq.forall (fun (m1, m2) -> Infos.MethInfosUseIdenticalDefinitions () m1 m2) | Wrap(Item_val(vref1)), Wrap(Item_val(vref2)) -> g.vref_eq vref1 vref2 | Wrap(Item_apelem(APElemRef(apinfo1, vref1, idx1))), Wrap(Item_apelem(APElemRef(apinfo2, vref2, idx2))) -> idx1 = idx2 && g.vref_eq vref1 vref2 | Wrap(Item_ucase(UnionCaseInfo(_, ur1))), Wrap(Item_ucase(UnionCaseInfo(_, ur2))) -> g.ucref_eq ur1 ur2 | Wrap(Item_recdfield(RecdFieldInfo(_, RFRef(tcref1, n1)))), Wrap(Item_recdfield(RecdFieldInfo(_, RFRef(tcref2, n2)))) -> (tcref_eq g tcref1 tcref2) && (n1 = n2) // there is no direct function as in the previous case | Wrap(Item_property(_, pi1s)), Wrap(Item_property(_, pi2s)) -> List.zip pi1s pi2s |> List.forall(fun (pi1, pi2) -> Infos.PropInfosUseIdenticalDefinitions pi1 pi2) | Wrap(Item_event(evt1)), Wrap(Item_event(evt2)) -> Infos.EventInfosUseIdenticalDefintions evt1 evt2 | _ -> false member x.GetHashCode item = match item with | Wrap(Item_delegate_ctor(ty)) | Wrap(Item_typs(_,[ty])) -> if is_stripped_tyapp_typ g ty then hash (tcref_of_stripped_typ g ty).Stamp else 1010 | Wrap(Item_il_field(ILFieldInfo(_, fld))) -> fld.GetHashCode() // hash on the object identity of the AbstractIL metadata blob for the field | Wrap(Item_modrefs(modref :: _)) -> hash (full_display_text_of_modref modref) | Wrap(Item_prop_name(id)) -> hash (id.idRange, id.idText) | Wrap(Item_meth_group(_, meths)) -> meths |> List.fold (fun st a -> st + (Infos.GetMethInfoHashCode(a))) 0 | Wrap(Item_val(vref)) -> hash vref.MangledName | Wrap(Item_apelem(APElemRef(apinfo, vref, idx))) -> hash (vref.MangledName, idx) | Wrap(Item_ecref(tcref)) -> hash tcref.Stamp | Wrap(Item_ucase(UnionCaseInfo(_, UCRef(tcref, n)))) -> hash(tcref.Stamp, n) | Wrap(Item_recdfield(RecdFieldInfo(_, RFRef(tcref, n)))) -> hash(tcref.Stamp, n) | Wrap(Item_event(evt)) -> Infos.GetEventInfoHashCode(evt) | Wrap(Item_property(name, pis)) -> hash (pis |> List.map Infos.GetPropInfoHashCode) | _ -> failwith "unreachable" } let getPartialStructuralComparerBy f = let hid = HashIdentity.Structural { new IPartialEqualityComparer<_> with member x.InEqualityRelation (Wrap(item)) = f(item) |> Option.is_some member x.Equals(Wrap(item1), Wrap(item2)) = hid.Equals(item1, item2) member x.GetHashCode(Wrap(item)) = hid.GetHashCode(item) } // Remove items containing the same module references, where f projects out the module reference let RemoveDuplicateModuleRefs f xs = xs |> partial_distinct_by (getPartialStructuralComparerBy (f >> Option.map full_display_text_of_modref)) /// Remove all duplicate items let RemoveDuplicateItems g items = partial_distinct_by (ItemDisplayPartialEquality g) items /// Filter types that are explicitly suppressed from the IntelliSense (such as uppercase "FSharpList", "Option", etc.) let RemoveExplicitlySuppressed g items = items |> List.filter (function | Item_typs(it, [ty]) -> g.suppressed_types |> List.forall (fun supp -> if is_stripped_tyapp_typ g ty then // check if they are the same logical type (after removing all abbreviations) let tcr1 = tcref_of_stripped_typ g ty let tcr2 = tcref_of_stripped_typ g (snd(generalize_tcref supp) ) not(tcref_eq g tcr1 tcr2 && // check the display name is precisely the one we're suppressing it = supp.DisplayName) else true ) | _ -> true ) let SimplerDisplayEnv denv = { denv with suppressInlineKeyword=true; shortConstraints=true; showConstraintTyparAnnotations=false; abbreviateAdditionalConstraints=false } /// Output a the description of a language item let FormatItemDescriptionToBuffer (infoReader:InfoReader) m denv os d = let g = infoReader.g let amap = infoReader.amap let denv = SimplerDisplayEnv denv match d with | Item_val v -> bprintf os "%a%a%a" (NicePrint.output_qualified_val_spec denv) (deref_val v) boutput_xmldoc v.XmlDoc (boutput_fullname pubpath_of_vref full_display_text_of_vref) v; // adjust the type in case this is the 'this' pointer stored in a reference cell let ty = if v.BaseOrThisInfo = CtorThisVal && is_refcell_ty g v.Type then dest_refcell_ty g v.Type else v.Type OutputUsefulTypeInfo infoReader m denv os ty; boutput_range_of_item os d // Union tags (constructors) | Item_ucase(ucr) -> let uc = ucr.UnionCase let rty =snd(generalize_tcref ucr.TyconRef) let recd = uc.RecdFields let ty = if recd = [] then rty else (mk_tupled_ty g (recd |> List.map (fun rfld -> rfld.FormalType))) --> rty bprintf os "union case %a.%s: %a%a" (NicePrint.output_tcref denv) ucr.TyconRef (DecompileOpName uc.ucase_id.idText) (NicePrint.output_typ denv) ty boutput_xmldoc uc.ucase_xmldoc; boutput_range_of_item os d // Active pattern tag inside the declaration (result) | Item_apres(APInfo(_, items, _), ty, idx) -> bprintf os "active pattern result %s: %a" (List.nth items idx) (NicePrint.output_typ denv) ty // Active pattern tags | Item_apelem(apref) -> let v = apref.ActivePatternVal let tps,tau = v.TypeScheme let args,res = strip_fun_typ g tau let apinfo = the (apinfo_of_vref v) let apnames = names_of_apinfo apinfo let aparity = List.length apnames let rty = if aparity <= 1 then res else List.nth (tinst_of_stripped_typ g res) apref.CaseIndex let ty = mk_iterated_fun_ty args rty // Format the type parameters to get e.g. ('a -> 'a) rather than ('?1234 -> '?1234) let tps,tau = v.TypeScheme let tprenaming,ptau,cxs = PrettyTypes.PrettifyTypes1 denv.g tau let ptps = tps |> generalize_typars |> List.map (InstType tprenaming) |> List.filter (is_anypar_typ denv.g) |> List.map (dest_anypar_typ denv.g) bprintf os "active recognizer %s: %a%a%a" (name_of_apref apref) (NicePrint.output_typ denv) ptau boutput_xmldoc v.XmlDoc (boutput_fullname pubpath_of_vref full_display_text_of_vref) v // F# exception names | Item_ecref(ecref: TyconRef) -> bprintf os "%a%a%a" (NicePrint.output_exnc denv) (deref_tycon ecref) boutput_xmldoc ecref.XmlDoc (boutput_fullname pubpath_of_tcref full_display_text_of_ecref) ecref; boutput_range_of_item os d // F# record field names | Item_recdfield(rfinfo) -> let f = rfinfo.RecdField let _,ty,tpcs = PrettyTypes.PrettifyTypes1 g rfinfo.FieldType bprintf os "%a.%s: %a" (NicePrint.output_tcref denv) rfinfo.TyconRef (DecompileOpName f.Name) (NicePrint.output_typ denv) ty; match rfinfo.LiteralValue with | None -> () | Some lit -> try bprintf os " = %s" (Layout.showL ( NicePrint.constL lit )) with _ -> () bprintf os "%a" boutput_xmldoc f.rfield_xmldoc; boutput_range_of_item os d // Not used | Item_newdef(id) -> bprintf os "patvar %s" id.idText // .NET fields | Item_il_field(finfo) -> bprintf os "field %a.%s" (NicePrint.output_tref denv) finfo.ILTypeRef finfo.FieldName; match finfo.LiteralValue with | None -> () | Some v -> try bprintf os " = %s" (Layout.showL ( NicePrint.constL (TypeChecker.TcFieldInit m v) )) with _ -> () // .NET events | Item_event(ILEvent(g,ilEventInfo) as einfo) -> let rty = PropTypOfEventInfo infoReader m AccessibleFromSomewhere einfo let _,rty,tpcs = PrettyTypes.PrettifyTypes1 g rty bprintf os "event %a.%s: %a" (NicePrint.output_tref denv) ilEventInfo.TypeRef einfo.EventName (NicePrint.output_typ denv) rty // F# and .NET properties | Item_property(_,pinfos) -> let pinfo = List.hd pinfos let rty = PropertyTypeOfPropInfo amap m pinfo let _,rty,tpcs = PrettyTypes.PrettifyTypes1 g rty let ty = if pinfo.IsIndexer then mk_tupled_ty g (List.map snd (ParamNamesAndTypesOfPropInfo amap m pinfo)) --> rty else rty bprintf os "property %a.%s: %a%a" (NicePrint.output_tcref denv) (tcref_of_stripped_typ g pinfo.EnclosingType) pinfo.PropertyName (NicePrint.output_typ denv) rty boutput_xmldoc pinfo.XmlDoc; boutput_range_of_item os d // F# constructors | Item_ctor_group(_,minfos) -> FormatMemberInfosToBuffer infoReader m denv os d minfos | Item_meth_group(_,minfos) -> FormatMemberInfosToBuffer infoReader m denv os d minfos // The 'fake' zero-argument constructors of .NET structs | Item_fake_intf_ctor typ -> let _,typ,tpcs = PrettyTypes.PrettifyTypes1 g typ bprintf os "%a" (NicePrint.output_tcref denv) (tcref_of_stripped_typ g typ) // The 'fake' representation of constructors of .NET delegate types | Item_delegate_ctor delty -> let _,delty,tpcs = PrettyTypes.PrettifyTypes1 g delty let minfo, argtys,rty,fty = GetSigOfFunctionForDelegate infoReader delty m AccessibleFromSomewhere bprintf os "%a(%a)" (NicePrint.output_tcref denv) (tcref_of_stripped_typ g delty) (NicePrint.output_typ denv) fty; boutput_range_of_item os d // Types. | Item_typs(_,((TType_app(tcref,_) as typ):: _)) -> bprintf os "%a" (NicePrint.output_tycon denv) (deref_tycon tcref); boutput_xmldoc os tcref.XmlDoc; boutput_fullname pubpath_of_tcref full_display_text_of_tcref os tcref; OutputUsefulTypeInfo infoReader m denv os typ; boutput_range_of_item os d // F# Modules and namespaces | Item_modrefs((modref :: _) as modrefs) -> let modrefs = modrefs |> RemoveDuplicateModuleRefs (fun x -> Some(x)) let definiteNamespace = modrefs |> List.forall (fun modref -> modref.IsNamespace) let kind = if definiteNamespace then "namespace" elif modrefs |> List.forall (fun modref -> modref.IsModule) then "module" else "namespace/module" bprintf os "%s %s" kind (if definiteNamespace then full_display_text_of_modref modref else demangled_name_of_modref modref) if not definiteNamespace then let namesToAdd = modrefs |> Seq.fold (fun st modref -> match full_display_text_of_parent_of_modref modref with | Some(txt) -> txt::st | _ -> st) [] |> Seq.mapi (fun i x -> i,x) |> Seq.to_list if namesToAdd <> [] then bprintf os "\n" for i, txt in namesToAdd do bprintf os "\n%s %s" (if i = 0 then "from" else "also from") txt boutput_xmldoc os modref.XmlDoc boutput_range_of_item os d // Named parameters | Item_param_name(id) -> //let _,typ,tpcs = PrettyTypes.PrettifyTypes1 typ bprintf os "argument %s" id.idText; // (NicePrint.output_typ denv) typ | Item_prop_name(id) -> bprintf os "property %s" id.idText; // (NicePrint.output_typ denv) typ | _ -> () // Format the return type of an item let FormatItemReturnTypeToBuffer (infoReader:InfoReader) m denv os d = let g = infoReader.g let amap = infoReader.amap let denv = SimplerDisplayEnv denv match d with | Item_val vref -> let tps,tau = vref.TypeScheme (* Note: prettify BEFORE we strip to make sure params look the same as types *) if is_fun_typ g tau then let dtau,rtau = dest_fun_typ g tau let ptausL,tpcsL = NicePrint.typesAndConstraintsL denv [dtau;rtau] let _,prtauL = List.frontAndBack ptausL bprintf os "%a %a" bufferL prtauL bufferL tpcsL else bufferL os (NicePrint.topTypAndConstraintsL denv [] tau) | Item_ucase(ucr) -> let uc = ucr.UnionCase let rty =snd(generalize_tcref ucr.TyconRef) bprintf os "%a" (NicePrint.output_typ denv) rty | Item_apelem(apref) -> let v = apref.ActivePatternVal let tps,tau = v.TypeScheme let args,res = strip_fun_typ g tau let apinfo = the (apinfo_of_vref v) let apnames = names_of_apinfo apinfo let aparity = apnames.Length let rty = if aparity <= 1 then res else List.nth (tinst_of_stripped_typ g res) apref.CaseIndex bprintf os "%a" (NicePrint.output_typ denv) rty | Item_ecref(ecr) -> bufferL os (NicePrint.topTypAndConstraintsL denv [] g.exn_ty) | Item_recdfield(rfinfo) -> bufferL os (NicePrint.topTypAndConstraintsL denv [] rfinfo.FieldType); | Item_il_field(finfo) -> bufferL os (NicePrint.topTypAndConstraintsL denv [] (FieldTypeOfILFieldInfo amap m finfo)) | Item_event(einfo) -> bufferL os (NicePrint.topTypAndConstraintsL denv [] (PropTypOfEventInfo infoReader m AccessibleFromSomewhere einfo)) | Item_property(_,pinfos) -> let pinfo = List.hd pinfos let rty = PropertyTypeOfPropInfo amap m pinfo bufferL os (NicePrint.topTypAndConstraintsL denv [] rty) | Item_meth_group(_,(minfo :: _)) | Item_ctor_group(_,(minfo :: _)) -> let rty = FSharpReturnTyOfMeth amap m minfo minfo.FormalMethodInst bufferL os (NicePrint.topTypAndConstraintsL denv [] rty) | Item_fake_intf_ctor typ | Item_delegate_ctor typ -> bufferL os (NicePrint.topTypAndConstraintsL denv [] typ) | _ -> () // Find the name of the metadata file for this external definition let ftinfo_of_il_tcref (infoReader:InfoReader) m tcref = let g = infoReader.g match tcref with | ERef_private _ -> None | ERef_nonlocal nlref -> match (ccu_of_nlpath (nlpath_of_nlref nlref)).FileName with | None -> None | Some libfile -> (* Generalize the pinfo to get a formal signature *) let ftctps = tcref.Typars(m) let ftinst = generalize_typars ftctps let ftinfo = tinfo_of_il_typ g (TType_app(tcref,ftinst)) Some(libfile,ftctps,ftinfo) // This function gets the signature to pass to Visual Studio to use its lookup functions for .NET stuff. // NOTE: XmlDoc for F# items is added by FormatItemDescriptionToBuffer let GetXmlDocHelpSigOfItemForLookup (infoReader:InfoReader) m d = let g = infoReader.g let amap = infoReader.amap match d with | Item_val v -> None | Item_ucase(ucr) -> None | Item_ecref(ecr) -> None | Item_recdfield(rfinfo) -> None | Item_newdef(id) -> None | Item_il_field(ILFieldInfo(tinfo, fdef)) -> let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> (libfile, "F:"^ftinfo.ILTypeRef.FullName^"."^fdef.fdName)) | Item_typs(_,((TType_app(tcref,_) as typ) :: _)) -> // Find the name of the metadata file for this external definition if tcref.IsILTycon then ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> (libfile,"T:"^ftinfo.ILTypeRef.FullName)) else None | Item_modrefs(modref :: _) -> None | Item_property(_,(pinfo :: _)) -> match pinfo with | FSProp _ -> None | ILProp(g, (ILPropInfo(tinfo,pdef) as ilpinfo)) -> let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> let filpinfo = ILPropInfo(ftinfo,pdef) (libfile, "P:"^ftinfo.ILTypeRef.FullName^"."^pdef.propName^ XmlDocArgsEnc g ftctps (List.map snd (params_of_il_pinfo amap m filpinfo)))) | Item_event(ILEvent(g,ilEventInfo) as einfo) -> let tinfo = ilEventInfo.ILTypeInfo let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> (libfile, "E:"^ftinfo.ILTypeRef.FullName^"."^einfo.EventName)) | Item_meth_group(_,minfos) | Item_ctor_group(_,minfos) -> match minfos with | [] -> None | FSMeth _ :: _ -> None | (ILMeth (g,ILMethInfo(tinfo,isExt,mdef,fmtps)) as minfo) :: _ -> // REVIEW: this is not right for extension members // Generalize the minfo to get a formal signature let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> let filminfo = ILMethInfo(ftinfo,isExt,mdef,fmtps) (libfile, "M:"^tinfo.ILTypeRef.FullName^"."^mdef.mdName^ XmlDocArgsEnc g (ftctps@fmtps) (filminfo.ArgTypes(amap,m,minfo.FormalMethodInst)))) | (DefaultStructCtor _ :: _) -> None | _ -> None let GetF1Keyword (infoReader:InfoReader) m d : string option = let g = infoReader.g let amap = infoReader.amap match d with | Item_val v -> None | Item_ucase(ucr) -> None | Item_ecref(ecr) -> None | Item_recdfield(rfinfo) -> None | Item_newdef(id) -> None | Item_il_field(ILFieldInfo(tinfo, fdef)) -> let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> (tcref |> ticks_and_argcount_text_of_tcref)^"."^fdef.fdName) | Item_typs(_,((TType_app(tcref,_) as typ) :: _)) -> Some (ticks_and_argcount_text_of_tcref tcref) | Item_modrefs(modref :: _) -> None | Item_property(_,(pinfo :: _)) -> match pinfo with | FSProp _ -> None | ILProp(g, (ILPropInfo(tinfo,pdef) as ilpinfo)) -> let tcref = tinfo.TyconRef begin ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> let filpinfo = ILPropInfo(ftinfo,pdef) (tcref |> ticks_and_argcount_text_of_tcref)^"."^pdef.propName) end | Item_event(ILEvent(g,ilEventInfo) as einfo) -> let tinfo = ilEventInfo.ILTypeInfo let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> (tcref |> ticks_and_argcount_text_of_tcref)^"."^einfo.EventName) | Item_ctor_group(_,minfos) -> match minfos with | [] -> None | FSMeth _ :: _ -> None | (ILMeth (g,ILMethInfo(tinfo,isExt,mdef,fmtps)) as minfo) :: _ -> let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (libfile,ftctps,ftinfo) -> let filminfo = ILMethInfo(ftinfo,isExt,mdef,fmtps) //tinfo.ILTypeRef.FullName^".#ctor" // dmilom: spec suggests I need to name it #ctor (tcref |> ticks_and_argcount_text_of_tcref)^"."^tcref.DisplayName) // but this is what works | (DefaultStructCtor _ :: _) -> None | Item_meth_group(_,minfos) -> match minfos with | [] -> None | FSMeth _ :: _ -> None | (ILMeth (g,ILMethInfo(tinfo,isExt,mdef,fmtps)) as minfo) :: _ -> // REVIEW: this is not right for extension members // Generalize the minfo to get a formal signature let tcref = tinfo.TyconRef ftinfo_of_il_tcref infoReader m tcref |> Option.map (fun (_,ftctps,ftinfo) -> let filminfo = ILMethInfo(ftinfo,isExt,mdef,fmtps) let nGenericParams = mdef.GenericParams.Length let paramString = if nGenericParams > 0 then "`"^(nGenericParams.ToString()) else "" (tcref |> ticks_and_argcount_text_of_tcref)^"."^mdef.mdName^paramString) | (DefaultStructCtor _ :: _) -> None | _ -> None let FormatDescriptionOfItem (infoReader:InfoReader) m denv d = ErrorScope.Protect m (fun () -> let g = infoReader.g let amap = infoReader.amap let text,xmlSigToAdd = bufs (fun buf -> FormatItemDescriptionToBuffer infoReader m denv buf d), GetXmlDocHelpSigOfItemForLookup infoReader m d if verbose then xmlSigToAdd |> Option.iter (fun (lib,sg) -> dprintf "FormatDescriptionOfItem: libOfXmlSigToAdd = %s, xmlSigToAdd = %s\n" lib sg); (text,xmlSigToAdd)) (fun err -> err,None) let FormatReturnTypeOfItem (infoReader:InfoReader) m denv d = ErrorScope.Protect m (fun () -> bufs (fun buf -> FormatItemReturnTypeToBuffer infoReader m denv buf d)) (fun err -> err) // Compute the index of the VS glyph shown with an item in the Intellisense menu let GlyphOfItem(denv,d) = /// Find the glyph for the given representation. let ReprToGlyph(repr) = match repr with | TFsObjModelRepr om -> match om.fsobjmodel_kind with | TTyconClass -> iIconGroupClass | TTyconInterface -> iIconGroupInterface | TTyconStruct -> iIconGroupStruct | TTyconDelegate _ -> iIconGroupDelegate | TTyconEnum _ -> iIconGroupEnum | TRecdRepr _ -> iIconGroupType | TFiniteUnionRepr _ -> iIconGroupUnion | TILObjModelRepr(_,_,{tdKind=kind}) -> match kind with | TypeDef_class -> iIconGroupClass | TypeDef_valuetype -> iIconGroupStruct | TypeDef_interface -> iIconGroupInterface | TypeDef_enum -> iIconGroupEnum | TypeDef_delegate -> iIconGroupDelegate | TypeDef_other _ -> iIconGroupTypedef | TAsmRepr _ -> iIconGroupTypedef | TMeasureableRepr _-> iIconGroupTypedef /// Find the glyph for the given type representation. let rec TypToGlyph(typ) = if is_stripped_tyapp_typ denv.g typ then let tcref = tcref_of_stripped_typ denv.g typ tcref.TypeReprInfo |> (function None -> iIconGroupClass | Some repr -> ReprToGlyph repr) elif is_tuple_typ denv.g typ then iIconGroupStruct elif is_function denv.g typ then iIconGroupDelegate elif is_typar_typ denv.g typ then iIconGroupStruct else iIconGroupTypedef /// Find the glyph for the given value representation. let ValueToGlyph(typ) = if is_function denv.g typ then iIconGroupMethod else iIconGroupConstant /// Find the major glyph of the given named item. let NamedItemToMajorGlyph = function | Item_val(vref) -> ValueToGlyph(vref.Type) | Item_typs(_,typ::_) -> TypToGlyph(strip_tpeqns_and_tcabbrevs denv.g typ) | Item_ucase _ | Item_apelem _ -> iIconGroupEnumMember | Item_ecref _ -> iIconGroupException | Item_recdfield _ -> iIconGroupFieldBlue | Item_il_field _ -> iIconGroupFieldBlue | Item_event _ -> iIconGroupEvent | Item_property _ -> iIconGroupProperty | Item_ctor_group _ | Item_delegate_ctor _ | Item_fake_intf_ctor _ | Item_meth_group _ -> iIconGroupMethod | Item_typs _ -> iIconGroupClass | Item_modrefs(modref::_) -> if modref.IsNamespace then iIconGroupNameSpace else iIconGroupModule | Item_param_name _ -> iIconGroupVariable | Item_prop_name _ -> iIconGroupVariable | x -> iIconGroupError /// Find the minor glyph of the given named item. let NamedItemToMinorGlyph = function | Item_val(vref) when is_function denv.g vref.Type -> iIconItemSpecial | _ -> iIconItemNormal (6 * NamedItemToMajorGlyph(d)) + NamedItemToMinorGlyph(d) let string_is_prefix_of m n = String.length n >= String.length m && String.sub n 0 (String.length m) = m open ItemDescriptions //---------------------------------------------------------------------------- // Scopes etc. for intellisense //-------------------------------------------------------------------------- type Names = string list type NamesWithResidue = Names * string //---------------------------------------------------------------------------- // Declarations //-------------------------------------------------------------------------- [] type TextResult(v: TextWithXmlSig) = member x.TextWithXmlSig = v member x.Text= fst v member x.XMLFileAndSig = snd v /// An intellisense declaration [] type Declaration(name, description:TextWithXmlSig Lazy.t, glyph:int, syncop:(unit->unit)->unit) = let mutable descriptionText:TextResult option = None member decl.Name = name member decl.DescriptionText = match descriptionText with | Some(descriptionText) -> descriptionText | None -> // syncop "Synchronous Operation" causes the lambda to execute inside the background compiler. syncop (fun () -> descriptionText<-Some(TextResult(Lazy.force description))) descriptionText.Value member decl.Glyph = glyph /// A table of declarations for Intellisense completion [] type Decls(d: Declaration array) = member self.Items = d member self.Count = Array.length d member self.Name i : string = d.[i].Name member self.Description i : TextWithXmlSig = ErrorScope.Protect Range.range0 (fun () -> d.[i].DescriptionText.TextWithXmlSig) (fun err -> err, None) member self.Glyph i = d.[i].Glyph // Make a 'Declarations' object for a set of selected items static member Create(infoReader:InfoReader,m,denv,items, syncop:(unit->unit)->unit) = let g = infoReader.g // Remove all duplicates first let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g // Bag by name using multi-maps let items = items |> List.map (fun d -> DisplayNameOfItem g d, d) let items = List.foldBack (fun (n,d) acc -> NameMultiMap.add n d acc) items NameMultiMap.empty // Prefer to show types first - they usually have very useful XmlDocs let items = items |> NameMap.to_list let items = items |> List.map (fun (nm,bag) -> nm, bag |> List.sortWith (fun a b -> match a,b with | Item_ctor_group _, Item_typs _ -> 1 | Item_typs _, Item_ctor_group _ -> -1 | _ -> 0)) // Sort by name let items = items |> List.sortBy fst if verbose then dprintf "service.ml: mkDecls: %d found after filtering\n" (List.length items); // Get the descriptions, lazily let getDescription item rest = lazy (let text,xmlSigToAdd = FormatDescriptionOfItem infoReader m denv item let text = System.String.Join("\n-------------\n", Array.of_list (text :: (rest |> List.map (FormatDescriptionOfItem infoReader m denv >> fst)))) text, xmlSigToAdd) let decls = // Filter out operators let items = items |> List.filter (fun (nm,d) -> not (IsOpName nm) || (is_ap_name nm)) // Filter out duplicate names items |> List.map (fun (nm,itemsWithSameName) -> match itemsWithSameName with | [] -> failwith "Unexpected empty bag" | item :: rest -> let desc = getDescription item rest Declaration(nm,desc,GlyphOfItem(denv,item),syncop)) new Decls(Array.of_list decls) //---------------------------------------------------------------------------- // Methods //-------------------------------------------------------------------------- type Param = { Name: string; Display: string; Description: string } /// Format parameters for Intellisense completion module Params = let param_of_typL denv tyL = { Name= ""; Display = Layout.showL tyL; Description = "" } let ParamOfRecdField denv f = { Name= "" (* f.rfield_id.idText *) ; Display = NicePrint.pretty_string_of_typ denv f.rfield_type; Description = "" } let ParamOfParamData denv paramData = { Name= ""; //(match pname with None -> "" | Some pn -> pn); Display = string_of_param_data denv paramData; Description = "" } let ParamsOfParamDatas denv (paramDatas:ParamData list) rty = let paramPrefixes,paramTypes = paramDatas |> List.map (fun (ParamData(_,_,optArgInfo,nmOpt,pty)) -> let isOptArg = optArgInfo <> NotOptional match nmOpt, isOptArg, try_dest_option_ty denv.g pty with // Layout an optional argument | Some(nm), true, Some(pty) -> (sprintf "?%s:" nm), pty // Layout an unnamed argument | None, _,_ -> "", pty; // Layout a named argument | Some nm,_,_ -> (sprintf "%s: " nm),pty) |> List.unzip let paramTypeAndRetLs,_ = NicePrint.typesAndConstraintsL denv (paramTypes@[rty]) let paramTypeLs,_ = List.frontAndBack paramTypeAndRetLs (paramPrefixes,paramTypeLs) ||> List.map2 (fun paramPrefix tyL -> { Name= ""; Display = paramPrefix^(showL tyL); Description = "" }) let ParamsOfTypes denv args rtau = (*let arg,rtau = dest_fun_typ rtau let args = try_dest_tuple_typ arg *) let ptausL,tpcsL = NicePrint.typesAndConstraintsL denv (args@[rtau]) let argsL,_ = List.frontAndBack ptausL List.map (param_of_typL denv) argsL let ParamsOfItem (infoReader:InfoReader) m denv d = let g = infoReader.g let amap = infoReader.amap match d with | Item_val vref -> let tps,tau = vref.TypeScheme if is_fun_typ denv.g tau then let arg,rtau = dest_fun_typ denv.g tau let args = try_dest_tuple_typ denv.g arg ParamsOfTypes denv args rtau else [] | Item_ucase(ucr) -> ucr.UnionCase.RecdFields |> List.map (ParamOfRecdField denv) | Item_apelem(apref) -> let v = apref.ActivePatternVal let tps,tau = v.TypeScheme let args,res = strip_fun_typ denv.g tau ParamsOfTypes denv args tau | Item_ecref(ecref) -> ecref |> rfields_of_ecref |> List.map (ParamOfRecdField denv) | Item_property(_,pinfo :: _) -> let paramDatas = ParamNamesAndTypesOfPropInfo amap m pinfo |> List.map (fun (nm,pty) -> ParamData(false,false,NotOptional,nm, pty)) let rty = PropertyTypeOfPropInfo amap m pinfo ParamsOfParamDatas denv paramDatas rty | Item_ctor_group(_,(minfo :: _)) | Item_meth_group(_,(minfo :: _)) -> let paramDatas = ParamDatasOfMethInfo amap m minfo minfo.FormalMethodInst |> List.hd let rty = FSharpReturnTyOfMeth amap m minfo minfo.FormalMethodInst ParamsOfParamDatas denv paramDatas rty | Item_fake_intf_ctor typ -> [] | Item_delegate_ctor delty -> let minfo, argtys,rty,fty = GetSigOfFunctionForDelegate infoReader delty m AccessibleFromSomeFSharpCode ParamsOfParamDatas denv [ParamData(false,false,NotOptional,None, fty)] delty | _ -> [] /// A single method for Intellisense completion type Method = { Description: TextResult; Type: string; Parameters: Param array } /// A table of methods for Intellisense completion [] type MethodOverloads( name: string, methods: Method array) = member x.Name = name member x.Methods = methods member meths.Count = try methods.Length with | e -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "Unexpected error (Methods.getCount): %s\n" (e.ToString())); 0 member meths.Description i : TextResult = ErrorScope.Protect Range.range0 (fun () -> methods.[i].Description) (fun err -> TextResult((err, None))) member meths.Type i : string = ErrorScope.ProtectWithDefault Range.range0 (fun () -> methods.[i].Type) "" member meths.ParameterCount i = ErrorScope.ProtectWithDefault Range.range0 (fun () -> methods.[i].Parameters.Length) 0 member meths.ParameterInfo i j : Param = ErrorScope.Protect Range.range0 (fun () -> methods.[i].Parameters.[j]) (fun err -> { Name= "";Display = "";Description = err }) static member Create(infoReader:InfoReader,m,denv,items) = let g = infoReader.g let amap = infoReader.amap if verbose then dprintf "mkMethods: %d items on input\n" (List.length items); if items = [] then new MethodOverloads("", [| |]) else let name = DisplayNameOfItem g (List.hd items) let items = items |> List.collect (fun item -> match item with | Item_meth_group(nm,minfos) -> List.map (fun minfo -> Item_meth_group(nm,[minfo])) minfos | Item_ctor_group(nm,cinfos) -> List.map (fun minfo -> Item_ctor_group(nm,[minfo])) cinfos | Item_fake_intf_ctor _ | Item_delegate_ctor _ -> [item] | Item_newdef _ | Item_il_field _ -> [] | Item_event _ -> [] | Item_recdfield(rfinfo) -> if is_function g rfinfo.FieldType then [item] else [] | Item_val v -> if is_function g v.Type then [item] else [] | Item_ucase(ucr) -> if not ucr.UnionCase.IsNullary then [item] else [] | Item_ecref(ecr) -> if rfields_of_ecref ecr |> nonNil then [item] else [] | Item_property(_,pinfos) -> let pinfo = List.hd pinfos let rty = PropertyTypeOfPropInfo amap m pinfo if pinfo.IsIndexer then [item] else [] | _ -> []) if verbose then dprintf "mkMethods: %d items after filtering for methodness\n" (List.length items); let methods = items |> Array.of_list |> Array.map (fun item -> { Description= TextResult(FormatDescriptionOfItem infoReader m denv item); Type= (FormatReturnTypeOfItem infoReader m denv item); Parameters = Array.of_list (Params.ParamsOfItem infoReader m denv item) }) new MethodOverloads(name, methods) //---------------------------------------------------------------------------- // Scopes. //-------------------------------------------------------------------------- type Position = int * int type Range = Position * Position type FindDeclResult = /// no identifier at this locn | IdNotFound /// no decl info in this buffer at the moment | NoDeclInfo /// found declaration; return (position-in-file, name-of-file, names-of-referenced-assemblies) | DeclFound of Position * string * (string list) /// found declaration but source file doesn't exist; try to generate an .fsi | NeedToGenerate of string * (string -> string) * (string list) /// This type is used to describe what was found during the name resolution. /// (Depending on the kind of the items, we may stop processing or continue to find better items) [] type NameResResult = | Members of (NamedItem list * DisplayEnv * range) | Cancel of DisplayEnv * range | Empty // A scope represents everything we get back from the typecheck of a file. // It acts like an in-memory database about the file. // It is effectively immutable and not updated: when we re-typecheck we just drop the previous // scope object on the floor and make a new one. [] type Scope(/// Information corresponding to miscellaneous command-line options (--define, etc). sTcConfig: Build.TcConfig, g: Env.TcGlobals, /// AssemblyName -> IL-Module amap: Import.ImportMap, /// project directory, or directory containing the file that generated this scope if no project directory given sProjectDir: string , sFile:string, /// Name resolution environments for every interesting region in the file. These regions may /// overlap, in which case the smallest region applicable should be used. sEnvs: ResizeArray, /// This is a name resolution environment to use if no better match can be found. sFallback:Nameres.NameResolutionEnv, /// Information of exact types found for expressions, that can be to the left of a dot. /// Also for exact name resolutions /// pos -- line and column /// typ - the inferred type for an expression /// NamedItem -- named item /// DisplayEnv -- information about printing. For example, should redundant keywords be hidden? /// NameResolutionEnv -- naming environment--for example, currently open namespaces. /// range -- the starting and ending position capturedExprTypings: ResizeArray<(pos * typ * DisplayEnv * Nameres.NameResolutionEnv * AccessorDomain * range)>, capturedNameResolutions: ResizeArray<(pos * NamedItem * ItemOccurence * DisplayEnv * Nameres.NameResolutionEnv * AccessorDomain * range)>, capturedReferenceResolutions : AssemblyResolution list, syncop:(unit->unit)->unit) = // These strings are potentially large and the editor may choose to hold them for a while. // Use this cache to fold together data tip text results that are the same. // Is not keyed on 'Names' collection because this is invariant for the current position in // this unchanged file. Keyed on lineStr though to prevent a change to the currently line // being available against a stale scope. let getDataTipTextCache = AgedLookup(recentForgroundTypeCheckLookupSize) let infoReader = new InfoReader(g,amap) let ncenv = new NameResolver(g,amap,infoReader,Nameres.FakeInstantiationGenerator) // Visual Studio uses line counts starting at 0, F# uses them starting at 1 let mkPos line idx = mk_pos (line+1) idx /// Find the most precise naming environment for the given line and column let GetBestEnvForPos cursorPos = let bestSoFar = ref None // Find the most deeply nested enclosing scope sEnvs |> ResizeArray.iter (fun (possm,env,ad) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Examining range %s for strict inclusion\n" (string_of_range possm)) if range_contains_pos possm cursorPos then match !bestSoFar with | Some (bestm,_,_) -> if range_contains_range bestm possm then bestSoFar := Some (possm,env,ad) | None -> bestSoFar := Some (possm,env,ad)); let mostDeeplyNestedEnclosingScope = !bestSoFar match mostDeeplyNestedEnclosingScope with | Some (m,env,ad) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Strict Inclusion found env for range %s\n" (string_of_range m)) | None ->Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Strict Inclusion found no environment, cursorPos = %s\n" (string_of_pos cursorPos)) // Look for better subtrees on the r.h.s. of the subtree to the left of where we are // Should really go all the way down the r.h.s. of the subtree to the left of where we are // This is all needed when the index is floating free in the area just after the environment we really want to capture // We guarantee to only refine to a more nested environment. It may not be strictly // the right environment, but will alwauys be at least as rich let bestAlmostIncludedSoFar = ref None sEnvs |> ResizeArray.iter (fun (possm,env,ad) -> if range_before_pos possm cursorPos then let contained = match mostDeeplyNestedEnclosingScope with | Some (bestm,_,_) -> range_contains_range bestm possm | None -> true if contained then match !bestAlmostIncludedSoFar with | Some (rightm,_,_) -> if pos_gt (end_of_range possm) (end_of_range rightm) || (pos_eq (end_of_range possm) (end_of_range rightm) && pos_gt (start_of_range possm) (start_of_range rightm)) then bestAlmostIncludedSoFar := Some (possm,env,ad) | _ -> bestAlmostIncludedSoFar := Some (possm,env,ad)); let resEnv = match !bestAlmostIncludedSoFar with | Some (m,env,ad) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Chose refined-rightmost env covering range %s\n" (string_of_range m)) env,ad | None -> match mostDeeplyNestedEnclosingScope with | Some (m,env,ad) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Chose refined env covering range %s\n" (string_of_range m)) env,ad | None -> Trace.PrintLine("CompilerServicesVerbose", fun () -> "Using fallback global env\n") (sFallback,AccessibleFromSomeFSharpCode) let pm = mk_range sFile cursorPos cursorPos resEnv,pm /// The items that come back from ResolveCompletionsInType are a bit /// noisy. Filter a few things out. /// /// e.g. prefer types to constructors for DataTipText let FilterItemsForCtors filterCtors items = let items = items |> List.filter (function (Item_ctor_group _) when filterCtors = ResolveTypeNamesToTypeRefs -> false | _ -> true) items /// Looks at the exact name resolutions that occurred during type checking /// If 'membersByResidue' is specified, we look for members of the item obtained /// from the name resultion and fitler them by the specified residue (?) let GetPreciseItemsFromNameResolution(line,colAtEndOfNames,membersByResidue,filterCtors) = let endOfNamesPos = mkPos line colAtEndOfNames Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseItemsFromNameResolution: line = %d, colAtEndOfNames = %d, endOfNamesPos = %s\n" line colAtEndOfNames (string_of_pos endOfNamesPos)) let quals = capturedNameResolutions |> ResizeArray.filter (fun (pos,_,_,_,_,_,_) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Checking position %s = %s\n" (string_of_pos endOfNamesPos) (string_of_pos pos)) pos_eq pos endOfNamesPos) Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseItemsFromNameResolution: Found %d relevant quals\n" quals.Count) let items = quals |> ResizeArray.to_list // Filter items to show only valid & return Some if there are any let returnItemsOfType items g denv m f = let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g let items = items |> FilterItemsForCtors filterCtors if (items <> []) then Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseItemsFromNameResolution: Results in %d items!\n" items.Length) f(items, denv, m) else NameResResult.Empty match items, membersByResidue with // If we're looking for members using a residue, we'd expect only // a single item (pick the first one) and we need the residue (which may be "") | (_,Item_typs(_,(typ::_)),_,denv,nenv,ad,m)::_, Some(_) -> let items = ResolveCompletionsInType ncenv nenv m ad true typ returnItemsOfType items g denv m NameResResult.Members // Value reference from the name resolution. Primarilly to disallow "let x.$ = 1" // In most of the cases, value references can be obtained from expression typings or from environment, // so we wouldn't have to handle values here. However, if we have something like: // let varA = "string" // let varA = if b then 0 else varA. // then the expression typings get confused (thinking 'varA:int'), so we use name resolution even for usual values. | (_, Item_val(vref), occurence, denv, nenv, ad, m)::_, Some(_) -> if (occurence <> ItemOccurence.Use) then // Return empty list to stop further lookup - for value declarations NameResResult.Cancel(denv, m) else // If we have any valid items for the value, then return completions for its type now let items = ResolveCompletionsInType ncenv nenv m ad false vref.TauType returnItemsOfType items g denv m NameResResult.Members // No residue, so the items are the full resolution of the name // Grab the first resolution (note the capturedNameResolutions come in in reversed order: latest recorded comes // first, so this will be the last name resolution recorded for this text in the file. This is used to update the // resolution for an expression // new Type // to an Item_ctor_group rather than an Item_typs | (_,_,_,denv,nenv,ad,m) :: _, None -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseItemsFromNameResolution: No residue, precise results found\n") let items = items |> List.map (fun (_,item,_,_,_,_,_) -> item) let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g NameResResult.Members(items, denv,m) | _ , _ -> NameResResult.Empty /// Looks at the exact expression types at the position to the left of the /// residue then the source when it was typechecked. let GetPreciseCompletionListFromExprTypings(line,colAtEndOfNames,filterCtors) = let endOfExprPos = mkPos line colAtEndOfNames Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseCompletionListFromExprTypings: line = %d, colAtEndOfNames = %d, endOfExprPos = %s\n" line colAtEndOfNames (string_of_pos endOfExprPos)) let quals = capturedExprTypings |> ResizeArray.filter (fun (pos,_,_,_,_,_) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "Checking position %s = %s\n" (string_of_pos endOfExprPos) (string_of_pos pos)) pos_eq pos endOfExprPos) |> ResizeArray.to_list Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseCompletionListFromExprTypings: Found %d relevant quals\n" quals.Length) // Note the capturedExprTypings come in in reversed order (latest recorded comes first). // If expressions overlap we want to find the last one pushed. // e.g. this happens with expressions of the form // (a + b) // which are really // ((+) a) b // but where both applications are marked to be at the same position // Heuristic "not (is_fun_typ typ)": // Generally, we prefer qualItems since it is the result of resolving the particular thing we're dotting off of. // // However, qualItems isn't guaranteed to be accurate because it's the result of a type check on possibly incomplete // code. See bug 2584 for example. // // We can do a little better when the thing being dotted is thought to be a function by qualItems. In this case, dotting // the function will just show methods for System.Object anyway so we can return the best named environment instead since // it will also contain the methods for System.Object. quals |> List.iter (fun (_,typ,denv,nenv,ad,m) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseCompletionListFromExprTypings: before filter, type '%s', is_typar_ty=%b, IsFromError=%b\n" (showL (typeL typ)) (is_typar_typ g typ) (is_typar_typ g typ && (dest_typar_typ g typ).IsFromError))) let quals = quals |> List.filter (fun (_,typ,denv,nenv,ad,m) -> not (is_fun_typ denv.g typ) && not (is_typar_typ denv.g typ && (dest_typar_typ denv.g typ).IsFromError)) quals |> List.iter (fun (_,typ,denv,nenv,ad,m) -> Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseCompletionListFromExprTypings: after filter, type '%s'\n" (showL (typeL typ)))) match quals |> List.rev with | (_,typ,denv,nenv,ad,m) :: _ -> let items = ResolveCompletionsInType ncenv nenv m ad false typ Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetPreciseCompletionListFromExprTypings: Results in %d items!\n" items.Length) let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g let items = items |> FilterItemsForCtors filterCtors Some(items,denv,m) | _ -> None /// Find items in the best naming environment. let GetEnvironmentLookupResolutions(line,colAtEndOfNamesAndResidue,plid,filterCtors,showObsolete) = Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetEnvironmentLookupResolutions: line = %d, colAtEndOfNamesAndResidue = %d, plid = %+A, showObsolete = %b\n" line colAtEndOfNamesAndResidue plid showObsolete) let cursorPos = mkPos line colAtEndOfNamesAndResidue let (nenv,ad),m = GetBestEnvForPos cursorPos let items = Nameres.ResolvePartialLongIdent ncenv nenv m ad plid showObsolete let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g let items = items |> FilterItemsForCtors filterCtors Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "GetEnvironmentLookupResolutions: found %d item groups by looking up long identifier chain in environment\n" (List.length items)) items, nenv.DisplayEnv,m /// Resolve a location and/or text to items. // Three techniques are used // - look for an exact known name resolution from type checking // - use the known type of an expression, e.g. (expr).Name, to generate an item list // - lookup an entire name in the name resolution environment, e.g. A.B.Name, to generate an item list // // The overall aim is to resolve as accurately as possible based on what we know from type inference let GetDeclItemsForNamesAtPosition(origLongIdentOpt: string list option,residueOpt,line,(lineStr:string),colAtEndOfNamesAndResidue,filterCtors) = use t = Trace.Call("CompilerServices","GetDeclItemsForNamesAtPosition", fun _->sprintf " plid=%+A residueOpt=%+A line=%d colAtEndOfNames=%d" origLongIdentOpt (residueOpt:option) line colAtEndOfNamesAndResidue) // Try to use the exact results of name resolution during type checking to generate the results // This is based on position (i.e. colAtEndOfNamesAndResidue). This is not used if a residueOpt is given. let nameResItems = match residueOpt with | None -> GetPreciseItemsFromNameResolution(line, colAtEndOfNamesAndResidue, None, filterCtors) | Some(residue) when (let loc = colAtEndOfNamesAndResidue - residue.Length - 1 loc >= 0 && loc < lineStr.Length && lineStr.[loc] = '.') -> // for "Foo.B", get location at the end of Foo (or "Foo") let colAtEndOfNames = max 0 (colAtEndOfNamesAndResidue - residue.Length - 1) GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors) | _ -> NameResResult.Empty // Normalize to form A.B.C.D where D is the residue. It may be empty for "A.B.C." let plid, residue = match origLongIdentOpt, residueOpt with | None, _ -> [], "" | Some(origLongIdent), Some(r) -> origLongIdent,r | Some(origLongIdent), None -> assert (nonNil origLongIdent) List.frontAndBack origLongIdent /// Post-filter items to make sure they have precisely the right name /// This also checks that there are some remaining results let (|FilterRelevantItems|_|) ((items,denv,m) as orig) = // Return only items with the specified name let filterDeclItemsByResidue items = items |> List.filter (fun item -> let n1 = DisplayNameOfItem g item Trace.PrintLine("CompilerServicesVerbose", fun () -> sprintf "\nn1 = <<<%s>>>\nn2 = <<<%s>>>\n" n1 residue) match item with | Item_typs _ | Item_ctor_group _ -> residue + "Attribute" = n1 || residue = n1 | _ -> residue = n1 ) // Are we looking for items with precisely the given name? if origLongIdentOpt <> None && items <> [] && isNone residueOpt then Trace.PrintLine("CompilerServices", fun _ -> sprintf "looking through %d items before filtering by residue\n" (List.length items)) let items = items |> filterDeclItemsByResidue if nonNil items then Some(items,denv,m) else None else // When (items = []) we must returns Some([],..) and not None // because this value is used if we want to stop further processing (e.g. let x.$ = ...) Some(orig) match nameResItems with | NameResResult.Cancel(denv,m) -> Some([], denv, m) | NameResResult.Members(FilterRelevantItems(items)) -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "GetDeclItemsForNamesAtPosition: lookup based on name resolution results successful, #items = %d, exists ctor = %b\n" (p13 items).Length (items |> p13 |> List.exists (function Item_ctor_group _ -> true | _ -> false))) Some items | _ -> match origLongIdentOpt with | None -> None | Some(_) -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "GetDeclItemsForNamesAtPosition: plid = %+A, residue = %+A, colAtEndOfNamesAndResidue = %+A\n" plid residue colAtEndOfNamesAndResidue) // Try to use the type of the expression on the left to help generate a completion list let qualItems = // we're looking for 'expr.residue'. Drop the residue and the "." if isNil plid then None else let colAtEndOfNames = max 0 (colAtEndOfNamesAndResidue - residue.Length - 1) GetPreciseCompletionListFromExprTypings(line,colAtEndOfNames,filterCtors) match qualItems with | Some(FilterRelevantItems(items)) // Initially we only use the expression typings when looking up, e.g. (expr).Nam or (expr).Name1.Nam // These come through as a long identifier with plid starting with "". Otherwise we try an environment lookup // and then return to the qualItems. This is because the expression typings are a little inaccurate, primarily because // it appears we're getting some typings recorded for non-atomic expressions like "f x" when (match plid with "" :: _ -> true | _ -> false) -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "GetDeclItemsForNamesAtPosition: lookup based on expression typings successful\n") Some items | _ -> // Use an environment lookup as the last resort // Do we need to look for obsolete modules & types? // This will be true when we're after a dot and we want completions in an (otherwise) unseen module. // Note this doesn't mean that we want any unseen things (e.g. private) - only 'Obsolete'! // e.g.: SomeModuleName.SomeObsoleteModule.<$> ~> residueOpt = Some("") let showObsolete = residueOpt.IsSome let envItems = GetEnvironmentLookupResolutions(line,colAtEndOfNamesAndResidue-1,plid,filterCtors,showObsolete) // we should check whether the skipped char is '.' match nameResItems, envItems, qualItems with // First, use unfiltered name resolution items, if they're not empty | NameResResult.Members(items, denv, m), _, _ when items <> [] -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "GetDeclItemsForNamesAtPosition: lookup based on name resolution results successful, #items = %d, exists ctor = %b\n" (items).Length (items |> List.exists (function Item_ctor_group _ -> true | _ -> false))) Some(items, denv, m) // If we have nonempty items from environment that were resolved from a type, then use them... // (that's better than the next case - here we'd return 'int' as a type) | _, FilterRelevantItems(items, denv, m), _ when items <> [] -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "GetDeclItemsForNamesAtPosition: lookup based on name and environment successful\n") Some(items, denv, m) // Try again with the qualItems | _, _, Some(FilterRelevantItems(items)) -> Some(items) | _ -> None member x.GetDeclarations line lineStr colAtEndOfNames (names,residue) : Decls = use t = Trace.Call("CompilerServices","GetDeclarations", fun _->sprintf " line=%+A,colAtEndOfNames=%+A,names=%+A" line colAtEndOfNames names) ErrorScope.Protect Range.range0 (fun () -> match GetDeclItemsForNamesAtPosition(Some(names),Some(residue),line,lineStr,colAtEndOfNames,ResolveTypeNamesToTypeRefs) with | None -> Decls [| |] | Some(items,denv,m) -> Decls.Create(infoReader,m,denv,items,syncop)) (fun msg -> //#if DEBUG Decls [| Declaration("", (lazy(msg,None)), 0, syncop) |] //#else // Decls [| |] //#endif ) member scope.GetReferenceResolutionDataTipText(line,lineStr,col) : TextWithXmlSig = let pos = Range.mk_pos (line+1) col let LineIfExists(append) = if not(String.IsNullOrEmpty(append)) then append.Trim([|' '|])+"\n" else "" ErrorScope.Protect Range.range0 (fun () -> let matches = capturedReferenceResolutions |> List.filter(fun resolved->(Range.range_contains_pos resolved.originalReference.Range pos) && (resolved.originalReference.Range <> rangeStartup)) match matches with | [resolved] -> let tip = match resolved.resolvedFrom with | AssemblyFolders -> LineIfExists(resolved.resolvedPath) + LineIfExists(resolved.fusionName) + "Found by AssemblyFolders registry key" | AssemblyFoldersEx -> LineIfExists(resolved.resolvedPath) + LineIfExists(resolved.fusionName) + "Found by AssemblyFoldersEx registry key" | TargetFrameworkDirectory -> LineIfExists(resolved.resolvedPath) + LineIfExists(resolved.fusionName) + ".NET Framework" | RawFileName -> LineIfExists(resolved.fusionName) | GlobalAssemblyCache -> LineIfExists(resolved.fusionName) + "Global Assembly Cache\n" + LineIfExists(resolved.redist) | Unknown | Path _ -> LineIfExists(resolved.resolvedPath) + LineIfExists(resolved.fusionName) (tip.TrimEnd([|'\n'|])) ,None | _::_ -> failwith "Did not expect multiple results" | [] -> "",None) (fun err -> err,None) // GetDataTipText: return the "pop up" (or "Quick Info") text given a certain context. member x.GetDataTipText line lineStr colAtEndOfNames names : TextWithXmlSig = use t = Trace.Call("CompilerServices","GetDataTipText", fun _->sprintf " line=%+A,idx=%+A,names=%+A" line colAtEndOfNames names) let Compute() = ErrorScope.Protect Range.range0 (fun () -> match GetDeclItemsForNamesAtPosition(Some(names),None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToTypeRefs) with | None -> "", None | Some(items,denv,m) -> match items with | [] -> "",None | item :: rest -> let item,xmlSigToAdd = FormatDescriptionOfItem infoReader m denv item let restText = rest |> List.map (FormatDescriptionOfItem infoReader m denv >> fst) // Note: If you change the formatting of ToolTips to not contain "----" when multiple items // were returned, make sure to change the test "Quickinfo.OnlyClassInfo" and "Quickinfo.AsyncToolTips" as well. let text = System.String.Join("\n-------------\n", Array.of_list (item :: restText)) (text,xmlSigToAdd)) (fun err -> err,None) // See devdiv bug 646520 for rationale behind truncating and caching these quick infos (they can be big!) let key = line,colAtEndOfNames,lineStr let maxLinesInText = 45 match getDataTipTextCache.TryGet(key) with | Some(res) -> res | None -> let text,xmlSigToAdd = Compute() // Trim it down to a reasonable size if necessary. let lines = text.Split([|'\n'|],maxLinesInText+1) // Need one more than max to determine whether there is truncation. let truncate = lines.Length>maxLinesInText let lines = lines |> Seq.truncate maxLinesInText let lines = if truncate then Seq.append lines ["..."] else lines let lines = lines |> Seq.to_array let join = String.Join("\n",lines) let res = (join,xmlSigToAdd) getDataTipTextCache.Put(key,res) res member x.GetF1Keyword line lineStr colAtEndOfNames names : string option = use t = Trace.Call("CompilerServices","GetF1Keyword", fun _->sprintf " line=%+A,idx=%+A,names=%+A" line colAtEndOfNames names) ErrorScope.Protect Range.range0 (fun () -> match GetDeclItemsForNamesAtPosition(Some(names),None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToTypeRefs) with | None -> None | Some(items,denv,m) -> match items with | [] -> None | item :: _ -> GetF1Keyword infoReader (Microsoft.FSharp.Compiler.Range.range0) item ) (fun _ -> None) member scope.GetMethods line lineStr colAtEndOfNames namesOpt : MethodOverloads = ErrorScope.Protect Range.range0 (fun () -> use t = Trace.Call("CompilerServices", "GetMethods", fun _ -> sprintf "line = %d, idx = %d, names = %+A" line colAtEndOfNames namesOpt) match GetDeclItemsForNamesAtPosition(namesOpt,None,line,lineStr,colAtEndOfNames,ResolveTypeNamesToCtors) with | None -> MethodOverloads("",[| |]) | Some(items,denv,m) -> MethodOverloads.Create(infoReader,m,denv,items)) (fun msg -> MethodOverloads(msg,[| |])) member scope.GetDeclarationLocationInternal (forceFsiGeneration : bool)(line : int)(lineStr : string)(idx : int)(names : Names)(tag : tokenId option)(isDecl : bool) : FindDeclResult = // Get the fully-qualified name of a `namedItemInEnv` // // This code relies on the fact that the pretty printing code in tastops.ml has been augmented (in subtle ways) // to attach "mangled paths" to each item that is pretty printed. These paths // are then attached as attributes to the layout object generated by the pretty printing. A somewhat baroque piece of // code in FsiGeneration.Renderer (posTrackMappingBuildingR) consumes these attributes during the rendering process // and uses them to build up a dicitonary that tracks the "path --> line number" mapping as the printing proceeds. // This dictionary is saved. When queried (here), the paths are re-generated using 'fullMangledNameOfItemForFsiGeneration', and // this path is used as a key into the dictionary. // // Thus the behaviour of fullMangledNameOfItemForFsiGeneration must patch the mangled path generation during the recursive-decent pretty printing. // // It would be better to get remove these pseudo-unique paths altogether and instead simply // attach the stamp of the item via a (non-string) attribute to the pretty printing layout object and // do the lookup based on the unique integer stamp. However this would require // (a) rely on having unique stamps for: // - each Abstract IL declaration entity (ILTypeDef, ILMethodDef, ILFieldDef etc.) // - each UnionCase and RecordField declaration entity // (both would be "Good" to have for other reasons, e.g. get rid of pointer equality on these elements in several places) // (b) require us to change the attribution object on layouts to "obj" or some other generic type let rec fullMangledNameOfItemForFsiGeneration x = let stringName = match x with | Item_val v -> approx_full_mangled_name_of_vref v | Item_ucase (UnionCaseInfo(_,v)) -> approx_full_mangled_name_of_ucref v | Item_ecref v -> approx_full_mangled_name_of_ecref v | Item_recdfield (RecdFieldInfo (_, v)) -> approx_full_mangled_name_of_rfref v | Item_typs (_, (TType_app (v, _)) :: _) -> approx_full_mangled_name_of_tcref v | Item_modrefs (v :: _) -> approx_full_mangled_name_of_modref v | Item_meth_group (s, meth :: _) -> match meth with // grab just the first overload | FSMeth (_,_, v) -> approx_full_mangled_name_of_vref v | ILMeth (_,ILMethInfo (t, _, i, _)) -> let methName = i.Name // this is the name only of the method, so we'll have to look up the class's full name also let className = Item_typs ("", [ t.ToType ]) |> fullMangledNameOfItemForFsiGeneration className @ [ methName ] |> PrettyNaming.JoinNamesForFsiGenerationPath | _ -> "" | Item_il_field (ILFieldInfo (ti, fd)) -> let className = Item_typs ("", [ ti.ToType ]) |> fullMangledNameOfItemForFsiGeneration className @ [fd.Name] |> PrettyNaming.JoinNamesForFsiGenerationPath | _ -> "" stringName |> PrettyNaming.SplitNamesForFsiGenerationPath |> PrettyNaming.ChopUnshowableInFsiGenerationPath match tag with | None // we have to be charitable in this case -- this makes `scope.GetDeclarationLocation` backward-compatible | Some TOKEN_IDENT -> match GetDeclItemsForNamesAtPosition (Some(names), None, line, lineStr, idx, ResolveTypeNamesToTypeRefs) with | None | Some ([], _, _) -> FindDeclResult.IdNotFound | Some (h :: _ , denv, m) -> let h' = match h with | Item_meth_group (_, (ILMeth (_,ilinfo)) :: _) // range_of_item, ccu_of_item don't work on IL methods or fields; we'll be okay, though, looking up the method's *type* instead because they've the same CCU / source file | Item_ctor_group (_, (ILMeth (_,ilinfo)) :: _) -> let (ILMethInfo (typeInfo,_,_,_)) = ilinfo Item_typs ("", [ typeInfo.ToType ]) | Item_il_field (ILFieldInfo (typeInfo, _)) -> Item_typs ("", [ typeInfo.ToType ]) | _ -> h match range_of_item g isDecl h' with | None -> FindDeclResult.IdNotFound | Some m -> if verbose then dprintf "tcConfig.fsharpBinariesDir = '%s'\n" sTcConfig.fsharpBinariesDir let filename = filename_of_item g (Some sProjectDir) m h' let resultInFile = FindDeclResult.DeclFound ((start_line_of_range m - 1, start_col_of_range m), filename, []) // REVIEW: WHAT WAS I THINKING HERE? SINCE I JUST GET THE FILENAME (I.E., NOT THE PROJECT) I'M NOT SURE WHERE I'D FIND THE REF'D ASSEMBLIES FOR A SOURCE FILE let generateFsi () = match ccu_of_item g h' with | Some ccu -> match ccu.FileName with | Some n when enableInterfaceGeneration() -> // Generation of source from assembly is currently disabled // it can be turned on using the 'enableInterfaceGeneration' flag FindDeclResult.NeedToGenerate (n, Filename.fullpath g.directoryToResolveRelativePaths, fullMangledNameOfItemForFsiGeneration h) | _ -> FindDeclResult.IdNotFound | _ -> FindDeclResult.IdNotFound if forceFsiGeneration then generateFsi () else if Internal.Utilities.FileSystem.File.SafeExists filename then resultInFile else generateFsi () | _ -> FindDeclResult.IdNotFound member scope.GetDeclarationLocation (line:int)(lineStr:string)(idx:int)(names: Names)(isDecl:bool) : FindDeclResult = scope.GetDeclarationLocationInternal false line lineStr idx names None isDecl //---------------------------------------------------------------------------- // Navigation items. //-------------------------------------------------------------------------- /// Represents an item to be displayed in the navigation bar [] type DeclarationItem(uniqueName : string, name : string, glyph : int, range : range, bodyRange : range, singleTopLevel:bool) = let range_of_m m = ((start_col_of_range m, start_line_of_range m), (end_col_of_range m, end_line_of_range m)) member x.bodyRange = bodyRange member x.UniqueName = uniqueName member x.Name = name member x.Glyph = glyph member x.Range = range_of_m range member x.BodyRange = range_of_m bodyRange member x.IsSingleTopLevel = singleTopLevel member x.WithUniqueName(uniqueName : string) = DeclarationItem(uniqueName, name, glyph, range, bodyRange, singleTopLevel) static member Create(name : string, glyph : int, range : range, bodyRange : range, singleTopLevel:bool) = DeclarationItem("", name, glyph, range, bodyRange, singleTopLevel) /// Represents top-level declarations (that should be in the type drop-down) /// with nested declarations (that can be shown in the member drop-down) type TopLevelDeclaration = { Declaration : DeclarationItem Nested : DeclarationItem[] } /// Represents result of 'GetNavigationItems' operation - this contains /// all the members and currently selected indices. First level correspond to /// types & modules and second level are methods etc. [] type NavigationItems(declarations:TopLevelDeclaration[]) = member x.Declarations = declarations //---------------------------------------------------------------------------- // Untyped scope //---------------------------------------------------------------------------- type UntypedParseResults = { // Error infos Errors : ErrorInfo array // Untyped AST Input : input option // Do not report errors from the type checker StopErrorReporting : bool /// When these files change then the build is invalid DependencyFiles : string list } /// [] type UntypedParseInfo(parsed:UntypedParseResults, syncop:(unit -> unit) -> unit) = let union_ranges_checked r1 r2 = if r1 = 0L then r2 elif r2 = 0L then r1 else union_ranges r1 r2 let range_of_decls' f decls = match (decls |> List.map (f >> (fun (d:DeclarationItem) -> d.bodyRange))) with | hd::tl -> tl |> List.fold (union_ranges_checked) hd | [] -> 0L let range_of_decls = range_of_decls' fst let moduleRange idm others = union_ranges_checked (end_range_of_range idm) (range_of_decls' (fun (a, _, _) -> a) others) let fldspec_range fldspec = match fldspec with | UnionCaseFields(flds) -> flds |> List.fold (fun st (Field(_, _, _, _, _, _, _, m)) -> union_ranges_checked m st) 0L | UnionCaseFullType(ty, _) -> range_of_syntype ty let bodyRange mb decls = union_ranges_checked (range_of_decls decls) mb /// Get information for implementation file let getNavigationFromImplFile (name:string) (modules:moduleImpl list) = // Map for dealing with name conflicts let nameMap = ref Map.empty let addItemName name = let count = defaultArg (!nameMap |> Map.tryFind name) 0 nameMap := (Map.add name (count + 1) (!nameMap)) (count + 1) let uniqueName name idx = let total = Map.find name (!nameMap) sprintf "%s_%d_of_%d" name idx total // Create declaration (for the left dropdown) let createDeclLid(baseName, lid, baseGlyph, m, bodym, nested) = let name = (if baseName <> "" then baseName + "." else "") + (text_of_lid lid) DeclarationItem.Create (name, baseGlyph * 6, m, bodym, false), (addItemName name), nested let createDecl(baseName, (id:ident), baseGlyph, m, bodym, nested) = let name = (if baseName <> "" then baseName + "." else "") + (id.idText) DeclarationItem.Create (name, baseGlyph * 6, m, bodym, false), (addItemName name), nested // Create member-kind-of-thing for the right dropdown let createMemberLid(lid, baseGlyph, m) = DeclarationItem.Create(text_of_lid lid, baseGlyph * 6, m, m, false), (addItemName(text_of_lid lid)) let createMember((id:ident), baseGlyph, m) = DeclarationItem.Create(id.idText, baseGlyph * 6, m, m, false), (addItemName(id.idText)) // Process let-binding let processBinding isMember (Binding(_, kind, _, _, _, _, ValSynData(memebrOpt, _, _), synPat, BindingRhs(_, _, synExpr), m, _)) = let m = match synExpr with | Expr_typed(e, _, _) -> range_of_synexpr e // fix range for properties with type annotations | _ -> range_of_synexpr synExpr match synPat, memebrOpt with | Pat_lid(lid, _, _, _, _), Some(flags) when isMember -> let icon = match flags.MemberKind with | MemberKindClassConstructor | MemberKindConstructor | MemberKindMember -> if (flags.MemberIsOverrideOrExplicitImpl || flags.MemberIsVirtual) then iIconGroupMethod2 else iIconGroupMethod | MemberKindPropertyGetSet | MemberKindPropertySet | MemberKindPropertyGet -> iIconGroupProperty let lidShow, rangeMerge = match lid with | slf::nm::_ -> (List.tl lid, nm.idRange) | hd::_ -> (lid, hd.idRange) | _ -> (lid, m) [ createMemberLid(lidShow, icon, union_ranges rangeMerge m) ] | Pat_lid(lid, _, _, _, _), _ -> [ createMemberLid(lid, iIconGroupConstant, union_ranges (List.hd lid).idRange m) ] | _ -> [] // Process a class declaration or F# type declaration let rec processTycon baseName (TyconDefn(ComponentInfo(_, _, _, _, lid, _, _, _, _), repr, membDefns, m)) = let topMembers = processMembers membDefns |> snd match repr with | TyconDefnRepr_class(kind, membDefns, mb) -> // mb is odd // F# class declaration let members = processMembers membDefns |> snd let nested = members@topMembers ([ createDeclLid(baseName, lid, iIconGroupClass, m, bodyRange (start_range_of_range mb) nested, nested) ] : ((DeclarationItem * int * _) list)) | TyconDefnRepr_simple(simple, _) -> // F# type declaration match simple with | TyconCore_union(_, cases, mb) -> // mb has wrong end, we use only start let cases = [ for (UnionCase(_, id, fldspec, _, _, m)) in cases -> createMember(id, iIconGroupValueType, union_ranges (fldspec_range fldspec) id.idRange) ] let nested = cases@topMembers [ createDeclLid(baseName, lid, iIconGroupUnion, m, bodyRange (start_range_of_range mb) nested, nested) ] | TyconCore_enum(cases, mb) -> // mb is wrong let cases = [ for (EnumCase(_, id, _, _, m)) in cases -> createMember(id, iIconGroupEnumMember, m) ] let nested = cases@topMembers [ createDeclLid(baseName, lid, iIconGroupEnum, m, bodyRange (start_range_of_range mb) nested, nested) ] | TyconCore_recd(_, fields, mb) -> let fields = [ for (Field(_, _, id, _, _, _, _, m)) in fields do if (id.IsSome) then yield createMember(id.Value, iIconGroupFieldBlue, m) ] let nested = fields@topMembers [ createDeclLid(baseName, lid, iIconGroupType, m, bodyRange mb nested, nested) ] // no start_range_of_range here, records work fine | TyconCore_abbrev(_, mb) -> [ createDeclLid(baseName, lid, iIconGroupTypedef, m, bodyRange mb topMembers, topMembers) ] // should be ok too //| TyconCore_general of TyconKind * (SynType * range * ident option) list * (valSpfn * MemberFlags) list * fieldDecls * bool * bool * range //| TyconCore_asm of ILType * range //| TyconCore_repr_hidden of range | _ -> [] // Returns class-members for the right dropdown and processMembers members : (int64 * list) = let members = members |> List.map (fun memb -> (range_of_classmember memb, match memb with | ClassMemberDefn_let_bindings(binds, _, _, _) -> List.collect (processBinding false) binds | ClassMemberDefn_member_binding(bind, _) -> processBinding true bind | ClassMemberDefn_field(Field(_, _, Some(rcid), ty, _, _, _, _), _) -> [ createMember(rcid, iIconGroupFieldBlue, (range_of_syntype ty)) ] | ClassMemberDefn_slotsig(ValSpfn(_, id, _, ty, _, _, _, _, _, _, _), flags, _) -> [ createMember(id, iIconGroupMethod2, (range_of_syntype ty)) ] | ClassMemberDefn_tycon(tycon, _, m3) -> failwith "tycon as member????" //processTycon tycon | ClassMemberDefn_interface(synty, Some(membs), _) -> processMembers membs |> snd | _ -> [] )) ((members |> Seq.map fst |> Seq.fold union_ranges_checked 0L), (members |> List.map snd |> List.concat)) // Process declarations in a module that belong to the right drop-down (let bindings) let processNestedDeclarations decls = decls |> List.collect (function | Def_let(_, binds, _) -> List.collect (processBinding false) binds | _ -> [] ) // Process declarations nested in a module that should be displayed in the left dropdown // (such as type declarations, nested modules etc.) let rec processTopLevelDeclarations(baseName, decls) = decls |> List.collect (function | Def_module_abbrev(id, lid, m) -> [ createDecl(baseName, id, iIconGroupModule, m, range_of_lid lid, []) ] | Def_module(ComponentInfo(_, _, _, _, lid, _, _, _, _), decls, _, m) -> // Find let bindings (for the right dropdown) let nested = processNestedDeclarations(decls) let newBaseName = (if (baseName = "") then "" else baseName+".") + (text_of_lid lid) // Get nested modules and types (for the left dropdown) let other = processTopLevelDeclarations(newBaseName, decls) createDeclLid(baseName, lid, iIconGroupModule, m, union_ranges_checked (range_of_decls nested) (moduleRange (range_of_lid lid) other), nested)::other | Def_tycons(tydefs, _) -> tydefs |> List.collect (processTycon baseName) | Def_partial_tycon(ComponentInfo(_, _, _, _, lid, _, _, _, mimpl), membDefns, m) -> // Type augmentation / extension let membersM, nested = processMembers membDefns [ createDeclLid(baseName, lid, iIconGroupClass, m, union_ranges (start_range_of_range membersM) (range_of_decls nested), nested) ] | Def_exn(ExconDefn(ExconCore(_, (UnionCase(_, id, fldspec, _, _, _)), _, _, _, _), membDefns, _), m) -> // Exception declaraton let nested = processMembers membDefns |> snd [ createDecl(baseName, id, iIconGroupException, m, fldspec_range fldspec, nested) ] | _ -> [] ) // Collect all the items let items = // Show base name for this module only if it's not the root one let singleTopLevel = (modules.Length = 1) modules |> List.collect (fun (ModuleOrNamespaceImpl(id,isModule,decls,_,_,_,m)) -> let baseName = if (not singleTopLevel) then text_of_lid id else "" // Find let bindings (for the right dropdown) let nested = processNestedDeclarations(decls) // Get nested modules and types (for the left dropdown) let other = processTopLevelDeclarations(baseName, decls) // Create explicitly - it can be 'single top level' thing that is hidden let decl = DeclarationItem.Create (text_of_lid id, iIconGroupModule * 6, m, union_ranges_checked (range_of_decls nested) (moduleRange (range_of_lid id) other), singleTopLevel), (addItemName(text_of_lid id)), nested decl::other ) let items = items |> Array.of_list |> Array.map (fun (d, idx, nest) -> let nest = nest |> Array.of_list |> Array.map (fun (decl, idx) -> decl.WithUniqueName(uniqueName d.Name idx)) nest |> Array.sortInPlaceWith (fun a b -> compare a.Name b.Name) { Declaration = d.WithUniqueName(uniqueName d.Name idx); Nested = nest } ) items |> Array.sortInPlaceWith (fun a b -> compare a.Declaration.Name b.Declaration.Name) new NavigationItems(items) member scope.Results = parsed /// Get declaraed items and the selected item at the specified location member private scope.GetNavigationItemsImpl() = ErrorScope.Protect Range.range0 (fun () -> use t = Trace.Call("CompilerServices", "GetNavigationItems", fun _ -> "") match parsed.Input with | Some(ImplFileInput(ImplFile(modname,isScript,qualName,pragmas,hashDirectives,modules,canContainEntryPoint))) -> getNavigationFromImplFile modname modules | Some(SigFileInput(SigFile(modname,qualName,pragmas,hashDirectives,modules))) -> new NavigationItems([| |]) | _ -> new NavigationItems([| |]) ) (fun msg -> new NavigationItems([| |])) member private scope.ValidateBreakpointLocationImpl((line,col)) = let pos = mk_pos (line+1) col let adjust p = (line_of_pos p - 1),(col_of_pos p) let adjustRange m = adjust (start_of_range m), adjust (end_of_range m) // Process let-binding let findBreakPoints allowSameLine = let checkRange m = [ if range_contains_pos m pos || (allowSameLine && start_line_of_range m = line_of_pos pos) then yield adjustRange m ] let walkBindSeqPt sp = [ match sp with SequencePointAtBinding m -> yield! checkRange m | _ -> () ] let walkForSeqPt sp = [ match sp with SequencePointAtForLoop m -> yield! checkRange m | _ -> () ] let walkWhileSeqPt sp = [ match sp with SequencePointAtWhileLoop m -> yield! checkRange m | _ -> () ] let walkTrySeqPt sp = [ match sp with SequencePointAtTry m -> yield! checkRange m | _ -> () ] let walkWithSeqPt sp = [ match sp with SequencePointAtWith m -> yield! checkRange m | _ -> () ] let walkFinallySeqPt sp = [ match sp with SequencePointAtFinally m -> yield! checkRange m | _ -> () ] let rec walkBind (Binding(_, _, _, _, _, _, ValSynData(memFlagsOpt,_,_), pat, BindingRhs(_,_,e), _, spInfo)) = [ // Don't yield the binding sequence point if there are any arguments, i.e. we're defining a function or a method let isFunction = isSome memFlagsOpt || match pat with | Pat_lid (_,_,args,_,_) when nonNil args -> true | _ -> false if not isFunction then yield! walkBindSeqPt spInfo yield! walkExpr (isFunction || (match spInfo with SequencePointAtBinding _ -> false | _-> true)) e ] and walkExprs es = [ for e in es do yield! walkExpr false e ] and walkBinds es = [ for e in es do yield! walkBind e ] and walkMatchClauses cl = [ for (Clause(_,whenExpr,e,_,_)) in cl do match whenExpr with Some e -> yield! walkExpr false e | _ -> () yield! walkExpr true e; ] and walkExprOpt (spAlways:bool) eOpt = [ match eOpt with Some e -> yield! walkExpr spAlways e | _ -> () ] // Determine the breakpoint locations for an expression. spAlways indicates we always // emit a breakpoing location for the expression unless it is a syntactic control flow construct and walkExpr (spAlways:bool) e = [ if spAlways && not (IsControlFlowExpression e) then yield! checkRange (range_of_synexpr e) match e with | Expr_arb _ | Expr_lid_get _ | Expr_quote _ | Expr_asm _ | Expr_static_optimization _ | Expr_null _ | Expr_typeof _ | Expr_id_get _ | Comp_zero _ | Expr_const _ -> () | Expr_isinst (e,_,_) | Expr_upcast (e,_,_) | Expr_addrof (_,e,_,_) | Expr_comprehension (_,_,e,_) | Expr_array_or_list_of_seq (_,e,_) | Expr_typed (e,_,_) | Expr_throwaway (e,_) | Expr_do (e,_) | Expr_assert (e,_) | Expr_lvalue_get (e,_,_) | Expr_lid_set (_,e,_) | Expr_new (_,_,e,_) | Expr_tyapp (e,_,_) | Expr_constr_field_get (e,_,_,_) | Expr_downcast (e,_,_) | Expr_inferred_upcast (e,_) | Expr_inferred_downcast (e,_) | Expr_lazy (e, _) | Expr_trait_call(_,_,e,_) | Comp_yield (_,e,_) | Comp_yieldm (_,e,_) | Comp_do_bind (e,_) | Expr_paren(e,_) -> yield! walkExpr false e | Expr_lid_indexed_set (_,e1,e2,_) | Expr_lvalue_set (e1,_,e2,_) | Expr_constr_field_set (e1,_,_,e2,_) | Expr_ifnull (e1,e2,_) | Expr_app (_,e1,e2,_) -> yield! walkExpr false e1; yield! walkExpr false e2; | Expr_array_or_list (_,es,_) | Expr_tuple (es,_) -> yield! walkExprs es | Expr_recd (_,_,fs,_) -> yield! walkExprs (List.map snd fs) | Expr_impl (_,_,bs,is,_) -> yield! walkBinds bs ; for (InterfaceImpl(_,bs,_)) in is do yield! walkBinds bs | Expr_while (spWhile,e1,e2,_) -> yield! walkWhileSeqPt spWhile yield! walkExpr false e1; yield! walkExpr true e2; | Expr_for (spFor,_,e1,_,e2,e3,_) -> yield! walkForSeqPt spFor yield! walkExpr false e1; yield! walkExpr true e2; yield! walkExpr true e3; | Expr_foreach (spFor,_,_,e1,e2,_) -> yield! walkForSeqPt spFor yield! walkExpr false e1; yield! walkExpr true e2; | Expr_lambda (_,_,_,e,_) -> yield! walkExpr true e; | Expr_match (spBind,e,cl,_,_) -> yield! walkBindSeqPt spBind yield! walkExpr false e; for (Clause(_,whenExpr,e,_,_)) in cl do yield! walkExprOpt false whenExpr yield! walkExpr true e; | Expr_let (_,_,bs,e,_) -> yield! walkBinds bs ; yield! walkExpr true e; | Expr_try_catch (e,_,cl,_,_,spTry,spWith) -> yield! walkTrySeqPt spTry yield! walkWithSeqPt spWith yield! walkExpr true e yield! walkMatchClauses cl | Expr_try_finally (e1,e2,_,spTry,spFinally) -> yield! walkExpr true e1 yield! walkExpr true e2 yield! walkTrySeqPt spTry yield! walkFinallySeqPt spFinally | Expr_seq (spSeq,_,e1,e2,_) -> yield! walkExpr (match spSeq with SuppressSequencePointOnStmtOfSequential -> false | _ -> true) e1 yield! walkExpr (match spSeq with SuppressSequencePointOnExprOfSequential -> false | _ -> true) e2 | Expr_cond (e1,e2,e3opt,spBind,_,_) -> yield! walkBindSeqPt spBind yield! walkExpr false e1 yield! walkExpr true e2 yield! walkExprOpt true e3opt | Expr_lbrack_get (e1,es,_,_) -> yield! walkExpr false e1; yield! walkExprs es; | Expr_lbrack_set (e1,es,e2,_,_) -> yield! walkExpr false e1; yield! walkExprs es; yield! walkExpr false e2; | Expr_lvalue_indexed_set (e1,_,e2,e3,_) -> yield! walkExpr false e1; yield! walkExpr false e2; yield! walkExpr false e3; | Comp_bind (spBind,_,_,e1,e2,_) -> yield! walkBindSeqPt spBind yield! walkExpr true e1 yield! walkExpr true e2 ] // Process a class declaration or F# type declaration let rec walkTycon (TyconDefn(ComponentInfo(_, _, _, _, lid, _, _, _, _), repr, membDefns, m)) = [ for m in membDefns do yield! walkMember m match repr with | TyconDefnRepr_class(_, membDefns, _) -> for m in membDefns do yield! walkMember m | _ -> () ] // Returns class-members for the right dropdown and walkMember memb = [ match memb with | ClassMemberDefn_let_bindings(binds, _, _, _) -> yield! walkBinds binds | ClassMemberDefn_implicit_ctor(_,_,_,_,m) -> yield! checkRange m | ClassMemberDefn_member_binding(bind, _) -> yield! walkBind bind | ClassMemberDefn_interface(synty, Some(membs), _) -> for m in membs do yield! walkMember m | ClassMemberDefn_inherit(_, _, m) -> // can break on the "inherit" clause yield! checkRange m | _ -> () ] // Process declarations nested in a module that should be displayed in the left dropdown // (such as type declarations, nested modules etc.) let rec walkDecl decl = [ match decl with | Def_let(_, binds, m) -> if range_contains_pos m pos then yield! walkBinds binds | Def_expr(spExpr,expr, m) -> yield! walkBindSeqPt spExpr yield! walkExpr false expr | Def_module_abbrev(id, lid, m) -> () | Def_module(ComponentInfo(_, _, _, _, lid, _, _, _, _), decls, _, m) -> if range_contains_pos m pos then for d in decls do yield! walkDecl d | Def_tycons(tydefs, m) -> if range_contains_pos m pos then for d in tydefs do yield! walkTycon d | Def_partial_tycon(ComponentInfo(_, _, _, _, lid, _, _, _, mimpl), membDefns, m) -> if range_contains_pos m pos then for m in membDefns do yield! walkMember m | Def_exn(ExconDefn(ExconCore(_, _, _, _, _, _), membDefns, _), m) -> if range_contains_pos m pos then for m in membDefns do yield! walkMember m | _ -> () ] // Collect all the items let walkModule (ModuleOrNamespaceImpl(id,isModule,decls,_,_,_,m)) = if range_contains_pos m pos then [ for d in decls do yield! walkDecl d ] else [] /// Get information for implementation file let walkImplFile (modules:moduleImpl list) = [ for x in modules do yield! walkModule x ] match parsed.Input with | Some(ImplFileInput(ImplFile(_,_,_,_,_,modules,_))) -> walkImplFile modules | _ -> [] ErrorScope.Protect Range.range0 (fun () -> // Find the last breakpoint reported where the position is inside the region match findBreakPoints false |> List.rev with | h::t -> Some(h) | _ -> // If there is no such breakpoint, look for any breakpoint beginning on this line match findBreakPoints true with | h::t -> Some(h) | _ -> None) (fun msg -> None) /// When these files appear or disappear the configuration for the current project is invalidated. member scope.DependencyFiles() : string list = parsed.DependencyFiles member scope.FileName = match parsed.Input with | Some(ImplFileInput(ImplFile(modname, _, _, _, _, _, _))) | Some(SigFileInput(SigFile(modname, _, _, _, _))) -> modname | _ -> "" // Get items for the navigation drop down bar member scope.GetNavigationItems() = use t = Trace.Call("SyncOp","GetNavigationItems", fun _->"") // This does not need to be run on the background thread scope.GetNavigationItemsImpl() member scope.ValidateBreakpointLocation(pos:Position) = use t = Trace.Call("SyncOp","ValidateBreakpointLocation", fun _->"") // This does not need to be run on the background thread scope.ValidateBreakpointLocationImpl(pos) //---------------------------------------------------------------------------- // parseSource builds all the information necessary to report errors, match braces and build scopes // See service.cpp for the code that picks up the generated data. We don't call the C++ sinks // functions directly (i.e. via 'external' calls) since I've had problems // using to build DLLs that call from C to and back again. Instead // we return an array of specifications of calls that need to be made. //-------------------------------------------------------------------------- module Parser = //---------------------------------------------------------------------------- // Error handling for parsing & type checking //-------------------------------------------------------------------------- let push r x = r := x :: !r type ErrorHandler(reportErrors, mainInputFileName, tcConfig : TcConfig, source:string) = let errors = ref false let errorsAndWarningsCollector = ref [] let errorCount = ref 0 let errorsAreOn = ref false let errorsAreFullyOff = ref false // We'll need number of lines for adjusting error messages at EOF let fileInfo = (source |> Seq.sum_by (fun c -> if c = '\n' then 1 else 0), // number of lines in the source file source.Length - source.LastIndexOf("\n",StringComparison.Ordinal) - 1) // length of the last line // This function gets called whenever an error happens during parsing or checking let errorSink warn exn = if reportErrors then let warn = warn && not (ReportWarningAsError tcConfig.globalWarnLevel tcConfig.specificWarnOff tcConfig.specificWarnAsError tcConfig.globalWarnAsError exn) if (!errorsAreOn && not !errorsAreFullyOff && (not warn or ReportWarning tcConfig.globalWarnLevel tcConfig.specificWarnOff exn)) then let oneError trim exn = // We use the first line of the file as a fallbackRange for reporting unexpected errors. // Not ideal, but it's hard to see what else to do. let fallbackRange = rangeN mainInputFileName 1 let ei = ErrorInfo.CreateFromExceptionAndAdjustEof(exn,warn,trim,fallbackRange,fileInfo) Trace.PrintLine("UntypedParseAux", fun _ -> sprintf "Reporting one error: %s\n" (ei.ToString())) push errorsAndWarningsCollector ei if not warn then errors := true; incr errorCount let mainError,relatedErrors = Build.SplitRelatedErrors exn oneError false mainError List.iter (oneError true) relatedErrors let errorLogger = { new ErrorLogger with member x.WarnSink(exn) = errorSink true exn member x.ErrorSink(exn) = errorSink false exn member x.ErrorCount = !errorCount } let errorsOn () = errorsAreOn := true let errorsOff() = errorsAreOn := false (* Errors on while parsing project arguments *) do errorsOn() // Public members member x.GetErrorsState() = !errorsAreOn member x.ErrorLogger = errorLogger member x.ErrorSink = errorSink member x.PushErrorInfo ei = push errorsAndWarningsCollector ei incr errorCount member x.CollectedErrorsAndWarnings = !errorsAndWarningsCollector member x.ErrorCount = !errorCount // switch error logging on/off member x.ErrorsOn() = errorsOn() member x.ErrorsOff() = errorsOff() member x.ErrorsFullyOff() = errorsAreFullyOff := true; member x.AnyErrors = !errors /// Locate addition #r references for script file. let AdditionalReferencesFromScript(mainInputFileName,tcConfig,parseResult,tcImports,tcGlobals,projectDir,tcStateForPriorInputs:tcState,lexResourceManager,(errHandler:ErrorHandler)) = if Build.IsScript(mainInputFileName) then // Process meta commands to get referenced DLLs let fileSpecificTcConfig = ApplyMetaCommandsFromInputToTcConfig tcConfig parseResult let capturedReferenceResolutions = GetResolvedAssemblyInformation(fileSpecificTcConfig) Trace.PrintLine("UntypedParseAux", fun () -> sprintf "Computing parse for fsx with referenced DLLs %+A" fileSpecificTcConfig.referencedDLLs) // Handle errors and warnings of #loaded files. See 3149. let FilterByHashLoaded handler = function | HashLoadedSourceHasIssues _ as loadproblem -> handler loadproblem | _ -> () // From here, we're assuming this is a script project and that there are no other files in the project. let priorErrorsAreOn = errHandler.GetErrorsState() errHandler.ErrorsOn() // Don't show errors for prior parsing. let errorLogger = { new ErrorLogger with member x.ErrorCount=0; member x.WarnSink(err) = FilterByHashLoaded errHandler.ErrorLogger.WarnSink err; member x.ErrorSink(err) = FilterByHashLoaded errHandler.ErrorLogger.ErrorSink err } let build,dependencyFiles = IncrementalFSharpBuild.Create (fileSpecificTcConfig, projectDir, "myscript.dll", tcStateForPriorInputs.NiceNameGenerator, lexResourceManager, [], // Files come from loaded sources. true, IncrementalFSharpBuild.BuildEvents.Default,errorLogger, (fun (e:exn) (m:range) ->raise e)); if priorErrorsAreOn then errHandler.ErrorsOn() else errHandler.ErrorsOff() // Don't show errors for prior parsing. let loadedSourcesCount = List.length (fileSpecificTcConfig.GetAvailableLoadedSources()) let build = IncrementalFSharpBuild.EvalTypeCheckSlot(loadedSourcesCount,build) match IncrementalFSharpBuild.GetAntecedentTypeCheckResultsBySlot(loadedSourcesCount,build) with | Some(tcState,tcImports,tcGlobals,tcConfig,_) -> tcImports, tcState, tcGlobals,tcConfig,capturedReferenceResolutions | None -> tcImports,tcStateForPriorInputs, tcGlobals,tcConfig,capturedReferenceResolutions else // Process meta commands to get warnings for referenced DLLs ApplyMetaCommandsFromInputToTcConfig tcConfig parseResult |> ignore tcImports,tcStateForPriorInputs,tcGlobals,tcConfig,[] //---------------------------------------------------------------------------- // Parsing //-------------------------------------------------------------------------- let parseSource (source: string) (matchBracesOnly:bool) (reportErrors:bool) (mainInputFileName:string) (projectSourceFiles : string list) (tcConfig : TcConfig) : ( // Error infos ErrorInfo array * // Brace matchin (int * int * int * int * int * int * int * int) array (* MatchPair*) * // Untyped AST input option * // Any errors during parsing bool ) = try Trace.PrintLine("CompilerServices", fun _ -> sprintf "Service.parseSource %s, matchBraces = %b, reportErrors = %b" mainInputFileName matchBracesOnly reportErrors) // Initialize the error handler let errHandler = new ErrorHandler(reportErrors, mainInputFileName, tcConfig, source) // This helps reason=MethodTip to work - todo: investigate why. reason=MethodTip // calls with only partial text. Preumably adding this causes the final EndParameters // call to refer to a different line than the StartParameters call we're really interested in // Or something like that. Yuck. let source = source ^ "\n\n\n" let lexbuf = UnicodeLexing.StringAsLexbuf source let adjust p = (line_of_pos p - 1),(col_of_pos p) let matchPairRef = ref [] let matchPairSink m1 m2 = if matchBracesOnly then let s1,s2 = adjust (start_of_range m1) let s3,s4 = adjust (end_of_range m1) let s5,s6 = adjust (start_of_range m2) let s7,s8 = adjust (end_of_range m2) Trace.PrintLine("MatchPairs", fun _ -> sprintf "MatchPair: (%s,%s)\n" (string_of_range m1) (string_of_range m2)); push matchPairRef (s1,s2,s3,s4,s5,s6,s7,s8) use unwind = InstallGlobalErrorLogger (fun oldLogger -> errHandler.ErrorLogger) (* Errors on while parsing project arguments *) let parseResult = if verbose then dprintf "Parsing, text = \n<<<\n%s\n>>>\n" source; // If we're editing a script then we define INTERACTIVE otherwise COMPILED. Since this parsing for intellisense we always // define EDITING let conditionalCompilationDefines = SourceFile.AdditionalDefinesForUseInEditor(mainInputFileName) @ tcConfig.conditionalCompilationDefines (* Note: there is currently no way to override this *) let syntaxFlagRequired = tcConfig.ComputeSyntaxFlagRequired(mainInputFileName) let lightSyntaxStatusInital = tcConfig.ComputeLightSyntaxInitialStatus mainInputFileName let lightSyntaxStatus = LightSyntaxStatus(lightSyntaxStatusInital,true) // Note: we don't really attempt to intern strings across a large scope let lexResourceManager = new Lexhelp.LexResourceManager() let lexargs = mkLexargs((fun () -> tcConfig.implicitIncludeDir), mainInputFileName, conditionalCompilationDefines, lightSyntaxStatus, lexResourceManager, ref [], errHandler.ErrorLogger) Lexhelp.usingLexbufForParsing (lexbuf, mainInputFileName, None) (fun lexbuf -> try let skip = true let tokenizer = Lexfilter.create syntaxFlagRequired lightSyntaxStatus (Lexer.token lexargs skip) lexbuf let lexfun = tokenizer.lexer if matchBracesOnly then (* Quick bracket matching parse *) let parenTokensBalance t1 t2 = match t1,t2 with | (LPAREN,RPAREN) | (LBRACE,RBRACE) | (SIG,END) | (STRUCT,END) | (LBRACK_BAR,BAR_RBRACK) | (LBRACK,RBRACK) | (BEGIN,END) -> true | (LQUOTE q1,RQUOTE q2) when q1 = q2 -> true | _ -> false let rec matchBraces stack = match lexfun lexbuf,stack with | tok2,((tok1,m1) :: stack') when parenTokensBalance tok1 tok2-> matchPairSink m1 (Ast.GetLexerRange lexbuf); matchBraces stack' | ((LPAREN | LBRACE | LBRACK | LBRACK_BAR | LQUOTE _) as tok),_ -> matchBraces ((tok,Ast.GetLexerRange lexbuf) :: stack) | (EOF _ | LEX_FAILURE _),_ -> () | _ -> matchBraces stack matchBraces []; None else let canContainEntryPoint = tcConfig.target.IsExe && projectSourceFiles.Length >= 1 && System.String.Compare(List.last projectSourceFiles,mainInputFileName,StringComparison.CurrentCultureIgnoreCase)=0 let parseResult = ParseInput(lexfun,errHandler.ErrorLogger,lexbuf,None,mainInputFileName,canContainEntryPoint) Some(parseResult) with e -> Trace.PrintLine("CompilerServices", fun _ -> sprintf "Could not recover from errors while parsing: %s\n" (e.ToString())) errHandler.ErrorSink false e None) Trace.PrintLine("CompilerServices", fun _ -> sprintf "#errors = %d\n" errHandler.CollectedErrorsAndWarnings.Length); Array.of_list (List.rev errHandler.CollectedErrorsAndWarnings), Array.of_list (List.rev !matchPairRef), parseResult, errHandler.AnyErrors with | e -> System.Diagnostics.Debug.Assert(false, sprintf "Bug seen in service-level parse source: %s\n" (e.ToString())) Trace.PrintLine("CompilerServices", fun _ -> sprintf "Unexpected error %s\n" (e.ToString())); [| |], [| |], None, true type TypecheckResultsSink() = let capturedEnvs = new ResizeArray<_>(100) let capturedExprTypings = new ResizeArray<_>(100) let capturedNameResolutions = new ResizeArray<_>(100) interface Nameres.ITypecheckResultsSink with member sink.NotifyEnvWithScope(scopem,nenv,ad) = capturedEnvs.Add((scopem,nenv,ad)) member sink.NotifyExprHasType(endPos,ty,denv,nenv,ad,m) = capturedExprTypings.Add((endPos,ty,denv,nenv,ad,m)) member sink.NotifyNameResolution(endPos,item,occurenceType,denv,nenv,ad,m) = capturedNameResolutions.Add((endPos,item,occurenceType,denv,nenv,ad,m)) member x.CapturedEnvs = capturedEnvs member x.CapturedExprTypings = capturedExprTypings member x.CapturedNameResolutions = capturedNameResolutions let InstallTypecheckResultsSink sink = Nameres.GlobalTypecheckResultsSink := Some sink { new IDisposable with member x.Dispose() = Nameres.GlobalTypecheckResultsSink := None } //---------------------------------------------------------------------------- // Type-checking //-------------------------------------------------------------------------- let typeCheckSource (parseResult:input option) (source: string) (mainInputFileName:string) (projectFileName:string) (tcConfig : TcConfig) (tcGlobals : TcGlobals) (tcImports : TcImports) (tcState : tcState) (syncop:(unit->unit)->unit) (suppressTypecheckErrors : bool) : (ErrorInfo array * Scope option) = try let projectDir = Filename.dirname (if projectFileName = "" then mainInputFileName else projectFileName) Trace.PrintLine("CompilerServices", fun _ -> sprintf "Service.typeCheckSource %s, projectDir = %s" mainInputFileName projectDir) match parseResult with // When processing the following cases, we don't need to type-check | None -> [| |], None // Run the type checker... | Some parsedMainInput -> // Initialize the error handler let errHandler = new ErrorHandler(true,mainInputFileName,tcConfig, source) // TODO: get rid of this use of global state and pass the sink as a parameter to the type checker use unwind = InstallGlobalErrorLogger (fun oldLogger -> errHandler.ErrorLogger) // If additional references were brought in by the preprocessor then we need to process them let lexResourceManager = new Lexhelp.LexResourceManager() let tcImportsAfterParse,tcStateForPriorInputs,tcGlobals,tcConfig,capturedReferenceResolutions = AdditionalReferencesFromScript(mainInputFileName,tcConfig,parsedMainInput,tcImports,tcGlobals,projectDir, tcState,lexResourceManager,errHandler) // If we got real errors during parsing don;t report any more errors at all, but typecheck for intellisense if suppressTypecheckErrors then errHandler.ErrorsFullyOff() if verbose then tcConfig.includes |> List.iter (fun p -> Trace.PrintLine("CompilerServicesVerbose", fun _ -> sprintf "include directory '%s'\n" p)) ; tcConfig.implicitOpens |> List.iter (fun p -> Trace.PrintLine("CompilerServicesVerbose", fun _ -> sprintf "implicit open '%s'\n" p)) ; tcConfig.referencedDLLs |> List.iter (fun r -> Trace.PrintLine("CompilerServicesVerbose", fun _ -> sprintf "dll from flags '%s'\n" r.Text)) ; // A problem arises with nice name generation, which really should only // be done in the backend, but is also done in the typechecker for better or worse. // If we don't do this the NNG accumulates data and // we get a memory leak. tcStateForPriorInputs.NiceNameGenerator.Reset(); // Typecheck the real input. let sink = TypecheckResultsSink() // TODO: get rid of this use of global state and pass the sink as a parameter to the type checker use unwind2 = InstallTypecheckResultsSink sink let amap = tcImportsAfterParse.GetImportMap() let tcEnvAtEnd = try // Note: this is a long running operation which will make the background build thread unavailable. // Ideally we should convert this into an Eventually computation, and time slice it, and perform the continuation // as a new SyncOp let (tcEnvAtEnd,_,mimpls),_ = TypecheckMultipleInputs ((fun () -> errHandler.ErrorCount = 0),tcConfig,tcImportsAfterParse,tcGlobals,None,tcStateForPriorInputs,[parsedMainInput]) tcEnvAtEnd with e -> errHandler.ErrorSink false e tcStateForPriorInputs.TcEnvFromSignatures Trace.PrintLine("CompilerServicesVerbose", fun _ -> sprintf "Building and returning scope\n"); Array.of_list (List.rev errHandler.CollectedErrorsAndWarnings), Some (new Scope(tcConfig,tcGlobals, amap ,projectDir,mainInputFileName , sink.CapturedEnvs ,(nenv_of_tenv tcEnvAtEnd), sink.CapturedExprTypings, sink.CapturedNameResolutions, capturedReferenceResolutions, syncop)) with | e -> System.Diagnostics.Debug.Assert(false, sprintf "Bug seen in service-level parse source: %s\n" (e.ToString())) Trace.PrintLine("CompilerServices", fun _ -> sprintf "Unexpected error %s\n" (e.ToString())); [| |], None open TokenClassifications open ItemDescriptions // NOTE: may be better just to move to optional arguments here type ParseOptions = { FileName: string ProjectFileName: string ProjectFileNames: string array ProjectOptions: string array IsIncompleteTypeCheckEnvironment : bool; } static member Defaults = { FileName=""; ProjectFileName=""; ProjectFileNames= Array.empty; ProjectOptions=Array.empty; IsIncompleteTypeCheckEnvironment = false; } /// Whether the two parse options refer to the same project. static member AreSameProjectName(options1,options2) = options1.ProjectFileName = options2.ProjectFileName /// Compare two options sets with respect to the parts of the options that are important to building. static member AreSameProject(options1,options2) = ParseOptions.AreSameProjectName(options1,options2) && options1.ProjectFileNames = options2.ProjectFileNames && options1.ProjectOptions = options2.ProjectOptions /// Compute the project directory. member po.ProjectDirectory with get() = System.IO.Path.GetDirectoryName(po.ProjectFileName) /// A thin wrapper over a Decls object (we could eventually eleiminate one for the other) [] type DeclarationSet(v: Decls) = member x.Items = v.Items [] type TypeCheckInfo(scope: Scope, fsiGens : (string -> string) -> string -> FsiGeneration.FsiGenerationResult, syncop: (unit->unit)->unit) = let runSyncOp f = let result = ref None syncop (fun () -> result := Some(f())) Option.get !result /// Resolve the names at the given location to a set of declarations member info.GetDeclarations((line,colAtEndOfNames),lineStr,names:NamesWithResidue,tokenTag:int) = use t = Trace.Call("SyncOp","GetDeclarations", fun _->sprintf " at=(%d:%d),names=%+A" line colAtEndOfNames names) runSyncOp (fun () -> DeclarationSet(scope.GetDeclarations line lineStr colAtEndOfNames names)) /// Resolve the names at the given location to give a data tip member info.GetDataTipText((x1,x2),lineStr,names:Names,tokenTag:int) = use t = Trace.Call("SyncOp","GetDataTipText", fun _->sprintf " at=(%d:%d),names=%+A tag=%d tokenId=%+A" x1 x2 names tokenTag (tokenTagToTokenId tokenTag)) match tokenTagToTokenId tokenTag with | TOKEN_IDENT -> runSyncOp (fun () -> TextResult(scope.GetDataTipText x1 lineStr x2 names)) | TOKEN_STRING | TOKEN_STRING_TEXT -> runSyncOp (fun () -> TextResult(scope.GetReferenceResolutionDataTipText(x1,lineStr,x2))) | _ -> TextResult(("",None)) member info.GetF1Keyword ((line,colAtEndOfNames),lineStr,names) : string option = use t = Trace.Call("SyncOp","GetF1Keyword", fun _->sprintf " at=(%d:%d),names=%+A" line colAtEndOfNames names) runSyncOp (fun () -> scope.GetF1Keyword line lineStr colAtEndOfNames names) // Resolve the names at the given location to a set of methods member info.GetMethods((x1,x2):Position,lineStr:string,names:Names option,tokenTag:int) = use t = Trace.Call("SyncOp","GetMethods", fun _->sprintf " at=(%d:%d),names=%+A" x1 x2 names) runSyncOp (fun () -> scope.GetMethods x1 lineStr x2 names) member info.GetDeclarationLocationInternal (forceFsiGeneration : bool)((x1, x2) : Position, lineStr:string, names : Names, tokenTag : int, flag : bool) = use t = Trace.Call("SyncOp","GetDeclarationLocation", fun _->sprintf " at=(%d:%d),names=%+A,flag=%+A" x1 x2 names flag) let result = ref None let GetDeclarationLocation() = let res = scope.GetDeclarationLocationInternal forceFsiGeneration x1 lineStr x2 names (Some (tokenTagToTokenId tokenTag)) flag let res = match res with | FindDeclResult.NeedToGenerate (s, fnf, nm) -> // NOTE: This will be called only when 'enableInterfaceGeneration' is set to 'true' assert(enableInterfaceGeneration()) match (fsiGens fnf s) with | Some (f, m, assms) -> // Search for the item that has specified name as the prefix // This fixes the issue when reported name contains additional items (to be unique) // such as ["Class"; "foo"; "1"; "Static"] and we're looking for ["Class"; "foo"] let rec tryGetValueUsingPrefix (names:ResizeArray) = let ok, point = m.TryGetValue(List.of_seq names) if ok then ok, point else names.RemoveAt(names.Count - 1) tryGetValueUsingPrefix names let ok,point = tryGetValueUsingPrefix (ResizeArray.of_list nm) if ok then let x,y = point FindDeclResult.DeclFound ((y, x), f, assms) else FindDeclResult.DeclFound ((0, 0), f, assms) // couldn't find the identifier; let's show the file anyhow | None -> FindDeclResult.NoDeclInfo | _ -> res result:=Some(res) syncop GetDeclarationLocation Option.get !result /// Resolve the names at the given location to the declaration location of the corresponding construct member info.GetDeclarationLocation (p, lineStr, names, tokenTag, flag) = info.GetDeclarationLocationInternal false (p, lineStr, names, tokenTag, flag) /// Information about the compilation environment module CompilerEnvironment = /// These are the names of assemblies that should be referenced for scripting (.fsx) let DefaultReferencesForScripting = scriptingFramework /// These are the names of assemblies that should be referenced for .fs, .ml, .fsi, .mli files that /// are not asscociated with a project let DefaultReferencesForOrphanSources = coreFramework @ extendedFramework // Apply command-line arguments. let ApplyCommandLineArgumentsToTcConfig(commandLineArgs, tcConfig) = try ParseCompilerOptions (fun sourceOrDll -> () ) (Fscopts.GetCoreServiceCompilerOptions tcConfig) commandLineArgs with e -> errorRecovery e range0 #if TRYING_TO_FIX_4577 #else let ProjectGlobalsScope(projectDir) = if String.IsNullOrEmpty(projectDir) then null else let savedDirectory = System.IO.Directory.GetCurrentDirectory() System.IO.Directory.SetCurrentDirectory(projectDir) {new IDisposable with member d.Dispose() = System.IO.Directory.SetCurrentDirectory(savedDirectory) } #endif /// Create a type-check configuration let CreateTcConfig(projectDir,commandLineArgs,mainFileName:string) = let defaultFSharpBinariesDir = match Internal.Utilities.FSharpEnvironment.FSharpRunningBinFolder with Some(dir)->dir | None -> System.Environment.GetEnvironmentVariable("mFSharp_BinDir") #if TRYING_TO_FIX_4577 #else use unwind = ProjectGlobalsScope(projectDir) #endif let tcConfigB = Build.TcConfigBuilder.CreateNew(defaultFSharpBinariesDir, true (* optimize for memory *), projectDir) // The following uses more memory but means we don't take read-exclusions on the DLLs we reference // Could detect well-known assemblies--ie System.dll--and open them with read-locks tcConfigB.openBinariesInMemory <- true; tcConfigB.resolutionEnvironment <- if SourceFile.MustBeSingleFileProject(mainFileName) then MSBuildResolver.RuntimeLike else MSBuildResolver.CompileTimeLike ApplyCommandLineArgumentsToTcConfig(commandLineArgs, tcConfigB) if tcConfigB.framework then // ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- // If you see a failure here running unittests consider whether it it caused by // a mismatched version of Microsoft.Build.Framework. Run unittests under a debugger. If // you see an old version of Microsoft.Build.*.dll getting loaded it it is likely caused by // using an old ITask or ITaskItem from some tasks assembly. // I solved this problem by adding a Unittests.config.dll which has a binding redirect to // the current (right now, 4.0.0.0) version of the tasks assembly. // ~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~- System.Diagnostics.Debug.Assert(false, "Language service requires --noframework flag") tcConfigB.framework<-false tcConfigB /// Gets compiler defines. let CreateEditorDefines(filename:string, compilerFlags :string list) = use t = Trace.Call("CompilerEnvironment","CreateEditorDefines", fun () -> sprintf "filename = %s, compilerFlags = %A" filename compilerFlags) // Ignore errors in command line args on this path // Ideally we would explicitly install DiscardErrorsLogger here. However different threads are using the error logger! // Indeed we really have to move to a model where the error logger is passed explicitly everywhere in the codebase. // For the moment we jsut make the default global error logger discard errors. // use unwind = InstallGlobalErrorLogger (fun _ -> DiscardErrorsLogger) let tcConfigB = CreateTcConfig(@"C:\" (*dummy path unused*), compilerFlags, filename) let tcConfig = TcConfig.Create(tcConfigB,validate=false) let defines = tcConfig.conditionalCompilationDefines let defines = SourceFile.AdditionalDefinesForUseInEditor(filename) @ defines defines let definesCache = MruCache(Flags.definesCacheSize, CreateEditorDefines) /// Publish compiler-flags parsing logic let GetCompilationDefinesForEditing(filename:string, compilerFlags : string list) = definesCache.Get((filename,compilerFlags)) /// Information about the debugging environment module DebuggerEnvironment = /// Return the language ID, which is the expression evaluator id that the /// debugger will use. let GetLanguageID() = System.Guid(0xAB4F38C9u, 0xB6E6us, 0x43baus, 0xBEuy, 0x3Buy, 0x58uy, 0x08uy, 0x0Buy, 0x2Cuy, 0xCCuy, 0xE3uy) [] type TypeCheckResults(errors: ErrorInfo array, scope: TypeCheckInfo option, untypedInfo : UntypedParseInfo) = member pr.Errors = errors member pr.TypeCheckInfo = scope member pr.UntypedParse = untypedInfo /// Callbacks to notify of background changes [] type BackgroundCompilerEvents = { FileTypeCheckStateIsDirty: string -> unit } // Identical to _VSFILECHANGEFLAGS in vsshell.idl type DependencyChangeCode = | Nil = 0x00000000 // Not an actual value - here to make FxCop happy | FileChanged = 0x00000001 | TimeChanged = 0x00000002 | Size = 0x00000004 | Deleted = 0x00000008 | Added = 0x00000010 module Reactor = open Microsoft.FSharp.Control type ReactorCommands = /// Kick off a build. | StartBuild of ParseOptions /// Kick off the most recently known build. | StartRecentBuild /// Do a bit of work on the given build. | Step /// Do some work synchronized in the mailbox. | SyncOp of (unit->unit) * AsyncReplyChannel> /// Do some work not synchronized in the mailbox. | AsyncOp of (unit->unit) /// Stop building after finishing the current unit of work. | StopBuild of AsyncReplyChannel> /// Finish building. | FinishBuild of AsyncReplyChannel> override rc.ToString() = match rc with | StartBuild _->"StartBuild" | StartRecentBuild -> "StartRecentBuild" | Step->"Step" | SyncOp _->"SyncOp" | AsyncOp _->"AsyncOp" | StopBuild _->"StopBuild" | FinishBuild _->"FinishBuild" type ReactorState = | Idling | ActivelyBuilding of ParseOptions | FinishingBuild of ParseOptions * AsyncReplyChannel> | BackgroundError of Exception // An exception was seen in a prior state. The exception is preserved so it can be thrown back to the calling thread. override rs.ToString() = match rs with | Idling->"Idling" | ActivelyBuilding _->"ActivelyBuilding" | FinishingBuild _->"FinishingBuild" | BackgroundError _->"BackgroundError" [] /// There is one global Reactor for the entire language service, no matter how many projects or files /// are open. type Reactor(backgroundCompilerEvents:BackgroundCompilerEvents) = //------------------------------------------------------------------------------------ // Rules for reactive building. // // This phrases the compile as a series of vector functions and vector manipulations. // // Rules written in this language are then transformed into a plan to execute the // various steps of the process in small parts. //----------------------------------------------------------------------------------- let CreateIncrementalBuilder (options:ParseOptions) = use t = Trace.Call("Reactor","CreateIncrementalBuilder", fun () -> sprintf "options = %+A" options) #if TRYING_TO_FIX_4577 #else // Set the project directory use unwind = CompilerEnvironment.ProjectGlobalsScope(options.ProjectDirectory) #endif // Trap and report warnings and errors from creation. use errorScope = new ErrorScope() // Create the builder. let resourceManager = new Lexhelp.LexResourceManager() let sourceFiles = (options.ProjectFileNames|>Array.to_list) let commandLineArgs = options.ProjectOptions |> Array.to_list // Build initial TC config let tcConfigB = CompilerEnvironment.CreateTcConfig(options.ProjectDirectory, commandLineArgs, options.FileName) let outfile,pdbfile,assemblyName = tcConfigB.DecideNames sourceFiles let tcConfig = TcConfig.Create(tcConfigB,validate=true) // Share intern'd strings across all lexing/parsing let niceNameGen = NiceNameGenerator() let lexResourceManager = new Lexhelp.LexResourceManager() // Sink internal errors and warnings. let warnSink (exn:exn) = Trace.PrintLine("IncrementalBuild", (exn.ToString >> sprintf "Background warning: %s")) let errorSink (exn:exn) = Trace.PrintLine("IncrementalBuild", (exn.ToString >> sprintf "Background error: %s")) //-------------------------------------------------------------------------------------------------------- // Background notifications switched back to the given thread context. //-------------------------------------------------------------------------------------------------------- let HandleBackgroundBeforeTypeCheckFile(filename:string) = backgroundCompilerEvents.FileTypeCheckStateIsDirty(filename) let builder, dependencyFiles = let errorLogger = { new ErrorLogger with member x.ErrorCount=0; member x.WarnSink(e)=warnSink e; member x.ErrorSink(e)=errorSink e } IncrementalFSharpBuild.Create (tcConfig, options.ProjectDirectory, assemblyName, niceNameGen, resourceManager, sourceFiles, true, // stay reactive { BeforeTypeCheckFile = HandleBackgroundBeforeTypeCheckFile }, errorLogger, (fun (e:exn)->raise e)) Trace.PrintLine("IncrementalBuild", fun () -> sprintf "CreateIncrementalBuilder: %A" dependencyFiles) #if DEBUG dependencyFiles |> List.iter (fun df -> System.Diagnostics.Debug.Assert(System.IO.Path.IsPathRooted(df.Filename), sprintf "dependency file was not absolute: '%s'" df.Filename)) #endif (tcConfig, builder, dependencyFiles, errorScope.ErrorsAndWarnings) /// Cache of builds keyed by options. let buildCache = MruCache(Flags.buildCacheSize, CreateIncrementalBuilder, areSame = ParseOptions.AreSameProject, areSameForSubsumption = ParseOptions.AreSameProjectName) /// See if the build cache needs to be invalidated. Return true if there was an invalidation. let InvalidateBuildCacheEntry(options,changedFiles: (string * DependencyChangeCode) list) = let t = Trace.Call("ChangeEvents","InvalidateBuildCacheEntry", fun _ -> sprintf "Received notification to invalidate build. Changed files are:\n%A" changedFiles) match buildCache.GetAvailable(options) with | None -> () | Some(tcConfigA,builderA,dependencyFilesA,errorsA) -> let IsExistentialChangeCode(file,code) = (int (code &&& (DependencyChangeCode.Added ||| DependencyChangeCode.Deleted)))<>0 // The list of adds and deletes let existenceChange = changedFiles |> List.filter IsExistentialChangeCode |> List.map fst // The list of files for which an add or delete would cause a refresh of the TcConfig let existenceDependencies = dependencyFilesA |> List.filter(fun dep -> dep.ExistenceDependency) |> List.map(fun dep -> dep.Filename) // REVIEW: this doesn't look correct - we are ignoring the names in 'existenceChange' let acc (last:bool) (file:string) : bool = last or List.exists (fun s->true) existenceChange let configurationRefresh = existenceDependencies |> List.fold acc false if configurationRefresh then Trace.PrintLine("ChangeEvents", fun _ -> "Refreshing configuration") let tcConfigB, builderB, dependencyFilesB, errorsB = CreateIncrementalBuilder(options) buildCache.SetAlternate(options, (tcConfigB, builderB, dependencyFilesB, errorsB)) else Trace.PrintLine("ChangeEvents", fun _ -> "Not refreshing configuration because the change indicated only an incremental build is needed") /// Get the antecedant typecheck state for the give file (in the options). Return none if not available. let GetAntecendantResultWithoutSideEffects(options) = match buildCache.GetAvailable(options) with | Some(_,build,dependencyFiles,createErrors) -> let slotOfFile = IncrementalFSharpBuild.GetSlotOfFileName(options.FileName, build) Some(build, dependencyFiles, createErrors, IncrementalFSharpBuild.GetAntecedentTypeCheckResultsBySlot(slotOfFile,build)) | None->None /// Parses the source file and returns untyped AST let UntypedParseImpl (source,options) syncop = Trace.PrintLine("CompilerServices", fun _ -> "Service.UntypedParseImpl") use t = Trace.CallByThreadNamed("Reactor", "UntypedParseImpl", "ThreadPool", fun _->"") let tcConfig,_,dependencyFiles,_ = buildCache.Get(options) // Q: Whis it it ok to ignore createErrors in the build cache? A: These errors will be appended into the typecheck results // Do the parsing. let parseErrors, matchPairs, inputOpt, anyErrors = Parser.parseSource source false true options.FileName (options.ProjectFileNames |> Array.to_list) tcConfig // Strip everything but the file name. let dependencyFiles = dependencyFiles |> List.map (fun dep->dep.Filename) UntypedParseInfo(parsed = { Errors = parseErrors; Input = inputOpt; StopErrorReporting = anyErrors DependencyFiles = dependencyFiles}, syncop = syncop) /// Parses the source file and returns untyped AST let MatchBracesImpl (source,options) syncop = Trace.PrintLine("CompilerServices", fun _ -> "Service.MatchBracesImpl") use t = Trace.CallByThreadNamed("Reactor", "MatchBracesImpl", "ThreadPool", fun _->"") let tcConfig,_,_,_ = buildCache.Get(options) // Do the parsing. let parseErrors, matchPairs, inputOpt, anyErrors = Parser.parseSource source true false options.FileName (options.ProjectFileNames |> Array.to_list) tcConfig matchPairs |> Array.map (fun (s1,s2,s3,s4,s5,s6,s7,s8) -> ((s1,s2),(s3,s4)),((s5,s6),(s7,s8))) /// Type-check the result obtained by parsing /// The input should be first parsed using 'UntypedParseImpl' let TypeCheckSourceImpl (parseResult:UntypedParseResults) source options syncop = use t = Trace.CallByThreadNamed("Reactor", "TypeCheckSourceImpl", "ThreadPool", fun _->"") match GetAntecendantResultWithoutSideEffects(options) with | Some(build,dependencyFiles,createErrors,Some(tcPriorState,tcImports,tcGlobals,tcConfig,antecedantTimeStamp)) -> // Run the function let suppressTypecheckErrors = parseResult.StopErrorReporting let tcErrors, scopeOpt = Parser.typeCheckSource parseResult.Input source options.FileName options.ProjectFileName tcConfig tcGlobals tcImports tcPriorState syncop suppressTypecheckErrors // Collect thunks for .fsi generators used by GotoDefinition let (generators, build) = IncrementalFSharpBuild.GetFsiGenerators build // Append all the errors together. let errors = [| yield! createErrors; yield! parseResult.Errors if options.IsIncompleteTypeCheckEnvironment then yield! Seq.truncate Flags.maxErrorsOutOfProjectContext tcErrors else yield! tcErrors |] let res = TypeCheckResults (errors = errors, scope = (scopeOpt |> Option.map (fun v -> TypeCheckInfo (v, generators, syncop))), untypedInfo = new UntypedParseInfo(parseResult, syncop)) Some res | _ -> // Either the builder did not exist or the antecedent to the slot was not ready. Return 'None'. // The caller will send a request for a background build of this project. This // will create the builder and notify the UI when the antecedent to the slot is ready. None /// Mailbox dispatch function. let Dispatch (inbox:MailboxProcessor<_>) = // Post an exception back to FinishingBuild channel. let UnexpectedFinishingBuild commandName (channel:AsyncReplyChannel<_>) = channel.Reply(Exception (new Exception(sprintf "[Bug]Did not expect %s during FinishingBuild." commandName))) // Kick off a build. let HandleStartBuild options state = inbox.Post Step match state with | ActivelyBuilding(_) | Idling -> ActivelyBuilding options | FinishingBuild(_) -> state | BackgroundError(_)-> state // Kick off a build of the most recently known project if there is one. let HandleStartRecentBuild = function | Idling -> match buildCache.MostRecent with | Some(options,_)->HandleStartBuild options Idling | None -> Idling | state -> state // Stop the build. let HandleStopBuild (channel:AsyncReplyChannel<_>) state = buildCache.Clear() match state with | ActivelyBuilding(_) | Idling -> channel.Reply(Result ()) | FinishingBuild(options,channel) -> UnexpectedFinishingBuild "StopBuild" channel | BackgroundError(e)-> channel.Reply(Exception e) Idling // Do the given operation let HandleAsyncOp op state = use t = Trace.CallByThreadNamed("Reactor", "HandleAsyncOp", "ThreadPool", fun _->sprintf "state=%+A" state) match state with | ActivelyBuilding(_) | BackgroundError(_) | Idling -> try op() state with e-> System.Diagnostics.Debug.Assert(false, sprintf "Bug in target of HandleAsyncOp: %A: %s\nThe most recent error reported to an error scope: %A\n%+A\n" (e.GetType()) e.Message ErrorScope.MostRecentError e.StackTrace) state | FinishingBuild(options,oldChannel) -> UnexpectedFinishingBuild "AsyncOp" oldChannel Idling // Do the given operation and reply let HandleSyncOp op (channel:AsyncReplyChannel<_>) state = use t = Trace.CallByThreadNamed("Reactor", "HandleSyncOp", "ThreadPool", fun _->sprintf "state=%+A" state) match state with | ActivelyBuilding(_) | Idling -> try op() channel.Reply(Result ()) state with e-> System.Diagnostics.Debug.Assert(false, sprintf "Bug in target of HandleSyncOp: %A: %s\nThe most recent error reported to an error scope: %A\n%+A\n" (e.GetType()) e.Message ErrorScope.MostRecentError e.StackTrace) channel.Reply(Exception e) state | FinishingBuild(options,oldChannel) -> UnexpectedFinishingBuild "SyncOp" channel UnexpectedFinishingBuild "SyncOp" oldChannel Idling | BackgroundError(e)-> channel.Reply(Exception e) Idling // Do a step in the build. let HandleStep state = use t = Trace.CallByThreadNamed("Reactor", "HandleStep", "ThreadPool", fun _->sprintf "state=%+A" state) match state with | FinishingBuild(options,_) | ActivelyBuilding options -> // Gather any require reply channel. let replyChannel = match state with | Idling | ActivelyBuilding(_) | BackgroundError(_)->None | FinishingBuild(_,channel)->Some(channel) try // Do the step. let tcConfig,last,dependencyFiles,createErrors = buildCache.Get(options) let next = IncrementalFSharpBuild.Step(last) // Did the step do work? match next with | Some(next)-> buildCache.SetAlternate(options,(tcConfig,next,dependencyFiles,createErrors)) inbox.Post Step state | None-> match replyChannel with | Some(replyChannel)-> replyChannel.Reply(Result ()) | None->() // Switch to idle state. Idling with e-> System.Diagnostics.Debug.Assert(false, sprintf "[Bug]Failure in HandleStep: %s" (e.ToString())) Trace.PrintLine("CompilerServices", fun _ -> sprintf "[Bug]Failure in HandleStep: %+A\n" e) match replyChannel with | Some(replyChannel)-> replyChannel.Reply(Exception e) Idling | None->BackgroundError e | Idling -> Idling | BackgroundError(e)->state let HandleFinishBuilding (channel:AsyncReplyChannel<_>) = function | ActivelyBuilding(options)-> inbox.Post Step FinishingBuild(options,channel) | FinishingBuild(options,channelOld)-> // Don't expect to get here. If this is required then we need to keep all channels and post back to each // when the build finishes. For now, throw an exception back. UnexpectedFinishingBuild "FinishBuilding" channel UnexpectedFinishingBuild "FinishBuilding" channelOld Idling | Idling-> channel.Reply(Result ()) Idling | BackgroundError e-> // We have a waiting channel to post our exception to. channel.Reply(Exception e) Idling // Async workflow which receives messages and dispatches to worker functions. let rec Loop (state:ReactorState) = Trace.PrintLine("Reactor", fun () -> sprintf "Background compiler state is now: %s\n" (state.ToString())) async { let! msg = inbox.Receive() Trace.PrintLine("Reactor", fun () -> sprintf "Background compiler about to process message %s\n" (msg.ToString())) match msg with | StartBuild options -> return! Loop(HandleStartBuild options state) | StartRecentBuild -> return! Loop(HandleStartRecentBuild state) | Step -> return! Loop(HandleStep state) | SyncOp(op,channel) -> return! Loop(HandleSyncOp op channel state) | AsyncOp(op) -> return! Loop(HandleAsyncOp op state) | StopBuild(channel) -> return! Loop(HandleStopBuild channel state) | FinishBuild(channel) -> return! Loop(HandleFinishBuilding channel state) } Loop Idling let builder = MailboxProcessor<_>.Start(Dispatch) // [Foreground Mailbox Accessors] ----------------------------------------------------------- member r.StartBuilding(options) = builder.Post(StartBuild options) member r.StartBuildingRecent() = builder.Post(StartRecentBuild) member r.StopBuilding() = match builder.PostAndReply(fun replyChannel->StopBuild(replyChannel)) with | Result result->result | Exception excn-> Trace.PrintLine("CompilerServices", fun _ -> sprintf "[Bug]Exception in StopBuilding. Inner exception was:\n%+A\n%s" excn excn.StackTrace) raise excn member r.SyncOp(op) = match builder.PostAndReply(fun replyChannel->SyncOp(op,replyChannel)) with | Result result->result | Exception excn-> Trace.PrintLine("CompilerServices", fun _ -> sprintf "[Bug]Exception in SyncOp Inner exception was:\n%+A\n%s" excn excn.StackTrace) raise excn member r.AsyncOp(op) = builder.Post(AsyncOp(op)) member r.UntypedParse(inputs)= use t = Trace.Call("SyncOp","UntypedParse", fun _->"") let result = ref None let UntypedParseOp () = result:=Some(UntypedParseImpl inputs r.SyncOp) r.SyncOp UntypedParseOp Option.get !result member r.MatchBraces(inputs)= use t = Trace.Call("SyncOp","MatchBraces", fun _->"") let result = ref None let MatchBracesOp () = result:=Some(MatchBracesImpl inputs r.SyncOp) r.SyncOp MatchBracesOp Option.get !result member r.TypeCheckSource(parseSourceRes,source,options)= use t = Trace.Call("SyncOp","TypeCheckSource", fun _->"") let result = ref None let TypeCheckSourceOp () = result:=Some(TypeCheckSourceImpl parseSourceRes source options r.SyncOp) r.SyncOp TypeCheckSourceOp Option.get !result member r.InvalidateConfiguration(options : ParseOptions, changedFiles: (string * DependencyChangeCode) list) = use t = Trace.Call("SyncOp","InvalidateConfiguration", fun _->"") r.AsyncOp (fun () -> InvalidateBuildCacheEntry(options,changedFiles)) r.StartBuilding(options) () member r.WaitForBackgroundCompile() = match builder.PostAndReply(fun replyChannel->FinishBuild(replyChannel)) with | Result result->result | Exception excn-> Trace.PrintLine("CompilerServices", fun _ -> sprintf "[Bug]Exception in WaitForBackgroundCompile Inner exception was:\n%+A\n%s" excn excn.StackTrace) raise excn [] [] type InteractiveChecker(backgroundCompilerEvents) = let reactor = new Reactor.Reactor(backgroundCompilerEvents) static let mutable foregroundParseCount = 0 static let mutable foregroundTypeCheckCount = 0 /// Determine whether two sets of sources and parse options are the same. let AreSameForParsing((s1,o1),(s2,o2:ParseOptions)) = let same = o1.FileName=o2.FileName && ParseOptions.AreSameProject(o1,o2) same && s1 = s2 /// Determine whether two sets of sources and parse options should be subsumed under the same project. let AreSubsumableForParsing((s1,o1),(s2,o2)) = ParseOptions.AreSameProjectName(o1,o2) // Parse using reactor let ComputeBraceMatching(source,options) = Trace.PrintLine("CompilerServices", fun () -> sprintf "ComputeBraceMatching, FileName = %s\n " options.FileName) reactor.MatchBraces(source,options) let braceMatchMru = MruCache<_,_>(Flags.braceMatchCacheSize,ComputeBraceMatching,areSame=AreSameForParsing,areSameForSubsumption=AreSubsumableForParsing,isStillValid=(fun _ -> true)) // /// Cache which holds recently seen parses, up to one for each file, keyed by source text and parse/project options for the file // let parseLookup = AgedLookup<(string * ParseOptions),UntypedParseInfo>(8) /// Cache which holds recently seen type-checks, no more than one for each file. /// This cache may hold out-of-date entries, in two senses /// - there may be a more recent antecedent state available because the background build has made it available /// - the source for the file may have changed let typeCheckLookup = AgedLookup(recentForgroundTypeCheckLookupSize) /// Instantiate an interactive checker. static member Create (backgroundCompilerEvents) = new InteractiveChecker(backgroundCompilerEvents) /// Parse a source code file, returning an information about the untyped results /// and the results needed for further processing using 'TypeCheckSource' member ic.MatchBraces(source,options) = let pair = (source,options) braceMatchMru.Get(pair) /// Parse a source code file, returning an information about the untyped results /// and the results needed for further processing using 'TypeCheckSource' member ic.UntypedParse(source,options) = Trace.PrintLine("CompilerServices", fun () -> sprintf "UntypedParse, FileName = %s\n " options.FileName) foregroundParseCount <- foregroundParseCount + 1 reactor.UntypedParse(source,options) /// Try to get recent approximate type check results for a file. member ic.TryGetRecentTypeCheckResultsForFile(options) = match typeCheckLookup.TryGet(options) with | Some res -> Trace.PrintLine("CompilerServices", fun () -> sprintf "TryGetRecentTypeCheckResults, OK, FileName = %s\n " options.FileName) Some res | _ -> None /// This function is called when the configuration is known to have changed for reasons not encoded in the ParseOptions. /// For example, dependent references may have been deleted or created. member ic.InvalidateConfiguration(options : ParseOptions, changedFiles: (string * DependencyChangeCode) list) = reactor.InvalidateConfiguration(options,changedFiles) /// TypeCheck a source code file, returning a handle to the results of the /// parse including the reconstructed types in the file. member ic.TypeCheckSource(parseRes:UntypedParseInfo,source,options) = Trace.PrintLine("CompilerServices", fun () -> sprintf "TypeCheckSource, FileName = %s\n " options.FileName) match reactor.TypeCheckSource(parseRes.Results,source,options) with | None -> reactor.StartBuilding(options) None | Some typedResults -> foregroundTypeCheckCount <- foregroundTypeCheckCount + 1 typeCheckLookup.Put(options,(parseRes,typedResults)) Some typedResults /// Begin background parsing the given project. member ic.StartBackgroundCompile(options) = reactor.StartBuilding(options) /// Stop the background compile. member ic.StopBackgroundCompile() = reactor.StopBuilding() /// Block until the background compile finishes. member ic.WaitForBackgroundCompile() = reactor.WaitForBackgroundCompile() static member GlobalForegroundParseCountStatistic = foregroundParseCount static member GlobalForegroundTypeCheckCountStatistic = foregroundTypeCheckCount //---------------------------------------------------------------------------- //INDEX: FsiIntelisense //---------------------------------------------------------------------------- module FsiIntelisense = let rec getLineIndex (text:string,offset) = let crIndex = text.IndexOf("\n",StringComparison.Ordinal) if crIndex <> -1 then if offset <= crIndex then 0,offset,text else let text = text.Substring(crIndex + 1) let line,index,text = getLineIndex (text,offset - (crIndex+1)) line+1,index,text else 0,offset,text let getDeclarations (tcConfig,tcGlobals,tcImports,tcState) (text:string) names = let syncop f = f() : unit let mockFileName = "stdin.fs" (* Note: build.ml:ParseInput parses differently based on filename extension... *) let _, _, inp, suppressTypecheckErrors = Parser.parseSource text false false mockFileName [] tcConfig let _, scopeOpt = Parser.typeCheckSource inp text mockFileName "project" tcConfig tcGlobals tcImports tcState syncop suppressTypecheckErrors match scopeOpt with | Some scope when Array.length names > 0 -> let line,index,lineStr = getLineIndex (text,text.Length) let res = scope.GetDeclarations line lineStr index (List.frontAndBack (List.of_array names)) let project i = let name = res.Name(i) let desc = res.Description(i) |> fst let glyph = res.Glyph(i) let displ = name name,desc,displ,glyph let results = Array.init res.Count project results | _ -> [| |] module PrettyNaming = let IsIdentifierPartCharacter = Microsoft.FSharp.Compiler.PrettyNaming.IsIdentifierPartCharacter let IsLongIdentifierPartCharacter = Microsoft.FSharp.Compiler.PrettyNaming.IsLongIdentifierPartCharacter let GetLongNameFromString = Microsoft.FSharp.Compiler.PrettyNaming.SplitNamesForFsiGenerationPath