Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add iterators and lists #67

Merged
merged 113 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
414dbf1
Factor out checking code
mark-koch Nov 21, 2023
bba9c96
Factor out graph generation code
mark-koch Nov 21, 2023
eb1a327
Update cfg
mark-koch Nov 21, 2023
4390ddd
Update files
mark-koch Nov 21, 2023
d48a149
Add statement compiler
mark-koch Nov 21, 2023
a5d51e1
Moves custom AST nodes to separate file
mark-koch Nov 21, 2023
3edaa00
Add custom funcs, replacing extensions
mark-koch Nov 21, 2023
de2508f
Factor out decorator from module code
mark-koch Nov 21, 2023
f9033fd
Reimplement prelude
mark-koch Nov 21, 2023
5fb37d0
Remove unused files
mark-koch Nov 21, 2023
18222ed
Fix tests
mark-koch Nov 21, 2023
6db0327
Remove unused imports
mark-koch Nov 21, 2023
8616f6b
Run formatting
mark-koch Nov 21, 2023
de454bd
Rename guppy_types to types
mark-koch Nov 21, 2023
4dba8a7
Make node optional for type building
mark-koch Nov 21, 2023
3b0d0f4
Get rid of NotImplementedCompiler
mark-koch Nov 22, 2023
b7ccf59
Make base_checker in ReversingChecker optional
mark-koch Nov 22, 2023
c4bcbea
Add unsupported builtins
mark-koch Nov 22, 2023
ab599bc
Rename types to gtypes
mark-koch Nov 22, 2023
f7be21d
Split up into multiple PRs
mark-koch Nov 22, 2023
871b14c
Add type checking code
mark-koch Nov 22, 2023
aa33857
Add _internal file
mark-koch Nov 22, 2023
aabc6ca
Fix formatting
mark-koch Nov 22, 2023
21585fa
Work on polymorphism
mark-koch Nov 24, 2023
b0f482e
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 24, 2023
497cd23
refactor: Move graph generation code (#59)
mark-koch Nov 24, 2023
0f1c428
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 24, 2023
62dbf26
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 24, 2023
a3ba236
Fix add_input_with_ports
mark-koch Nov 24, 2023
0851fd6
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 24, 2023
0b4ee03
Make CustomCallChecker.check abstract again
mark-koch Nov 24, 2023
babe9be
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 24, 2023
c26aa2e
Fix type parsing
mark-koch Nov 24, 2023
35739a8
Compile TypeApply as dummy node
mark-koch Nov 24, 2023
8cf7e1e
Try synthesis before checking calls
mark-koch Nov 27, 2023
a9238ec
Fix annotated assign statement
mark-koch Nov 27, 2023
708ab55
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 27, 2023
96d96b1
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 27, 2023
3af488f
Factor out polymorphic checking code
mark-koch Nov 27, 2023
ed23fa3
Fix polymorphic type parsing
mark-koch Nov 27, 2023
d252bca
Run formatting
mark-koch Nov 27, 2023
1dd9d2b
Improve docstrings and specialise inference error
mark-koch Nov 27, 2023
4eba3dc
feat: Add builtins module (#60)
mark-koch Nov 27, 2023
d6982d5
Fix mypy issues
mark-koch Nov 27, 2023
3e89ac8
Run formatting
mark-koch Nov 27, 2023
6ea1df5
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 27, 2023
ebd9e88
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 27, 2023
b9c81ef
Add missing check method
mark-koch Nov 27, 2023
867e982
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 27, 2023
0945904
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 27, 2023
2aed4bf
Add some tests
mark-koch Nov 27, 2023
9e89ce1
Merge remote-tracking branch 'origin/main' into refactor/compilation-…
mark-koch Nov 27, 2023
d09ce09
Merge remote-tracking branch 'origin/refactor/compilation-stages' int…
mark-koch Nov 27, 2023
a39389c
Fix add_input_with_ports
mark-koch Nov 27, 2023
8aea545
Remove unused import
mark-koch Nov 27, 2023
05dc741
Merge branch 'refactor/compilation-stages' into stages/check
mark-koch Nov 27, 2023
b2ad478
Remove unused import
mark-koch Nov 27, 2023
022ef1a
Merge remote-tracking branch 'origin/stages/check' into feat/poly
mark-koch Nov 27, 2023
c3b1228
Fix small issues
mark-koch Nov 27, 2023
a83fff5
Make everything work with Hugr
mark-koch Nov 27, 2023
559469b
Add type args to CoercingChecker
mark-koch Nov 27, 2023
8da2b4a
Make non-linear args copyable
mark-koch Nov 27, 2023
9a0647f
Protect instantiated tuples from being turned into rows
mark-koch Nov 28, 2023
05fe2e5
Add more tests
mark-koch Nov 28, 2023
9bbfc86
Run formatting
mark-koch Nov 28, 2023
b1bdbe6
Store free vars in set instead of dict
mark-koch Nov 28, 2023
b52230b
Respect linearity
mark-koch Nov 28, 2023
506398f
Add more tests
mark-koch Nov 28, 2023
61e2cba
Detect unused linear expressions
mark-koch Nov 28, 2023
aface01
Fix UnknownFunctionType
mark-koch Nov 30, 2023
7269ea3
Run formatting
mark-koch Nov 30, 2023
302e2aa
Improve synthesize_call docstring
mark-koch Nov 30, 2023
145c6aa
Add module docstrings
mark-koch Nov 30, 2023
6b833cb
Rename cfg to containing_cfg
mark-koch Nov 30, 2023
29c6009
Improve check for entry BB
mark-koch Nov 30, 2023
2a3d01b
Merge branch 'stages/check' into feat/poly
mark-koch Nov 30, 2023
f982901
Use booly value of list
mark-koch Dec 4, 2023
55d7271
Use contextlib suppress
mark-koch Dec 4, 2023
6e3c2f3
Fix typo
mark-koch Dec 4, 2023
dacc31f
Clarify comment
mark-koch Dec 4, 2023
a872eed
Clarify docstring
mark-koch Dec 4, 2023
43e25e3
Align op table
mark-koch Dec 4, 2023
4de95e8
Specify type: ignore
mark-koch Dec 4, 2023
34db487
Improve error message wording
mark-koch Dec 4, 2023
c522771
Use pattern matches
mark-koch Dec 4, 2023
f930ecc
Turn assert into exception
mark-koch Dec 4, 2023
f5e60cf
Specify type: ignore
mark-koch Dec 4, 2023
2f29f2f
Fix CI
mark-koch Dec 4, 2023
e09dda1
Merge branch 'stages/check' into feat/poly
mark-koch Dec 5, 2023
6765fd8
Merge remote-tracking branch 'origin/main' into feat/poly
mark-koch Dec 5, 2023
91bb0eb
Merge remote-tracking branch 'origin/main' into feat/poly
mark-koch Dec 11, 2023
325dd25
Fix linting
mark-koch Dec 11, 2023
7b34e89
wip: Add list types and nodes
mark-koch Dec 20, 2023
d20af98
Add TypeBound join method
mark-koch Dec 20, 2023
4b92cc3
Add lists and linsts to builtins
mark-koch Dec 20, 2023
c153c65
Allow bound in decorated type
mark-koch Dec 20, 2023
46c5780
Add lists to default globals
mark-koch Dec 20, 2023
09ac632
Improve upper type bound computation
mark-koch Jan 3, 2024
3a5cb3d
Fix list op names
mark-koch Jan 3, 2024
f29ae3c
Improve ExprChecker docstring
mark-koch Jan 3, 2024
40bd94a
Fix error column offset
mark-koch Jan 3, 2024
3eedefb
Merge remote-tracking branch 'origin/main' into feat/poly
mark-koch Jan 9, 2024
876a5de
Rename FreeTypeVar to ExistentialTypeVar and free_vars to unsolved_vars
mark-koch Jan 9, 2024
618f20b
Clarify TypeApplication docstring
mark-koch Jan 9, 2024
f4053e2
Merge remote-tracking branch 'origin/feat/poly' into feat/lists
mark-koch Jan 9, 2024
3fcb3ef
Merge remote-tracking branch 'origin/main' into feat/lists
mark-koch Jan 15, 2024
e1e4da3
feat: Build for loops and comprehensions (#68)
mark-koch Jan 16, 2024
e22fde7
feat: Type check lists and comprehensions (#69)
mark-koch Jan 16, 2024
270ea9e
feat: Compile lists and comprehensions (#70)
mark-koch Jan 16, 2024
449ca6f
test: Add list, loop, and comprehension tests (#71)
mark-koch Jan 16, 2024
f33376e
Remove unused Rust import
mark-koch Jan 16, 2024
37a140e
cargo update
ss2165 Jan 16, 2024
83b95e4
bump validator version
ss2165 Jan 16, 2024
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
187 changes: 150 additions & 37 deletions guppy/ast_util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import ast
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
import textwrap
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, cast

if TYPE_CHECKING:
from guppy.gtypes import GuppyType
Expand Down Expand Up @@ -54,51 +57,161 @@ def generic_visit(self, node: Any, *args: Any, **kwargs: Any) -> T:
raise NotImplementedError(f"visit_{node.__class__.__name__} is not implemented")


class NameVisitor(ast.NodeVisitor):
"""Visitor to collect all `Name` nodes occurring in an AST."""

names: list[ast.Name]

def __init__(self) -> None:
self.names = []

def visit_Name(self, node: ast.Name) -> None:
self.names.append(node)
class AstSearcher(ast.NodeVisitor):
"""Visitor that searches for occurrences of specific nodes in an AST."""

matcher: Callable[[ast.AST], bool]
dont_recurse_into: set[type[ast.AST]]
found: list[ast.AST]
is_first_node: bool

def __init__(
self,
matcher: Callable[[ast.AST], bool],
dont_recurse_into: set[type[ast.AST]] | None = None,
) -> None:
self.matcher = matcher
self.dont_recurse_into = dont_recurse_into or set()
self.found = []
self.is_first_node = True

def generic_visit(self, node: ast.AST) -> None:
if self.matcher(node):
self.found.append(node)
if self.is_first_node or type(node) not in self.dont_recurse_into:
self.is_first_node = False
super().generic_visit(node)


def find_nodes(
matcher: Callable[[ast.AST], bool],
node: ast.AST,
dont_recurse_into: set[type[ast.AST]] | None = None,
) -> list[ast.AST]:
"""Returns all nodes in the AST that satisfy the matcher."""
v = AstSearcher(matcher, dont_recurse_into)
v.visit(node)
return v.found


def name_nodes_in_ast(node: Any) -> list[ast.Name]:
"""Returns all `Name` nodes occurring in an AST."""
v = NameVisitor()
v.visit(node)
return v.names


class ReturnVisitor(ast.NodeVisitor):
"""Visitor to collect all `Return` nodes occurring in an AST."""
found = find_nodes(lambda n: isinstance(n, ast.Name), node)
return cast(list[ast.Name], found)

returns: list[ast.Return]
inside_func_def: bool

def __init__(self) -> None:
self.returns = []
self.inside_func_def = False

def visit_Return(self, node: ast.Return) -> None:
self.returns.append(node)
def return_nodes_in_ast(node: Any) -> list[ast.Return]:
"""Returns all `Return` nodes occurring in an AST."""
found = find_nodes(lambda n: isinstance(n, ast.Return), node, {ast.FunctionDef})
return cast(list[ast.Return], found)

def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
# Don't descend into nested function definitions
if not self.inside_func_def:
self.inside_func_def = True
for n in node.body:
self.visit(n)

def breaks_in_loop(node: Any) -> list[ast.Break]:
"""Returns all `Break` nodes occurring in a loop.

def return_nodes_in_ast(node: Any) -> list[ast.Return]:
"""Returns all `Return` nodes occurring in an AST."""
v = ReturnVisitor()
v.visit(node)
return v.returns
Note that breaks in nested loops are excluded.
"""
found = find_nodes(
lambda n: isinstance(n, ast.Break), node, {ast.For, ast.While, ast.FunctionDef}
)
return cast(list[ast.Break], found)


class ContextAdjuster(ast.NodeTransformer):
"""Updates the `ast.Context` indicating if expressions occur on the LHS or RHS."""

ctx: ast.expr_context

def __init__(self, ctx: ast.expr_context) -> None:
self.ctx = ctx

def visit(self, node: ast.AST) -> ast.AST:
return cast(ast.AST, super().visit(node))

def visit_Name(self, node: ast.Name) -> ast.Name:
return with_loc(node, ast.Name(id=node.id, ctx=self.ctx))

def visit_Starred(self, node: ast.Starred) -> ast.Starred:
return with_loc(node, ast.Starred(value=self.visit(node.value), ctx=self.ctx))

def visit_Tuple(self, node: ast.Tuple) -> ast.Tuple:
return with_loc(
node, ast.Tuple(elts=[self.visit(elt) for elt in node.elts], ctx=self.ctx)
)

def visit_List(self, node: ast.List) -> ast.List:
return with_loc(
node, ast.List(elts=[self.visit(elt) for elt in node.elts], ctx=self.ctx)
)

def visit_Subscript(self, node: ast.Subscript) -> ast.Subscript:
# Don't adjust the slice!
return with_loc(
node,
ast.Subscript(value=self.visit(node.value), slice=node.slice, ctx=self.ctx),
)

def visit_Attribute(self, node: ast.Attribute) -> ast.Attribute:
return with_loc(
node,
ast.Attribute(value=self.visit(node.value), attr=node.attr, ctx=self.ctx),
)


@dataclass(frozen=True, eq=False)
class TemplateReplacer(ast.NodeTransformer):
"""Replaces nodes in a template."""

replacements: Mapping[str, ast.AST | Sequence[ast.AST]]
default_loc: ast.AST

def _get_replacement(self, x: str) -> ast.AST | Sequence[ast.AST]:
if x not in self.replacements:
msg = f"No replacement for `{x}` is given"
raise ValueError(msg)
return self.replacements[x]

def visit_Name(self, node: ast.Name) -> ast.AST:
repl = self._get_replacement(node.id)
if not isinstance(repl, ast.expr):
msg = f"Replacement for `{node.id}` must be an expression"
raise TypeError(msg)

# Update the context
adjuster = ContextAdjuster(node.ctx)
return with_loc(repl, adjuster.visit(repl))

def visit_Expr(self, node: ast.Expr) -> ast.AST | Sequence[ast.AST]:
if isinstance(node.value, ast.Name):
repl = self._get_replacement(node.value.id)
repls = [repl] if not isinstance(repl, Sequence) else repl
# Wrap expressions to turn them into statements
return [
with_loc(r, ast.Expr(value=r)) if isinstance(r, ast.expr) else r
for r in repls
]
return self.generic_visit(node)

def generic_visit(self, node: ast.AST) -> ast.AST:
# Insert the default location
node = super().generic_visit(node)
return with_loc(self.default_loc, node)


def template_replace(
template: str, default_loc: ast.AST, **kwargs: ast.AST | Sequence[ast.AST]
) -> list[ast.stmt]:
"""Turns a template into a proper AST by substituting all placeholders."""
nodes = ast.parse(textwrap.dedent(template)).body
replacer = TemplateReplacer(kwargs, default_loc)
new_nodes = []
for n in nodes:
new = replacer.visit(n)
if isinstance(new, list):
new_nodes.extend(new)
else:
new_nodes.append(new)
return new_nodes


def line_col(node: ast.AST) -> tuple[int, int]:
Expand Down
21 changes: 20 additions & 1 deletion guppy/cfg/bb.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing_extensions import Self

from guppy.ast_util import AstNode, name_nodes_in_ast
from guppy.nodes import NestedFunctionDef, PyExpr
from guppy.nodes import DesugaredListComp, NestedFunctionDef, PyExpr

if TYPE_CHECKING:
from guppy.cfg.cfg import BaseCFG
Expand Down Expand Up @@ -119,6 +119,25 @@ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
for name in name_nodes_in_ast(node.target):
self.stats.assigned[name.id] = node

def visit_DesugaredListComp(self, node: DesugaredListComp) -> None:
# Names bound in the comprehension are only available inside, so we shouldn't
# update `self.stats` with assignments
inner_visitor = VariableVisitor(self.bb)
inner_stats = inner_visitor.stats

# The generators are evaluated left to right
for gen in node.generators:
inner_visitor.visit(gen.iter_assign)
inner_visitor.visit(gen.hasnext_assign)
inner_visitor.visit(gen.next_assign)
for cond in gen.ifs:
inner_visitor.visit(cond)
inner_visitor.visit(node.elt)

self.stats.used |= {
x: n for x, n in inner_stats.used.items() if x not in self.stats.assigned
}

def visit_PyExpr(self, node: PyExpr) -> None:
# Don't look into `py(...)` expressions
pass
Expand Down
Loading