@@ -947,3 +947,193 @@ def items():
947947 return [1, 2, 3]
948948 assert [x * 2 for x in items()] == [2, 4, 7]
949949""" )
950+
951+
952+ # ---------------------------------------------------------------------------
953+ # Edge cases: combinations of new visitors with existing ones
954+ # ---------------------------------------------------------------------------
955+
956+
957+ class TestEdgeCases :
958+ """Regression and edge-case tests combining multiple expression types."""
959+
960+ def test_subscript_with_variable_key (self ) -> None :
961+ """Subscript where the key is a variable (not constant)."""
962+ assert_introspects (
963+ """
964+ def check():
965+ d = {"hello": 42}
966+ key = "hello"
967+ assert d[key] == 100
968+ """ ,
969+ must_contain = ["where 42 = " , "['hello']" ],
970+ )
971+
972+ def test_subscript_with_call_key (self ) -> None :
973+ """Subscript where the key is a function call."""
974+ assert_introspects (
975+ """
976+ def check():
977+ d = {0: "zero", 1: "one"}
978+ def get_key():
979+ return 0
980+ assert d[get_key()] == "wrong"
981+ """ ,
982+ must_contain = ["'zero'" , "'wrong'" ],
983+ )
984+
985+ def test_nested_subscript (self ) -> None :
986+ """Nested subscript: d[k1][k2]."""
987+ assert_introspects (
988+ """
989+ def check():
990+ d = {"a": {"b": 42}}
991+ assert d["a"]["b"] == 100
992+ """ ,
993+ must_contain = ["42" , "100" ],
994+ )
995+
996+ def test_method_call_with_args (self ) -> None :
997+ """Method call with arguments shows flat format."""
998+ assert_introspects (
999+ """
1000+ def check():
1001+ class Calculator:
1002+ def add(self, a, b):
1003+ return a + b
1004+ def __repr__(self):
1005+ return "Calc()"
1006+ c = Calculator()
1007+ assert c.add(2, 3) == 10
1008+ """ ,
1009+ must_contain = ["where 5 = Calc().add(2, 3)" ],
1010+ )
1011+
1012+ def test_chained_method_calls (self ) -> None :
1013+ """Chained method call: obj.method1().method2()."""
1014+ assert_introspects (
1015+ """
1016+ def check():
1017+ class Builder:
1018+ def __init__(self, val=0):
1019+ self.val = val
1020+ def add(self, n):
1021+ return Builder(self.val + n)
1022+ def result(self):
1023+ return self.val
1024+ def __repr__(self):
1025+ return f"Builder({self.val})"
1026+ b = Builder()
1027+ assert b.add(5).result() == 100
1028+ """ ,
1029+ must_contain = ["where 5 = " , ".result()" ],
1030+ )
1031+
1032+ def test_subscript_on_method_result (self ) -> None :
1033+ """Subscript on method return value: obj.method()[key]."""
1034+ assert_introspects (
1035+ """
1036+ def check():
1037+ class Store:
1038+ def get_data(self):
1039+ return {"x": 42}
1040+ def __repr__(self):
1041+ return "Store()"
1042+ s = Store()
1043+ assert s.get_data()["x"] == 100
1044+ """ ,
1045+ must_contain = ["42" , "100" ],
1046+ )
1047+
1048+ def test_ifexp_with_call_condition (self ) -> None :
1049+ """IfExp where condition is a function call."""
1050+ assert_introspects (
1051+ """
1052+ def check():
1053+ def is_ready():
1054+ return False
1055+ assert (1 if is_ready() else 0) == 1
1056+ """ ,
1057+ must_contain = ["if False else" ],
1058+ )
1059+
1060+ def test_walrus_in_subscript (self ) -> None :
1061+ """Walrus operator used as subscript key."""
1062+ assert_semantically_equivalent ("""
1063+ def check():
1064+ d = {1: "one", 2: "two"}
1065+ x = 1
1066+ assert d[(y := x + 1)] == "wrong"
1067+ """ )
1068+
1069+ def test_method_call_single_evaluation (self ) -> None :
1070+ """Method with side effects is only called once."""
1071+ assert_single_evaluation ("""
1072+ def check():
1073+ class Obj:
1074+ def compute(self):
1075+ counter[0] += 1
1076+ return 42
1077+ obj = Obj()
1078+ assert obj.compute() == 100
1079+ """ )
1080+
1081+ def test_subscript_single_evaluation (self ) -> None :
1082+ """Custom __getitem__ with side effects is only called once."""
1083+ assert_single_evaluation ("""
1084+ def check():
1085+ class CountingList:
1086+ def __init__(self, items):
1087+ self.items = items
1088+ def __getitem__(self, idx):
1089+ counter[0] += 1
1090+ return self.items[idx]
1091+ def __repr__(self):
1092+ return repr(self.items)
1093+ lst = CountingList([10, 20, 30])
1094+ assert lst[1] == 99
1095+ """ )
1096+
1097+ def test_ifexp_condition_single_evaluation (self ) -> None :
1098+ """IfExp condition with side effects is only evaluated once."""
1099+ assert_single_evaluation ("""
1100+ def check():
1101+ def check_flag():
1102+ counter[0] += 1
1103+ return True
1104+ assert (0 if check_flag() else 1) == 99
1105+ """ )
1106+
1107+ def test_complex_assertion_semantics (self ) -> None :
1108+ """Complex assertion combining multiple new visitors."""
1109+ assert_semantically_equivalent ("""
1110+ def check():
1111+ class Config:
1112+ def __init__(self):
1113+ self.data = {"timeout": 30}
1114+ def get(self, key):
1115+ return self.data[key]
1116+ cfg = Config()
1117+ flag = True
1118+ assert (cfg.get("timeout") if flag else 0) > 60
1119+ """ )
1120+
1121+ def test_assert_with_message_still_works (self ) -> None :
1122+ """Assert with a custom message still works with new visitors."""
1123+ msg = get_failure_message ("""
1124+ def check():
1125+ d = {"key": 42}
1126+ assert d["key"] == 100, "custom failure message"
1127+ """ )
1128+ assert "custom failure message" in msg
1129+
1130+ def test_method_call_on_global (self ) -> None :
1131+ """Method call on a global/module-level object."""
1132+ assert_introspects (
1133+ """
1134+ items = [1, 2, 3]
1135+ def check():
1136+ assert items.count(99) == 1
1137+ """ ,
1138+ must_contain = ["where 0 = [1, 2, 3].count(99)" ],
1139+ )
0 commit comments