on master help sign in
Improve messages when variable is not found
ef18b2fEvan Czaplicki
elm-compiler.cabal+1
@@ -115,6 +115,7 @@ Library
115 cmdargs >= 0.7 && < 0.11,
115 cmdargs >= 0.7 && < 0.11,
116 containers >= 0.3 && < 0.6,
116 containers >= 0.3 && < 0.6,
117 directory >= 1.0 && < 2.0,
117 directory >= 1.0 && < 2.0,
118+ edit-distance >= 0.2 && < 0.3,
118 filepath >= 1 && < 2.0,
119 filepath >= 1 && < 2.0,
119 indents >= 0.3 && < 0.4,
120 indents >= 0.3 && < 0.4,
120 language-ecmascript >= 0.15 && < 0.18,
121 language-ecmascript >= 0.15 && < 0.18,
src/Transform/Canonicalize/Variable.hs+149−24
@@ -2,21 +2,26 @@
2module Transform.Canonicalize.Variable where
2module Transform.Canonicalize.Variable where
3
3
4import Control.Monad.Error
4import Control.Monad.Error
5+import qualified Data.Either as Either
6+import Data.Function (on)
5import qualified Data.List as List
7import qualified Data.List as List
6import qualified Data.Map as Map
8import 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
8
12
9−import AST.Helpers as Help
13+import qualified AST.Helpers as Help
10import qualified AST.Module as Module
14import qualified AST.Module as Module
11import qualified AST.Type as Type
15import qualified AST.Type as Type
12import qualified AST.Variable as Var
16import qualified AST.Variable as Var
13import Transform.Canonicalize.Environment as Env
17import Transform.Canonicalize.Environment as Env
18+import Elm.Utils ((|>))
14
19
15
20
16variable :: Environment -> String -> Canonicalizer String Var.Canonical
21variable :: Environment -> String -> Canonicalizer String Var.Canonical
17variable env var =
22variable env var =
18− case modul of
23+ case splitName var of
19− Just name
24+ Right (name, varName)
20 | Module.nameIsNative name ->
25 | Module.nameIsNative name ->
21 Env.using (Var.Canonical (Var.Module name) varName)
26 Env.using (Var.Canonical (Var.Module name) varName)
22
27
@@ -25,11 +30,6 @@ variable env var =
25 Just [v] -> Env.using v
30 Just [v] -> Env.using v
26 Just vs -> preferLocals env "variable" vs var
31 Just vs -> preferLocals env "variable" vs var
27 Nothing -> notFound "variable" (Map.keys (_values env)) var
32 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)
33
33
34
34
35tvar
35tvar
@@ -42,8 +42,11 @@ tvar env var =
42 [v] -> found extract v
42 [v] -> found extract v
43 vs -> preferLocals' env extract "type" vs var
43 vs -> preferLocals' env extract "type" vs var
44 where
44 where
45+ adts =
45− adts = map Left . fromMaybe [] $ Map.lookup var (_adts env)
46+ map Left (fromMaybe [] (Map.lookup var (_adts env)))
47+
48+ aliases =
46− aliases = map Right . fromMaybe [] $ Map.lookup var (_aliases env)
49+ map Right (fromMaybe [] (Map.lookup var (_aliases env)))
47
50
48 extract value =
51 extract value =
49 case value of
52 case value of
@@ -59,25 +62,14 @@ pvar env var =
59 Nothing -> notFound "pattern" (Map.keys (_patterns env)) var
62 Nothing -> notFound "pattern" (Map.keys (_patterns env)) var
60
63
61
64
65+-- FOUND
66+
62found :: (a -> Var.Canonical) -> a -> Canonicalizer String a
67found :: (a -> Var.Canonical) -> a -> Canonicalizer String a
63found extract v =
68found extract v =
64 do _ <- Env.using (extract v)
69 do _ <- Env.using (extract v)
65 return v
70 return v
66
71
67
72
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−
81preferLocals
73preferLocals
82 :: Environment
74 :: Environment
83 -> String
75 -> String
@@ -116,3 +108,136 @@ preferLocals' env extract kind possibilities var =
116 msg = "Ambiguous usage of " ++ kind ++ " '" ++ var ++ "'.\n" ++
108 msg = "Ambiguous usage of " ++ kind ++ " '" ++ var ++ "'.\n" ++
117 " Disambiguate between: " ++ List.intercalate ", " vars
109 " Disambiguate between: " ++ List.intercalate ", " vars
118
110
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+