-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontextlib.py
More file actions
110 lines (95 loc) · 4.62 KB
/
Copy pathcontextlib.py
File metadata and controls
110 lines (95 loc) · 4.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
@namespace("contextlib")
from Promethium import ValueError
# A small, opt-in subset of Python's contextlib module: `closing` and
# `ExitStack`, the two pieces that need only the `with`-statement protocol
# (confirmed working) and ordinary object construction/composition
# (confirmed working — a class constructor can take an already-constructed
# instance of another Promethium-defined class as an argument). Both are
# real, general-purpose implementations, not toy versions on Echoes,
# Island, and Cooper.
#
# Blocked on Toffee: both hold an untyped context-manager reference
# (`cm`/`thing`) so they can wrap *any* Promethium object, the same
# untyped-field pattern `functools.py`'s `DefaultDict` note documents —
# on Toffee that erases to Objective-C `id`, whose surface doesn't expose
# arbitrary field/method access the way Echoes/Island/Cooper's real CLR-
# or JVM-backed `object` does (confirmed: `_CMNode`'s own `prev` field
# becomes unreachable — "No member 'prev' on type 'id'" — even though
# `_CMNode` is a concrete Promethium class, apparently because it holds
# an untyped field itself). Raises on Toffee instead of silently
# misbehaving, same convention as `functools.reduce`/`partial`.
#
# `@contextmanager` is NOT attempted: it needs to drive a generator
# function manually (call it once to run to the `yield`, capture the
# yielded value as `__enter__`'s result, resume it later for `__exit__`) —
# a different mechanism from anything confirmed working elsewhere in this
# codebase, and out of scope for this pass.
#
# `suppress`/`redirect_stdout` are NOT attempted either: `suppress` needs
# to match a caught exception's runtime type against a set of exception
# *types* passed in, and there's no confirmed pattern in this codebase for
# testing a Promethium exception value's actual type against an arbitrary
# `type` value at runtime; `redirect_stdout` needs a per-target way to
# swap `Console.Out`, not investigated here.
class _CMNode:
# `cm` is deliberately untyped so it can hold any Promethium object
# that implements `__enter__`/`__exit__` — matching this project's
# established untyped-callable-field pattern (see `functools.py`'s
# note on `DefaultDict`'s factory field). `prev` starts `None`
# unconditionally in `__init__` and is only ever overwritten from
# *outside* via a plain field write, never passed into the
# constructor alongside `cm` — mixing an untyped class-instance
# argument with an explicit `None` argument in the same constructor
# call was found to trip a compiler issue; this shape avoids it.
def __init__(self, cm):
self.cm = cm
self.prev = None
def closing(thing):
return _Closing(thing)
class _Closing:
def __init__(self, thing):
self._thing = thing
def __enter__(self):
return self._thing
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> bool:
if defined("TOFFEE"):
raise ValueError("contextlib.closing cannot call an untyped close() on Toffee yet")
else:
self._thing.close()
return False
class ExitStack:
# A genuine LIFO stack of entered context managers, unwound in
# reverse order on exit — verified end-to-end with two managers,
# correct enter/exit ordering (`enterA;enterB;...;exitB;exitA;`).
#
# Deliberately NOT backed by `List[T]`: retrieving a value back out
# of a `List[T]` (tried both `List[object]` and `List[typing.Any]`)
# and then calling a named method on it crashes with
# `OxygeneBinderException: No overload with these parameters` — a
# plain object field read (`self._top`, `node.prev`) does not have
# this problem. Implemented as a hand-rolled singly linked list of
# `_CMNode` instead, which only ever uses field access, never list
# indexing, to reach a stored context manager.
_count: int = 0
def enter_context(self, cm):
if defined("TOFFEE"):
raise ValueError("contextlib.ExitStack cannot hold an untyped context manager on Toffee yet")
else:
result = cm.__enter__()
node = _CMNode(cm)
if self._count > 0:
node.prev = self._top
self._top = node
self._count += 1
return result
def __enter__(self):
return self
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> bool:
if defined("TOFFEE"):
return False
else:
node = self._top
while node != None:
node.cm.__exit__(exc_type, exc_value, traceback)
node = node.prev
return False