Skip to content

Commit 4627815

Browse files
committed
add-loops-and-conditions
1 parent 082585e commit 4627815

2 files changed

Lines changed: 208 additions & 0 deletions

File tree

IPython/core/guarded_eval.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,82 @@ def dummy_function(*args, **kwargs):
817817
if hasattr(value, "__awaited_type__"):
818818
return value.__awaited_type__
819819
return value
820+
if isinstance(node, ast.While):
821+
loop_locals = context.transient_locals.copy()
822+
loop_context = context.replace(transient_locals=loop_locals)
823+
824+
result = None
825+
for stmt in node.body:
826+
result = eval_node(stmt, loop_context)
827+
828+
policy = get_policy(context)
829+
merged_locals = _merge_dicts_by_key(
830+
[loop_locals, context.transient_locals.copy()], policy
831+
)
832+
context.transient_locals.update(merged_locals)
833+
834+
return result
835+
if isinstance(node, ast.For):
836+
try:
837+
iterable = eval_node(node.iter, context)
838+
except:
839+
iterable = None
840+
841+
loop_var_value = None
842+
if iterable is not None:
843+
try:
844+
if policy.can_call(iterable.__iter__):
845+
iterator = iter(iterable)
846+
loop_var_value = next(iterator)
847+
except:
848+
pass
849+
850+
loop_locals = context.transient_locals.copy()
851+
if isinstance(node.target, ast.Name):
852+
if loop_var_value is not None:
853+
loop_locals[node.target.id] = loop_var_value
854+
855+
loop_context = context.replace(transient_locals=loop_locals)
856+
857+
result = None
858+
for stmt in node.body:
859+
result = eval_node(stmt, loop_context)
860+
861+
policy = get_policy(context)
862+
merged_locals = _merge_dicts_by_key(
863+
[loop_locals, context.transient_locals.copy()], policy
864+
)
865+
context.transient_locals.update(merged_locals)
866+
867+
return result
868+
if isinstance(node, ast.If):
869+
branches = []
870+
current = node
871+
result = None
872+
while True:
873+
branch_locals = context.transient_locals.copy()
874+
branch_context = context.replace(transient_locals=branch_locals)
875+
for stmt in current.body:
876+
result = eval_node(stmt, branch_context)
877+
branches.append(branch_locals)
878+
if not current.orelse:
879+
break
880+
elif len(current.orelse) == 1 and isinstance(current.orelse[0], ast.If):
881+
# It's an elif - continue loop
882+
current = current.orelse[0]
883+
else:
884+
# It's an else block - process and break
885+
else_locals = context.transient_locals.copy()
886+
else_context = context.replace(transient_locals=else_locals)
887+
for stmt in current.orelse:
888+
result = eval_node(stmt, else_context)
889+
branches.append(else_locals)
890+
break
891+
branches.append(context.transient_locals.copy())
892+
policy = get_policy(context)
893+
merged_locals = _merge_dicts_by_key(branches, policy)
894+
context.transient_locals.update(merged_locals)
895+
return result
820896
if isinstance(node, ast.Assign):
821897
return _handle_assign(node, context)
822898
if isinstance(node, ast.AnnAssign):
@@ -1006,6 +1082,23 @@ def dummy_function(*args, **kwargs):
10061082
return None
10071083

10081084

1085+
def _merge_dicts_by_key(dicts: list, policy: EvaluationPolicy):
1086+
"""Merge multiple dictionaries, combining values for each key."""
1087+
if len(dicts) == 1:
1088+
return dicts[0]
1089+
1090+
all_keys = set()
1091+
for d in dicts:
1092+
all_keys.update(d.keys())
1093+
1094+
merged = {}
1095+
for key in all_keys:
1096+
values = [d[key] for d in dicts if key in d]
1097+
if values:
1098+
merged[key] = _merge_values(values, policy)
1099+
1100+
return merged
1101+
10091102
def _merge_values(values, policy: EvaluationPolicy):
10101103
"""Recursively merge multiple values, combining attributes and dict items."""
10111104
if len(values) == 1:
@@ -1368,6 +1461,7 @@ def _list_methods(cls, source=None):
13681461
collections.Counter.most_common,
13691462
object.__dir__,
13701463
type.__dir__,
1464+
_Duck.__dir__,
13711465
}
13721466

13731467
BUILTIN_GETATTR: set[MayHaveGetattr] = {

tests/test_completer.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2316,6 +2316,120 @@ def _(expected):
23162316
),
23172317
"append",
23182318
],
2319+
[
2320+
"\n".join(["t = []", "if some_condition:", " t."]),
2321+
"append",
2322+
],
2323+
[
2324+
"\n".join(
2325+
[
2326+
"t = []",
2327+
"if some_condition:",
2328+
" t = 'string'",
2329+
"t.",
2330+
]
2331+
),
2332+
["append", "capitalize"],
2333+
],
2334+
[
2335+
"\n".join(
2336+
[
2337+
"t = []",
2338+
"if some_condition:",
2339+
" t = 'string'",
2340+
"else:",
2341+
" t.",
2342+
]
2343+
),
2344+
"append",
2345+
],
2346+
[
2347+
"\n".join(
2348+
[
2349+
"t = []",
2350+
"if some_condition:",
2351+
" t = 'string'",
2352+
"else:",
2353+
" t = 1",
2354+
"t.",
2355+
]
2356+
),
2357+
["append", "capitalize", "as_integer_ratio"],
2358+
],
2359+
[
2360+
"\n".join(
2361+
[
2362+
"t = []",
2363+
"if condition_1:",
2364+
" t = 'string'",
2365+
"elif condition_2:",
2366+
" t = 1",
2367+
"elif condition_3:",
2368+
" t.",
2369+
]
2370+
),
2371+
"append",
2372+
],
2373+
[
2374+
"\n".join(
2375+
[
2376+
"t = []",
2377+
"if condition_1:",
2378+
" t = 'string'",
2379+
"elif condition_2:",
2380+
" t = 1",
2381+
"elif condition_3:",
2382+
" t = {}",
2383+
"t.",
2384+
]
2385+
),
2386+
["append", "capitalize", "as_integer_ratio", "keys"],
2387+
],
2388+
[
2389+
"\n".join(
2390+
[
2391+
"t = []",
2392+
"if condition_1:",
2393+
" if condition_2:",
2394+
" t = 'nested'",
2395+
"t.",
2396+
]
2397+
),
2398+
["append", "capitalize"],
2399+
],
2400+
[
2401+
"\n".join(
2402+
[
2403+
"a = []",
2404+
"while condition:",
2405+
" a.",
2406+
]
2407+
),
2408+
"append",
2409+
],
2410+
[
2411+
"\n".join(
2412+
[
2413+
"t = []",
2414+
"while condition:",
2415+
" t = 'str'",
2416+
"t.",
2417+
]
2418+
),
2419+
["append", "capitalize"],
2420+
],
2421+
[
2422+
"\n".join(
2423+
[
2424+
"t = []",
2425+
"while condition_1:",
2426+
" while condition_2:",
2427+
" t = 'str'",
2428+
"t.",
2429+
]
2430+
),
2431+
["append", "capitalize"],
2432+
],
23192433
],
23202434
)
23212435
def test_undefined_variables(use_jedi, evaluation, code, insert_text):

0 commit comments

Comments
 (0)