Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/state_mutation_rule_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ rule was already inside the contract and is untouched.
| `css` | `None` | n/a | contract-level absence (ledgered) |
| `dart` | 764 -> 2052 | 2 | too narrow: only *unspaced* `x=1` matched; `x = 1` was invisible |
| `dockerfile` | 13 -> 3 | 4 -> 2 | too broad: `ENV` is `globals` (corollary 4) |
| `embedded_python` | 191 -> 894 | 2 | agrees (#2817): same plain-assignment arm as `python`, hardware-toggle arm kept |
| `embedded_python` | 191 -> 894 -> 892 | 2 | agrees (#2817); hardware arm tightened to the contract: `.value(` requires an argument (the no-arg getter is a read, corollary 3), and `.on/.off/.high/.low/.toggle` match the no-arg form only (a parameterised `.on(evt, cb)` is an event subscription `events`/`listeners` own, corollary 4) |
| `fortran` | 6344 -> 5066 | 3 | too broad: `INTEGER :: X = 1`, `CALL f(UNIT = 10)` mid-line specifiers |
| `go` | 3344 -> 1548 | 5 -> 2 | too broad: `:=` declarations, `_ = x` discards, `append(` double-counting its own `=` |
| `groovy` | 730 -> 895 | 2 | too broad/narrow: `@Setter`/`@Data` counted; `+=`, `++`, `.add(` did not |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,22 @@
# #2817: count a plain assignment statement (`x = v`, `obj.attr = v`,
# `d[k] = v`) as a write -- see python.py for the space-before-`=` black
# anchor, the `==`/kwarg/default/annotated exclusions, and why re.M.
# The hardware-toggle arm (`.value(`/`.on(`/...) is kept.
#
# Hardware-toggle arm (#2765 contract corollaries 3 & 4): a call by
# itself is not a write -- a read is not a write, and a token another
# rule owns is not a second signal.
# * `.value(` is BOTH getter and setter in MicroPython/CircuitPython:
# `pin.value(1)` writes, `pin.value()` reads. Require a non-empty
# argument so the getter (`if pin.value() == 0`, `x = pin.value()`)
# is not miscounted -- the `x = ...` case still counts once via the
# assignment arm above, not twice.
# * `.on()/.off()/.high()/.low()/.toggle()` are the no-arg imperative
# write forms; require empty parens so a parameterised `.on(evt, cb)`
# -- an event SUBSCRIPTION owned by `events`/`listeners` -- does not
# read as a mutation.
r"(?:^|;)[ \t]*[A-Za-z_]\w*(?:\.[A-Za-z_]\w*|\[[^\]\n]{0,80}\])*[ \t]+=(?![=])(?![^\n(]{0,300},[ \t]*$)"
r"|\bglobal\b|\bnonlocal\b|\b(?:self|cls)\.\w+[ \t]*=|:=|(?:\.\w+)?\.(?:append|extend|update|pop|remove|insert|clear)\s*\(|\.(?:value|on|off|high|low|toggle)\s*\(",
r"|\bglobal\b|\bnonlocal\b|\b(?:self|cls)\.\w+[ \t]*=|:=|(?:\.\w+)?\.(?:append|extend|update|pop|remove|insert|clear)\s*\("
r"|\.value\s*\(\s*[^)\s]|\.(?:on|off|high|low|toggle)\s*\(\s*\)",
re.M,
),
# 12. dead_code (Commented Logic / Deprecated Trails)
Expand Down
23 changes: 20 additions & 3 deletions tests/extraction/languages/test_state_mutation_contract_2765.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,21 @@ def _rule(lang):
["print(self.value)", "x == 1", "foo(x=1)", "def f(x=1):", "x: int = 1"],
),
"embedded_python": (
# #2817: same plain-assignment arm as python; keeps the hardware-toggle arm.
["x = 1", "obj.attr = 1", "led.value(1)", "global counter"],
["led.value == 1", "x == 1", "foo(x=1)", "def f(x=1):", "x: int = 1"],
# #2817: same plain-assignment arm as python. Hardware-toggle arm (corollaries
# 3 & 4): `.value(arg)` and the no-arg `.on()/.off()/.high()/.low()/.toggle()`
# imperative forms are writes; a `.value()` getter, an event-subscribe
# `.on(evt, cb)`, and a `.value` read are not.
["x = 1", "obj.attr = 1", "led.value(1)", "global counter", "led.on()", "pin.toggle()"],
[
"led.value == 1",
"x == 1",
"foo(x=1)",
"def f(x=1):",
"x: int = 1",
"if pin.value() == 0:", # corollary 3: the value getter is a read
"return sensor.value()", # corollary 3: getter read, no assignment
"emitter.on('evt', cb)", # corollary 4: event subscription (events/listeners own it)
],
),
# --- corollary 4: a token another rule owns is not a second signal ----------------
"dockerfile": (
Expand Down Expand Up @@ -411,6 +423,11 @@ def _rule(lang):
("embedded_python", "x = 1", 1),
("embedded_python", "led.value(1)", 1),
("embedded_python", "foo(x=1)", 0),
# #2765 corollary 3/4: reading a getter into an assignment is ONE write (the
# assignment), not two -- the `.value()` getter must not add a second hit.
("embedded_python", "val = self.pin.value()", 1),
("embedded_python", "if pin.value() == 0:", 0),
("embedded_python", "led.on()", 1),
("ruby", "x = 1", 1),
("ruby", "arr[0] = 1", 1),
("ruby", "CONST = 1", 0), # constant assignment is freeze_hits, not flux
Expand Down
Loading
Loading