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

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