Skip to main content

Danger unstable structure - No more!

·8 mins

Over the last year a lot of work has been done on the F# addin for Xamarin Studio. Lots of great new features have been added and a lot of bugs have been squashed. I want to talk a bit about whats been happening and the evolution of the F# addin.

The F# addin for MonoDevelop / Xamarin Studio hasn’t always been as stable and pretty as it is now. When I first started working on it I went through a rocky phase of just trying to get it to compile and install. There were a whole host of changes going on in MonoDevelop which caused a lot of head scratching and late nights. Of all the issues encountered tooltips were the absolute bane of my life, they were often horribly mangled like this:

Or completion lists were insanely long and downright pugly:

Modern IDE’s have features like tooltips and auto completions which are so intrinsic that without them you feel a little lost without them, instead you have to rely on having eidetic memory of the various APIs and functions. Over the course of fixing bugs and adding new features inevitably you end up breaking lots of things which can make for a frustrating experience with half mangled IDE. Thankfully all these issues have now been addressed and lots of new features have also been added. Here are all the new features and improvements that have gone in over the last year or so.

UI Improvements #

Rename refactoring #

One of the most recent and exciting new features is rename refactoring. Renaming can be achieved by either using a shortcut key Meta R or by right clicking and selecting rename from the context menu. Here’s a screen cast of rename refactoring in action:

Highlight usages #

The precursor to rename refactoring was highlight usages, by clicking in the code editor the current identifier is highlighted and all occurrences of of that identifier are also highlighted. This makes it very easy to see where a specific identifier is used and is one of my favorite features:

Tooltip overload refinements #

Overload tooltips have now been refined so they look like the image below rather than the huge overload list shown at the top of this post.

Tooltip arrow indicators #

A little arrow indicators is now shown and positioned over the center of the tooltip. Yeah is only cosmetic but it was annoying having it missing.

Symbol tooltips #

We now also have tooltips for symbols, which makes custom symbol use more palatable. Now you can simply hover over the symbol see see its signature and xmldoc summary.

Drag and drop file ordering #

F# source files can now be dragged in the Solution pad for correct ordering. This is limited to ordering files within a single folder at the moment. Source files can still be moved from one folder to another but they must be ordered as a separate operation.

Pathed document navigation #

Pathed document navigation is implemented in the source window to aid navigation between modules, types and functions within a source file:

Interactive Improvements #

Send all project references to FSI #

From the context menu or keyboard shortcut Ctrl Meta P you can choose to send all of the current projects reference to F# interactive, this saves having to enter a bunch of #r statements.

Send line moves down automatically #

For doing demos and testing scripts you can use a keyboard shortcut to sent the current line to F# interactive, the caret automatically drops down to the next line which makes stepping through the script a pinch.

Clear/Reset FSI keyboard shortcuts #

By pressing Ctrl Meta R you can reset the current instance of F# interactive, similarly Ctrl Meta C will clear the current F# interactive window.

FSI theming #

You can now also choose to have F# interactive match you current theme colours or pick your own:

Engineering improvements #

Originally we had a reflective binding to the F# compiler services which meant it was version agnostic but quite difficult to debug and add new features to.

Actually just for posterity, and you might find this useful/interesting heres the code, it uses the dynamic lookup operator (?) :

let (?) (o:obj) name : 'R =
  // The return type is a function, which means that we want to invoke a method
  if FSharpType.IsFunction(typeof<'R>) then
    let argType, resType = FSharpType.GetFunctionElements(typeof<'R>)
    FSharpValue.MakeFunction(typeof<'R>, fun args ->
      // We treat elements of a tuple passed as argument as a list of arguments
      // When the 'o' object is 'System.Type', we call static methods
      let methods, instance, args, owner = 
        let args = 
          if Object.Equals(argType, typeof<unit>) then [| |]
          elif not(FSharpType.IsTuple(argType)) then [| args |]
          else FSharpValue.GetTupleFields(args)
        if (typeof<System.Type>).IsAssignableFrom(o.GetType()) then 
          let methods = (unbox<Type> o).GetMethods(staticFlags) |> asMethodBase
          let ctors = (unbox<Type> o).GetConstructors(ctorFlags) |> asMethodBase
          let owner = (unbox<Type> o).Name + " (static)"
          Array.concat [ methods; ctors ], null, args, owner
          let owner = o.GetType().Name + " (instance)"
          o.GetType().GetMethods(instanceFlags) |> asMethodBase, o, args, owner
      // A simple overload resolution based on the name and number of parameters only
      let methods = 
        [ for m in methods do
            if m.Name = name && m.GetParameters().Length = args.Length then yield m 
            if m.Name = name && m.IsGenericMethod &&
               m.GetGenericArguments().Length + m.GetParameters().Length = args.Length then yield m ]
      match methods with 
      | [] -> failwithf "No method '%s' with %d arguments found in %s" name args.Length owner
      | _::_::_ -> failwithf "Multiple methods '%s' with %d arguments found %s" name args.Length owner
      | [:? ConstructorInfo as c] -> c.Invoke(args)
      | [ m ] when m.IsGenericMethod ->
          let tyCount = m.GetGenericArguments().Length
          let tyArgs = args |> Seq.take tyCount 
          let actualArgs = args |> Seq.skip tyCount
          let gm = (m :?> MethodInfo).MakeGenericMethod [| for a in tyArgs -> unbox a |]
          gm.Invoke(instance, Array.ofSeq actualArgs)
      | [ m ] -> m.Invoke(instance, args) ) |> unbox<'R>
    // When the 'o' object is 'System.Type', we access static properties
    let typ, flags, instance = 
      if (typeof<System.Type>).IsAssignableFrom(o.GetType()) then unbox o, staticFlags, null
      else o.GetType(), instanceFlags, o
    // Find a property that we can call and get the value
    let prop = typ.GetProperty(name, flags)
    if Object.Equals(prop, null) then 
      // Find a field that we can read
      let fld = typ.GetField(name, flags)
      if Object.Equals(fld, null) then
        // Try nested type...
        let nested = typ.Assembly.GetType(typ.FullName + "+" + name)
        if Object.Equals(nested, null) then 
          failwithf "Property, field or nested type '%s' not found in '%s' using flags '%A'." name typ.Name flags
        elif not ((typeof<'R>).IsAssignableFrom(typeof<System.Type>)) then
          failwithf "Cannot return nested type '%s' as value of type '%s'." nested.Name (typeof<'R>.Name)
        else nested |> box |> unbox<'R>
        // Get field value
        fld.GetValue(instance) |> unbox<'R>
      // Call property
      let meth = prop.GetGetMethod(true)
      if prop = null then failwithf "Property '%s' found, but doesn't have 'get' method." name
      try meth.Invoke(instance, [| |]) |> unbox<'R>
      with err -> failwithf "Failed to get value of '%s' property (of type '%s'), error: %s" name typ.Name (err.ToString())

To use it a type wrapper had to be constructed a bit like this:

let interactiveCheckerType = asmCompiler.GetType("Microsoft.FSharp.Compiler.SourceCodeServices.InteractiveChecker")

type InteractiveChecker(wrapped:obj) =
member x.TryGetRecentTypeCheckResultsForFile(filename:string, options:CheckOptions) =
   let res = wrapped?TryGetRecentTypeCheckResultsForFile(filename, options.Wrapped) : obj
   if res = null then None else
      let tuple = res?Value
      Some(UntypedParseInfo(tuple?Item1), TypeCheckResults(tuple?Item2), int tuple?Item3)

It was quite fiddly to get right as FSharp.Core had to be used reflectively as you would potentially be using a different version of FSharp.Core depending on what version of F# you were binding to. I think this kind of reflective calling could be simplified by creating an F# type provider, I can think of a variety of uses for something like this.

Ultimately a branch called hardbinding was created which led to the creation of the F# Compiler Editor branch of the F# compiler. Slowly over the last few months that evolved into the FSharp Compiler Service aka FCS.

I also experimented quite a bit with a custom tokeniser that allowed the keywords and other syntax elements to be highlighted as tooltips:

At the time the F# binding was going through quite a turbulent change to the source code, and the combination of trying to develop FSharp.Compiler.Services and also refactoring the existing code meant that I decided to put this on hold for the time being. I hope to resurrected this work one day, I can see that keyword highlighting would be very useful to beginners, especially if a detailed description was available similar to what is available on MSDN. It also addresses some bugs that are still outstanding so it would be nice to get finished.

Over the last couple of months a lot of the common code around parsing of long identifiers has been refactored so that is can be shared with other tools. In the F# Binding we also have an emacs plugin which uses this shared code. We have also created a shared language agent which makes using these language services even easier still!

Swat all the bugs! #

Anyone remember Picnic paranoia?

A shed load of bugs have also been swatted. Using the F# addin is no longer like ignoring the Unstable Structure sign and hoping things don’t crumble under your feet. Admittedly there is still work to be done but some of the recent refactoring make it even easier to contribute to either new features or fixing bugs. We’ve also added the F# binding to and added the up-for-grabs issues as a starting point for anyone thinking about to helping out. There’s been a lot of blood sweat and tears and its consumed quite a lot of my spare time but I hope you all enjoy using the new F# addin features.

Lastly a big thank you to everyone who has helped contribute over the last year, keep the contributions coming in!

That’s all for now, see you next time!