OILS / core / error.py View on Github | oils.pub

389 lines, 120 significant
1"""error.py - error types"""
2from __future__ import print_function
3
4from _devbuild.gen.syntax_asdl import loc_e, loc_t, loc
5from _devbuild.gen.value_asdl import (value, value_t, value_str)
6from core import num
7from mycpp.mylib import NewDict
8
9from typing import Dict, Union, NoReturn, TYPE_CHECKING
10
11# For storing errors in List[T]
12if TYPE_CHECKING:
13 IOError_OSError = Union[IOError, OSError]
14
15EXPR_STATUS = 3
16
17# status code 4 is special, for encode/decode errors.
18CODEC_STATUS = 4
19
20BUILTIN_DEFAULT_STATUS = 10 # for the error builtin
21
22
23def _ValType(val):
24 # type: (value_t) -> str
25 """Duplicate ui.ValType for now"""
26 return value_str(val.tag(), dot=False)
27
28
29class _ErrorWithLocation(Exception):
30 """A parse error that can be formatted.
31
32 Formatting is in ui.PrintError.
33 """
34
35 def __init__(self, msg, location):
36 # type: (str, loc_t) -> None
37
38 self.msg = msg
39
40 # Ensure that the location field is always populated
41 if location is None:
42 self.location = loc.Missing # type: loc_t
43 else:
44 self.location = location
45
46 def HasLocation(self):
47 # type: () -> bool
48 return self.location.tag() != loc_e.Missing
49
50 def UserErrorString(self):
51 # type: () -> str
52 return self.msg
53
54 def __repr__(self):
55 # type: () -> str
56 return '<%s %r>' % (self.msg, self.location)
57
58
59class Usage(_ErrorWithLocation):
60 """For flag parsing errors in builtins and main()
61
62 Called by e_usage(). TODO: Should settle on a single interface that
63 can be translated. Sometimes we use 'raise error.Usage()'
64 """
65
66 def __init__(self, msg, location):
67 # type: (str, loc_t) -> None
68 _ErrorWithLocation.__init__(self, msg, location)
69
70
71class Parse(_ErrorWithLocation):
72 """Used in the parsers."""
73
74 def __init__(self, msg, location):
75 # type: (str, loc_t) -> None
76 _ErrorWithLocation.__init__(self, msg, location)
77
78
79class WordFailure(_ErrorWithLocation):
80 """Raised for failure of word evaluation
81
82 Meant to be caught.
83 """
84
85 def __init__(self, msg, location):
86 # type: (str, loc_t) -> None
87 _ErrorWithLocation.__init__(self, msg, location)
88
89
90class FailGlob(WordFailure):
91 """Raised when a glob matches nothing when failglob is set.
92
93 Meant to be caught.
94 """
95
96 def __init__(self, msg, location):
97 # type: (str, loc_t) -> None
98 WordFailure.__init__(self, 'failglob: ' + msg, location)
99
100
101class VarSubFailure(WordFailure):
102 """Raised when a variable substitution fails. For example, this
103 is thrown with ${!ref} when the variable "ref" contains invalid
104 variable name such as ref="a b c".
105
106 Meant to be caught.
107 """
108
109 def __init__(self, msg, location):
110 # type: (str, loc_t) -> None
111 WordFailure.__init__(self, msg, location)
112
113
114class RedirectEval(_ErrorWithLocation):
115 """Used in the CommandEvaluator.
116
117 A bad redirect causes the SimpleCommand to return with status 1. To
118 make it fatal, use set -o errexit.
119 """
120
121 def __init__(self, msg, location):
122 # type: (str, loc_t) -> None
123 _ErrorWithLocation.__init__(self, msg, location)
124
125
126class FatalRuntime(_ErrorWithLocation):
127 """An exception that propagates to the top level.
128
129 Used in the evaluators, and also also used in test builtin for
130 invalid argument.
131 """
132
133 def __init__(self, exit_status, msg, location):
134 # type: (int, str, loc_t) -> None
135 _ErrorWithLocation.__init__(self, msg, location)
136 self.exit_status = exit_status
137
138 def ExitStatus(self):
139 # type: () -> int
140 return self.exit_status
141
142
143class Strict(FatalRuntime):
144 """Depending on shell options, these errors may be caught and ignored.
145
146 For example, if options like these are ON:
147
148 set -o strict_arith
149 set -o strict_word_eval
150
151 then we re-raise the error so it's caught by the top level. Otherwise
152 we catch it and return a dummy value like '' or -1 (i.e. what bash commonly
153 does.)
154
155 TODO: Have levels, like:
156
157 OILS_STRICT_PRINT=2 # print warnings at level 2 and above
158 OILS_STRICT_DIE=1 # abort the program at level 1 and above
159 """
160
161 def __init__(self, msg, location):
162 # type: (str, loc_t) -> None
163 FatalRuntime.__init__(self, 1, msg, location)
164
165
166class ErrExit(FatalRuntime):
167 """For set -e.
168
169 Travels between WordEvaluator and CommandEvaluator.
170 """
171
172 def __init__(self, exit_status, msg, location, show_code=False):
173 # type: (int, str, loc_t, bool) -> None
174 FatalRuntime.__init__(self, exit_status, msg, location)
175 self.show_code = show_code
176
177
178class NoUnset(FatalRuntime):
179 """For set -u.
180
181 Is raised in WordEvaluator then caught in CommandEvaluator.
182 """
183
184 def __init__(self, msg, location):
185 # type: (str, loc_t) -> None
186
187 # exit status is always 1
188 FatalRuntime.__init__(self, 1, msg, location)
189
190
191class Expr(FatalRuntime):
192 """e.g. KeyError, IndexError, ZeroDivisionError."""
193
194 def __init__(self, msg, location):
195 # type: (str, loc_t) -> None
196
197 # Unique status of 3 for expression errors -- for both the caught and
198 # uncaught case.
199 #
200 # Caught: try sets _status register to 3
201 # Uncaught: shell exits with status 3
202 FatalRuntime.__init__(self, EXPR_STATUS, msg, location)
203
204
205class Structured(FatalRuntime):
206 """An error that can be exposed via the _error Dict.
207
208 Including:
209 - Errors raised by the 'error' builtin
210 - J8 encode and decode errors.
211 """
212
213 def __init__(self, status, msg, location, properties=None):
214 # type: (int, str, loc_t, Dict[str, value_t]) -> None
215 FatalRuntime.__init__(self, status, msg, location)
216 self.properties = properties
217
218 def ToDict(self):
219 # type: () -> value.Dict
220
221 d = NewDict() # type: Dict[str, value_t]
222
223 # The _error Dict order is odd -- the optional properties come BEFORE
224 # required fields. We always want the required fields to be present so
225 # it makes sense.
226 if self.properties is not None:
227 d.update(self.properties)
228
229 # _error.code is better than _error.status
230 d['code'] = num.ToBig(self.ExitStatus())
231 d['message'] = value.Str(self.msg)
232
233 return value.Dict(d)
234
235
236class AssertionErr(Expr):
237 """An assertion."""
238
239 def __init__(self, msg, location):
240 # type: (str, loc_t) -> None
241 Expr.__init__(self, msg, location)
242
243
244class TypeErrVerbose(Expr):
245 """e.g. ~ on a bool or float, 'not' on an int."""
246
247 def __init__(self, msg, location):
248 # type: (str, loc_t) -> None
249 Expr.__init__(self, msg, location)
250
251
252class TypeErr(TypeErrVerbose):
253
254 def __init__(self, actual_val, msg, location):
255 # type: (value_t, str, loc_t) -> None
256 TypeErrVerbose.__init__(self,
257 "%s, got %s" % (msg, _ValType(actual_val)),
258 location)
259
260
261class Runtime(Exception):
262 """An error that's meant to be caught, i.e. it's non-fatal.
263
264 Thrown by core/state.py and caught by builtins
265 """
266
267 def __init__(self, msg):
268 # type: (str) -> None
269 self.msg = msg
270
271 def UserErrorString(self):
272 # type: () -> str
273 return self.msg
274
275
276class Decode(Exception):
277 """
278 List of J8 errors:
279 - message isn't UTF-8 - Id.Lit_Chars - need loc
280 - Invalid token Id.Unkown_Tok - need loc
281 - Unclosed double quote string -- need loc
282 - Parse error, e.g. [}{]
283
284 - Invalid escapes:
285 - b"" and u"" don't accept \\u1234
286 - u"" doesn't accept \\yff
287 - "" doesn't accept \\yff or \\u{123456}
288 """
289
290 def __init__(self, msg, s, start_pos, end_pos, line_num):
291 # type: (str, str, int, int, int) -> None
292 self.msg = msg
293 self.s = s # string being decoded
294 self.start_pos = start_pos
295 self.end_pos = end_pos
296 self.line_num = line_num
297
298 def Message(self):
299 # type: () -> str
300
301 # Show 10 chars of context for now
302 start = max(0, self.start_pos - 4)
303 end = min(len(self.s), self.end_pos + 4)
304
305 part = self.s[start:end]
306 return self.msg + ' (line %d, offset %d-%d: %r)' % (
307 self.line_num, self.start_pos, self.end_pos, part)
308
309 def __str__(self):
310 # type: () -> str
311 return self.Message()
312
313
314class Encode(Exception):
315 """
316 List of J8 encode errors:
317 - object cycle
318 - unprintable object like Eggex
319 When encoding JSON:
320 - binary data that can't be represented in JSON
321 - if using Unicode replacement char, then it won't fail
322 """
323
324 def __init__(self, msg):
325 # type: (str) -> None
326 self.msg = msg
327
328 def Message(self):
329 # type: () -> str
330 return self.msg
331
332
333def e_usage(msg, location):
334 # type: (str, loc_t) -> NoReturn
335 """Convenience wrapper for arg parsing / validation errors.
336
337 Usually causes a builtin to fail with status 2, but the script can continue
338 if 'set +o errexit'. Main programs like bin/ysh also use this.
339
340 Caught by
341
342 - RunAssignBuiltin and RunBuiltin, with optional LOCATION INFO
343 - various main() programs, without location info
344
345 Probably should separate these two cases?
346
347 - builtins pass Token() or loc::Missing()
348 - tool interfaces don't pass any location info
349 """
350 raise Usage(msg, location)
351
352
353def e_strict(msg, location):
354 # type: (str, loc_t) -> NoReturn
355 """Convenience wrapper for strictness errors.
356
357 Like e_die(), except the script MAY continue executing after these errors.
358
359 TODO: This could have a level too?
360 """
361 raise Strict(msg, location)
362
363
364def p_die(msg, location):
365 # type: (str, loc_t) -> NoReturn
366 """Convenience wrapper for parse errors.
367
368 Exits with status 2. See core/main_loop.py.
369 """
370 raise Parse(msg, location)
371
372
373def e_die(msg, location=None):
374 # type: (str, loc_t) -> NoReturn
375 """Convenience wrapper for fatal runtime errors.
376
377 Usually exits with status 1. See osh/cmd_eval.py.
378 """
379 raise FatalRuntime(1, msg, location)
380
381
382def e_die_status(status, msg, location=None):
383 # type: (int, str, loc_t) -> NoReturn
384 """Wrapper for C++ semantics.
385
386 Note that it doesn't take positional args, so you should use %
387 formatting.
388 """
389 raise FatalRuntime(status, msg, location)