@@ -2,21 +2,26 @@
22module Transform.Canonicalize.Variable where
33
44import Control.Monad.Error
5+import qualified Data.Either as Either
6+import Data.Function (on)
57import qualified Data.List as List
68import qualified Data.Map as Map
7−import Data.Maybe (fromMaybe)
9+import Data.Maybe (fromMaybe, mapMaybe)
10+import qualified Data.Set as Set
11+import qualified Text.EditDistance as Dist
812
9−import AST.Helpers as Help
13+import qualified AST.Helpers as Help
1014import qualified AST.Module as Module
1115import qualified AST.Type as Type
1216import qualified AST.Variable as Var
1317import Transform.Canonicalize.Environment as Env
18+import Elm.Utils ((|>))
1419
1520
1621variable :: Environment -> String -> Canonicalizer String Var.Canonical
1722variable env var =
18− case modul of
19− Just name
23+ case splitName var of
24+ Right (name, varName)
2025 | Module.nameIsNative name ->
2126 Env.using (Var.Canonical (Var.Module name) varName)
2227
@@ -25,11 +30,6 @@ variable env var =
2530 Just [v] -> Env.using v
2631 Just vs -> preferLocals env "variable" vs var
2732 Nothing -> notFound "variable" (Map.keys (_values env)) var
28− where
29− (modul, varName) =
30− case Help.splitDots var of
31− [x] -> (Nothing, x)
32− xs -> (Just (init xs), last xs)
3333
3434
3535tvar
@@ -42,8 +42,11 @@ tvar env var =
4242 [v] -> found extract v
4343 vs -> preferLocals' env extract "type" vs var
4444 where
45− adts = map Left . fromMaybe [] $ Map.lookup var (_adts env)
46− aliases = map Right . fromMaybe [] $ Map.lookup var (_aliases env)
45+ adts =
46+ map Left (fromMaybe [] (Map.lookup var (_adts env)))
47+
48+ aliases =
49+ map Right (fromMaybe [] (Map.lookup var (_aliases env)))
4750
4851 extract value =
4952 case value of
@@ -59,25 +62,14 @@ pvar env var =
5962 Nothing -> notFound "pattern" (Map.keys (_patterns env)) var
6063
6164
65+-- FOUND
66+
6267found :: (a -> Var.Canonical) -> a -> Canonicalizer String a
6368found extract v =
6469 do _ <- Env.using (extract v)
6570 return v
6671
6772
68−notFound :: String -> [String] -> String -> Canonicalizer String a
69−notFound kind possibilities var =
70− throwError $ "Could not find " ++ kind ++ " '" ++ var ++ "'." ++ msg
71− where
72− matches =
73− filter (List.isInfixOf var) possibilities
74−
75− msg =
76− if null matches
77− then ""
78− else "\nClose matches include: " ++ List.intercalate ", " matches
79−
80−
8173preferLocals
8274 :: Environment
8375 -> String
@@ -116,3 +108,136 @@ preferLocals' env extract kind possibilities var =
116108 msg = "Ambiguous usage of " ++ kind ++ " '" ++ var ++ "'.\n" ++
117109 " Disambiguate between: " ++ List.intercalate ", " vars
118110
111+
112+-- NOT FOUND HELPERS
113+
114+splitName :: String -> Either String ([String], String)
115+splitName var =
116+ case Help.splitDots var of
117+ [x] -> Left x
118+ xs -> Right (init xs, last xs)
119+
120+
121+getName :: Either String ([String], String) -> String
122+getName name =
123+ case name of
124+ Left x -> x
125+ Right (_, x) -> x
126+
127+
128+nameToString :: ([String], String) -> String
129+nameToString (modul, name) =
130+ Module.nameToString (modul ++ [name])
131+
132+
133+isOp :: Either String ([String], String) -> Bool
134+isOp name =
135+ Help.isOp (getName name)
136+
137+
138+distance :: String -> String -> Int
139+distance x y =
140+ Dist.restrictedDamerauLevenshteinDistance Dist.defaultEditCosts x y
141+
142+
143+nearbyNames :: (a -> String) -> a -> [a] -> [a]
144+nearbyNames format name names =
145+ let editDistance =
146+ if length (format name) < 3 then 1 else 2
147+ in
148+ names
149+ |> map (\x -> (distance (format name) (format x), x))
150+ |> List.sortBy (compare `on` fst)
151+ |> filter ( (<= editDistance) . abs . fst )
152+ |> map snd
153+
154+
155+-- NOT FOUND
156+
157+notFound :: String -> [String] -> String -> Canonicalizer String a
158+notFound kind possibilities var =
159+ let possibleNames =
160+ map splitName possibilities
161+
162+ name =
163+ splitName var
164+
165+ closeNames =
166+ possibleNames
167+ |> filter (\n -> isOp name == isOp n)
168+ |> nearbyNames getName name
169+
170+ (exposed, qualified) =
171+ Either.partitionEithers closeNames
172+
173+ message =
174+ case name of
175+ Left _ ->
176+ closeExposedMessage exposed ++ closeQualifiedMessage qualified
177+
178+ Right (modul, _) ->
179+ qualifiedMessage modul (Either.rights possibleNames) qualified
180+
181+ in
182+ throwError $ "Could not find " ++ kind ++ " '" ++ var ++ "'." ++ message
183+
184+
185+
186+closeExposedMessage :: [String] -> String
187+closeExposedMessage exposed =
188+ if null exposed
189+ then ""
190+ else
191+ "\n\nClose matches include:" ++ concatMap ("\n " ++) exposed
192+
193+
194+closeQualifiedMessage :: [([String], String)] -> String
195+closeQualifiedMessage qualified =
196+ if null qualified
197+ then ""
198+ else
199+ "\n\nMaybe you forgot to say which module it came from?\n"
200+ ++ "Close qualified names include:"
201+ ++ concatMap (("\n " ++) . nameToString) qualified
202+ ++ usingImportsMessage
203+
204+
205+qualifiedMessage :: [String] -> [([String], String)] -> [([String], String)] -> String
206+qualifiedMessage modul allQualified qualified =
207+ let availableModules =
208+ Set.fromList (map fst allQualified)
209+ in
210+ case Set.member modul availableModules of
211+ True ->
212+ let inSameModule =
213+ filter ((==) modul . fst) qualified
214+ in
215+ if null inSameModule
216+ then ""
217+ else
218+ "\n\nClose matches include:"
219+ ++ concatMap (("\n " ++) . nameToString) inSameModule
220+
221+ False ->
222+ let closeModules =
223+ Set.toList availableModules
224+ |> map Module.nameToString
225+ |> nearbyNames id (Module.nameToString modul)
226+ in
227+ case closeModules of
228+ [] ->
229+ "\n\nLooks like the prefix '" ++ Module.nameToString modul
230+ ++ "' is not in scope. Is it spelled correctly?"
231+ ++ "\nIs it imported correctly?"
232+ ++ usingImportsMessage
233+
234+ _ ->
235+ "\n\nClose matches to '" ++ Module.nameToString modul ++ "' include:"
236+ ++ concatMap ("\n " ++) closeModules
237+
238+
239+usingImportsMessage :: String
240+usingImportsMessage =
241+ "\n\nYou can read about how imports work at the following address:"
242+ ++ "\n<http://elm-lang.org/learn/Syntax.elm#modules>"
243+