From b16928cd8571c052e36a5d3c67d16cc58caa44ee Mon Sep 17 00:00:00 2001
From: Phil Freeman <paf31@cantab.net>
Date: Sat, 14 Jan 2017 14:44:39 -0800
Subject: [PATCH 1/2] First stab at type search

---
 server/Main.hs      | 51 +++++++++++++++++++++++++++++++++++++++++++--
 stack.yaml          |  4 ++--
 trypurescript.cabal |  5 +++--
 3 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/server/Main.hs b/server/Main.hs
index b33d831e..713b4320 100644
--- a/server/Main.hs
+++ b/server/Main.hs
@@ -1,22 +1,26 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE TupleSections #-}
 
 module Main (main) where
 
-import           Control.Monad (unless)
+import           Control.Monad (unless, (>=>))
+import           Control.Monad.Error.Class (throwError)
 import           Control.Monad.IO.Class (liftIO)
 import           Control.Monad.Logger (runLogger')
+import           Control.Monad.State (State)
+import qualified Control.Monad.State as State
 import           Control.Monad.Trans (lift)
-import           Control.Monad.Error.Class (throwError)
 import           Control.Monad.Trans.Except (ExceptT(..), runExceptT)
 import           Control.Monad.Trans.Reader (runReaderT)
 import qualified Data.Aeson as A
 import           Data.Aeson ((.=))
 import qualified Data.ByteString.Lazy as BL
 import           Data.List (foldl')
+import qualified Data.Map as M
 import           Data.String (fromString)
 import           Data.Text (Text)
 import qualified Data.Text as T
@@ -30,12 +34,14 @@ import qualified Language.PureScript.CodeGen.JS as J
 import qualified Language.PureScript.CoreFn as CF
 import qualified Language.PureScript.Errors.JSON as P
 import qualified Language.PureScript.Interactive as I
+import qualified Language.PureScript.TypeChecker.TypeSearch as TS
 import           System.Environment (getArgs)
 import           System.Exit (exitFailure)
 import           System.FilePath ((</>))
 import           System.FilePath.Glob (glob)
 import qualified System.IO as IO
 import           System.IO.UTF8 (readUTF8File)
+import qualified Text.Parsec.Combinator as Parsec
 import           Web.Scotty
 import qualified Web.Scotty as Scotty
 
@@ -90,6 +96,47 @@ server bundled externs initEnv port = do
           Scotty.json $ A.object [ "error" .= err ]
         Right comp ->
           Scotty.json $ A.object [ "js" .= comp ]
+    get "/search" $ do
+      query <- param "q"
+      case tryParseType query of
+        Nothing -> Scotty.json $ A.object [ "error" .= ("Cannot parse type" :: Text) ]
+        Just ty -> do
+          let ty' = replaceTypeVariablesAndDesugar ty
+          let results = TS.typeSearch (Just []) initEnv (P.emptyCheckState initEnv) ty'
+          Scotty.json $ A.object [ "results" .= A.object [ P.showQualified P.runIdent k .= P.prettyPrintType v
+                                                         | (k, v) <- take 20 (M.toList results)
+                                                         ]
+                                 ]
+
+-- | (Consistently) replace unqualified type constructors and type variables with unknowns.
+--
+-- Also remove the @ParensInType@ Constructor (we need to deal with type operators later at some point).
+replaceTypeVariablesAndDesugar :: P.Type -> P.Type
+replaceTypeVariablesAndDesugar ty = State.evalState (P.everywhereOnTypesM go ty) (0, M.empty) where
+  go = \case
+    P.ParensInType ty -> pure ty
+    P.TypeConstructor (P.Qualified Nothing tyCon) -> do
+      (next, m) <- State.get
+      case M.lookup (Left tyCon) m of
+        Nothing -> do
+          let ty = P.TUnknown next
+          State.put (next + 1, M.insert (Left tyCon) ty m)
+          pure ty
+        Just ty -> pure ty
+    P.TypeVar s -> do
+      (next, m) <- State.get
+      case M.lookup (Right s) m of
+        Nothing -> do
+          let ty = P.TUnknown next
+          State.put (next + 1, M.insert (Right s) ty m)
+          pure ty
+        Just ty -> pure ty
+    other -> pure other
+
+tryParseType :: Text -> Maybe P.Type
+tryParseType = hush (P.lex "") >=> hush (P.runTokenParser "" (P.parsePolyType <* Parsec.eof))
+  where
+    hush f = either (const Nothing) Just . f
 
 bundle :: IO (Either Bundle.ErrorMessage String)
 bundle = runExceptT $ do
diff --git a/stack.yaml b/stack.yaml
index 5727cb7e..daa0b481 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -3,7 +3,7 @@ flags: {}
 packages:
 - '.'
 extra-deps:
-- purescript-0.10.4
-- bower-json-0.8.0
+- purescript-0.10.5
+- bower-json-1.0.0.1
 - language-javascript-0.6.0.9
 - parsec-3.1.11
diff --git a/trypurescript.cabal b/trypurescript.cabal
index bbe911c3..490ba0d8 100644
--- a/trypurescript.cabal
+++ b/trypurescript.cabal
@@ -1,5 +1,5 @@
 name: trypurescript
-version: 0.10.4
+version: 0.10.5
 cabal-version: >=1.8
 build-type: Simple
 license: BSD3
@@ -20,11 +20,12 @@ executable trypurescript
                    filepath -any,
                    Glob -any,
                    scotty -any,
-                   purescript ==0.10.4,
+                   purescript ==0.10.5,
                    containers -any,
                    http-types >= 0.8.5,
                    transformers ==0.4.*,
                    mtl ==2.2.1,
+                   parsec,
                    text -any,
                    time -any
     hs-source-dirs: server

From a99d0f3307f5970e8793126820251354704ee9f4 Mon Sep 17 00:00:00 2001
From: Phil Freeman <paf31@cantab.net>
Date: Sat, 14 Jan 2017 15:29:37 -0800
Subject: [PATCH 2/2] Replace type constructors first, prefer strict matches

---
 server/Main.hs | 50 +++++++++++++++++++++++++++++++-------------------
 1 file changed, 31 insertions(+), 19 deletions(-)

diff --git a/server/Main.hs b/server/Main.hs
index 713b4320..7ecc8b46 100644
--- a/server/Main.hs
+++ b/server/Main.hs
@@ -19,7 +19,8 @@ import           Control.Monad.Trans.Reader (runReaderT)
 import qualified Data.Aeson as A
 import           Data.Aeson ((.=))
 import qualified Data.ByteString.Lazy as BL
-import           Data.List (foldl')
+import           Data.Function (on)
+import           Data.List (foldl', nubBy)
 import qualified Data.Map as M
 import           Data.String (fromString)
 import           Data.Text (Text)
@@ -98,37 +99,48 @@ server bundled externs initEnv port = do
           Scotty.json $ A.object [ "js" .= comp ]
     get "/search" $ do
       query <- param "q"
+      Scotty.setHeader "Access-Control-Allow-Origin" "*"
+      Scotty.setHeader "Content-Type" "application/json"
       case tryParseType query of
         Nothing -> Scotty.json $ A.object [ "error" .= ("Cannot parse type" :: Text) ]
         Just ty -> do
-          let ty' = replaceTypeVariablesAndDesugar ty
-          let results = TS.typeSearch (Just []) initEnv (P.emptyCheckState initEnv) ty'
-          Scotty.json $ A.object [ "results" .= A.object [ P.showQualified P.runIdent k .= P.prettyPrintType v
-                                                         | (k, v) <- take 20 (M.toList results)
-                                                         ]
+          let elabs = lookupAllConstructors initEnv ty
+              search = M.toList . TS.typeSearch (Just []) initEnv (P.emptyCheckState initEnv)
+              results = nubBy ((==) `on` fst) $ do
+                elab <- elabs
+                let strictMatches = search (replaceTypeVariablesAndDesugar (\nm s -> P.Skolem nm s (P.SkolemScope 0) Nothing) elab)
+                    flexMatches = search (replaceTypeVariablesAndDesugar (const P.TUnknown) elab)
+                take 50 (strictMatches ++ flexMatches)
+          Scotty.json $ A.object [ "results" .= [ P.showQualified P.runIdent k
+                                                | (k, _) <- take 50 results
+                                                ]
                                  ]
 
+lookupAllConstructors :: P.Environment -> P.Type -> [P.Type]
+lookupAllConstructors env = P.everywhereOnTypesM $ \case
+    P.TypeConstructor (P.Qualified Nothing tyCon) -> P.TypeConstructor <$> lookupConstructor env tyCon
+    other -> pure other
+  where
+    lookupConstructor :: P.Environment -> P.ProperName 'P.TypeName -> [P.Qualified (P.ProperName 'P.TypeName)]
+    lookupConstructor env nm =
+      [ q
+      | (q@(P.Qualified (Just mn) thisNm), _) <- M.toList (P.types env)
+      , thisNm == nm
+      ]
+
 -- | (Consistently) replace unqualified type constructors and type variables with unknowns.
 --
 -- Also remove the @ParensInType@ Constructor (we need to deal with type operators later at some point).
-replaceTypeVariablesAndDesugar :: P.Type -> P.Type
-replaceTypeVariablesAndDesugar ty = State.evalState (P.everywhereOnTypesM go ty) (0, M.empty) where
+replaceTypeVariablesAndDesugar :: (Text -> Int -> P.Type) -> P.Type -> P.Type
+replaceTypeVariablesAndDesugar f ty = State.evalState (P.everywhereOnTypesM go ty) (0, M.empty) where
   go = \case
     P.ParensInType ty -> pure ty
-    P.TypeConstructor (P.Qualified Nothing tyCon) -> do
-      (next, m) <- State.get
-      case M.lookup (Left tyCon) m of
-        Nothing -> do
-          let ty = P.TUnknown next
-          State.put (next + 1, M.insert (Left tyCon) ty m)
-          pure ty
-        Just ty -> pure ty
     P.TypeVar s -> do
       (next, m) <- State.get
-      case M.lookup (Right s) m of
+      case M.lookup s m of
         Nothing -> do
-          let ty = P.TUnknown next
-          State.put (next + 1, M.insert (Right s) ty m)
+          let ty = f s next
+          State.put (next + 1, M.insert s ty m)
           pure ty
         Just ty -> pure ty
     other -> pure other