From e54c3e64925bc7a3605c17fa86863a6ba710603d Mon Sep 17 00:00:00 2001 From: Stephen Macke Date: Thu, 25 Jan 2024 22:45:16 -0800 Subject: [PATCH] add nested virtual symbol to track mutations without bumping containing symbol shallow timestamp --- core/ipyflow/data_model/symbol.py | 34 +++++++++++++++++-- .../data_model/utils/update_protocol.py | 2 +- core/test/test_nested_symbols.py | 2 +- core/test/test_reactivity.py | 2 +- core/test/test_updated_symbols.py | 8 ++++- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/core/ipyflow/data_model/symbol.py b/core/ipyflow/data_model/symbol.py index 789dddc4..77997e91 100644 --- a/core/ipyflow/data_model/symbol.py +++ b/core/ipyflow/data_model/symbol.py @@ -100,6 +100,8 @@ class Symbol: IMMUTABLE_TYPES = set(IMMUTABLE_PRIMITIVE_TYPES) + IPYFLOW_MUTATION_VIRTUAL_SYMBOL_NAME = "__ipyflow_mutation" + def __init__( self, name: SupportedIndexType, @@ -505,6 +507,18 @@ def obj_len(self) -> Optional[int]: def obj_type(self) -> Type[Any]: return type(self.obj) + @property + def is_immutable(self) -> bool: + return self.obj_type in self.IMMUTABLE_TYPES + + @property + def is_mutation_virtual_symbol(self) -> bool: + return self.name == self.IPYFLOW_MUTATION_VIRTUAL_SYMBOL_NAME + + @property + def is_underscore(self) -> bool: + return self.name == "_" and self.containing_scope.is_global + @property def is_obj_lazy_module(self) -> bool: return self.obj_type is _LazyModule @@ -905,7 +919,7 @@ def should_mark_waiting(self, updated_dep): return True def _is_underscore_or_simple_assign(self, new_deps: Set["Symbol"]) -> bool: - if self.name == "_": + if self.is_underscore: # FIXME: distinguish between explicit assignment to _ from user and implicit assignment from kernel return True if not isinstance(self.stmt_node, (ast.Assign, ast.AnnAssign)): @@ -934,7 +948,7 @@ def update_deps( return if overwrite and not self.is_globally_accessible: self.watchpoints.clear() - if mutated and self.obj_type in self.IMMUTABLE_TYPES: + if mutated and self.is_immutable: return # if we get here, no longer implicit self._implicit = False @@ -961,6 +975,22 @@ def update_deps( self.fresher_ancestor_timestamps.clear() if mutated or isinstance(self.stmt_node, ast.AugAssign): self.update_usage_info() + if ( + (mutated or overwrite) + and Timestamp.current().is_initialized + and not self.is_immutable + and not self.is_mutation_virtual_symbol + and not self.is_anonymous + and self.containing_scope.is_global + and not self.is_underscore + and not self.is_implicit + and self.obj_type is not type + and not self.is_class + and self.namespace is not None + ): + self.namespace.upsert_symbol_for_name( + self.IPYFLOW_MUTATION_VIRTUAL_SYMBOL_NAME, object(), propagate=False + ) propagate = propagate and ( mutated or deleted or not self._should_cancel_propagation(prev_obj) ) diff --git a/core/ipyflow/data_model/utils/update_protocol.py b/core/ipyflow/data_model/utils/update_protocol.py index 5ce0cfc8..509ba7e1 100644 --- a/core/ipyflow/data_model/utils/update_protocol.py +++ b/core/ipyflow/data_model/utils/update_protocol.py @@ -174,7 +174,7 @@ def _propagate_waiting_to_namespace_children( if not skip_seen_check and sym in self.seen: return self.seen.add(sym) - self_ns = flow().namespaces.get(sym.obj_id, None) + self_ns = flow().namespaces.get(sym.obj_id) if self_ns is None: return for ns_child in self_ns.all_symbols_this_indentation(exclude_class=True): diff --git a/core/test/test_nested_symbols.py b/core/test/test_nested_symbols.py index aa745059..4fa18d94 100644 --- a/core/test/test_nested_symbols.py +++ b/core/test/test_nested_symbols.py @@ -128,7 +128,7 @@ def test_basic(): assert_detected("`y` depends on changed `d.y`") -def test_nested_readable_name(): +def test_nested_readable_name_attr(): run_cell("d = DotDict()") run_cell("d.x = DotDict()") run_cell("d.x.a = 5") diff --git a/core/test/test_reactivity.py b/core/test/test_reactivity.py index b4ef5862..b3a89014 100644 --- a/core/test/test_reactivity.py +++ b/core/test/test_reactivity.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import logging import sys -from test.utils import make_flow_fixture, skipif_known_failing +from test.utils import make_flow_fixture from typing import Optional, Set, Tuple from ipyflow.config import ExecutionMode diff --git a/core/test/test_updated_symbols.py b/core/test/test_updated_symbols.py index fb89ecc7..aa8e8722 100644 --- a/core/test/test_updated_symbols.py +++ b/core/test/test_updated_symbols.py @@ -2,6 +2,7 @@ import logging from test.utils import make_flow_fixture +from ipyflow.data_model.symbol import Symbol from ipyflow.singletons import flow logging.basicConfig(level=logging.ERROR) @@ -36,7 +37,12 @@ def test_simplest(): def test_dict_hierarchy(): run_cell("d = {}") updated_sym_names = updated_symbol_names() - assert updated_sym_names == ["d"], "got %s" % updated_sym_names + assert updated_sym_names == [ + "d", + f"d.{Symbol.IPYFLOW_MUTATION_VIRTUAL_SYMBOL_NAME}", + ], ( + "got %s" % updated_sym_names + ) run_cell('d["foo"] = {}') assert updated_symbol_names() == sorted(["d[foo]", "d"]) run_cell('d["foo"]["bar"] = []')