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

Stubs #33

Merged
merged 61 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
bbaf51f
Define map HOF
jmanuel1 Mar 3, 2024
978db2b
Resolve "from-import"-ed names using stub declarations
jmanuel1 Mar 6, 2024
5e30a72
Add snakeviz to dev dependencies
jmanuel1 Mar 23, 2024
6ca2a85
Back OrderedSet by 2-3 tree
jmanuel1 Mar 25, 2024
50f18b4
Move error message formatting to new module
jmanuel1 Mar 26, 2024
7a72b3e
Add type stubs for Python builtins
jmanuel1 Mar 27, 2024
1edc14e
Add type stubs for to_str and to_py_function
jmanuel1 Mar 27, 2024
ab89d75
Do more kind checking and allow forward references to forward operati…
jmanuel1 Mar 27, 2024
aef0d93
Implement pragmas that tell type checker about fundamental builtin types
jmanuel1 Mar 28, 2024
dc5267c
Fix order of variables in generalization
jmanuel1 May 26, 2024
333e816
Ignore flake8 shadowing warning
jmanuel1 May 30, 2024
57c0991
Fix LinkedList.__eq__
jmanuel1 May 30, 2024
272905c
Fix parsing in the REPL after adding error tolerance
jmanuel1 May 30, 2024
4b251be
Improve .many() error messages by being able to commit to an alternative
jmanuel1 May 30, 2024
4aa49aa
Use Python function as argument to cont.map in map_cont
jmanuel1 May 31, 2024
19fe9ff
Add preamble type stubs
jmanuel1 May 31, 2024
a732a09
Break infinite recursions in subtyping and free variables
jmanuel1 Jun 1, 2024
8d39206
Trade <= with is_subtype_of
jmanuel1 Jun 1, 2024
67ed672
Make notes about type-level lang design
jmanuel1 Jun 1, 2024
4b5a06c
Fix subtyping for object (top) and no_return (bottom)
jmanuel1 Jun 1, 2024
eefc36a
Try to get test_add_operator_inference to pass again
jmanuel1 Jun 4, 2024
ef29b57
Remove type hashing, except for variables
jmanuel1 Jun 4, 2024
2b2d811
Prevent sequence variables automatically introduced by type sequence …
jmanuel1 Jun 4, 2024
da1b127
Define get_type_of_attribute in terms of attributes
jmanuel1 Jun 4, 2024
4d8e98a
Import concat.parse at runtime
jmanuel1 Jun 4, 2024
85adac8
Give unrolled Fix the same id as the original type and use ids to com…
jmanuel1 Jun 5, 2024
ac8816d
Add verbose flag to print traceback for type errors
jmanuel1 Jun 6, 2024
48e911f
Add type stub for itertools.islice
jmanuel1 Jun 6, 2024
8075125
Add type stubs for to_slice and abs
jmanuel1 Jun 6, 2024
44266aa
Handle variadic generic type arguments in GenericType
jmanuel1 Jun 6, 2024
da0b9a6
Add cast because type variable failed to be unified
jmanuel1 Jun 6, 2024
8b8670e
Allow some (probably broken) subtyping between type of different arities
jmanuel1 Jun 6, 2024
9a11cbf
Implement kind subtyping
jmanuel1 Jul 19, 2024
1d31786
Provide user-friendly __str__ for kinds
jmanuel1 Jul 20, 2024
7b3e02b
Implement subsumption between polytypes using regeneralization
jmanuel1 Jul 20, 2024
b56be5c
Fail to constrain a bound variable only when it has to be substituted
jmanuel1 Jul 21, 2024
97eb347
Fix function definition transpiler test
jmanuel1 Jul 21, 2024
8755dd6
Fix some of the subtyping tests
jmanuel1 Jul 21, 2024
5bab4da
Fix typechecker tests
jmanuel1 Jul 21, 2024
f424567
Move test dependencies to pyproject.toml and remove tox
jmanuel1 Sep 2, 2024
5e1800b
Remove definition in Python of bool
jmanuel1 Sep 2, 2024
7fc0c46
Move all pyinterop.__init__ types to stub
jmanuel1 Sep 3, 2024
c411d78
Make sure missing stub causes error pointing to import*ing* file
jmanuel1 Sep 3, 2024
00a9217
Clean up imports in __main__
jmanuel1 Sep 3, 2024
c3b1fa3
Move as many preamble types as possible to stubs, separate nominal & …
jmanuel1 Sep 3, 2024
a10dbf1
Remove remaining module type guessing code and use stubs instead
jmanuel1 Sep 7, 2024
4ab282a
Update GitHub workflow after removing tox
jmanuel1 Sep 7, 2024
66a6b94
Make sure to generate coverage.xml and coverage.lcov
jmanuel1 Sep 7, 2024
8bdacae
Don't use sequence for multiple commands?
jmanuel1 Sep 7, 2024
078a9e1
Do some cleanup
jmanuel1 Oct 9, 2024
f119102
Build linked list in stack-safe way that doesn't require reversible i…
jmanuel1 Oct 9, 2024
31c30c2
Make an additional effort to avoid substitutions on types
jmanuel1 Oct 9, 2024
8995665
Simplify parameters of stub loading functions
jmanuel1 Oct 9, 2024
f98d2a6
Remove unused parameter of _generate_type_of_innermost_module
jmanuel1 Oct 9, 2024
67d5836
Attach end location to type application syntax
jmanuel1 Oct 11, 2024
8af8a09
Return result from _contains_assumption
jmanuel1 Oct 11, 2024
7b68cdd
Remove structural type check from ObjectType code
jmanuel1 Oct 11, 2024
f48e00d
Remove SubtypingExplanation and is_subtype_of
jmanuel1 Oct 11, 2024
b651a94
Raise type error if end of Python function constraint algorithm is re…
jmanuel1 Oct 11, 2024
d0b6271
Test typechecking of simple import forms
jmanuel1 Oct 12, 2024
55706b5
Run nose2 under coverage
jmanuel1 Oct 12, 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
11 changes: 8 additions & 3 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ jobs:
uses: actions/setup-python@v3
with:
python-version: "3.7"
- name: Install development dependencies
- name: Install test dependencies
run: |
pip install -e .[dev]
pip install -e .[test]
- name: Test
run: |
tox run
coverage run -m nose2 --pretty-assert concat.tests
- name: Collect coverage into one file
run: |
coverage combine
coverage xml
coverage lcov
- name: Report test coverage to DeepSource
uses: deepsourcelabs/test-coverage-action@master
with:
Expand Down
49 changes: 20 additions & 29 deletions concat/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@

import argparse
from concat.transpile import parse, transpile_ast, typecheck
import concat.astutils
from concat.error_reporting import get_line_at, create_parsing_failure_message
import concat.execute
import concat.lex
import concat.parser_combinators
import concat.stdlib.repl
import concat.typecheck
import io
import json
import os.path
import sys
import textwrap
from typing import Callable, IO, AnyStr, Sequence, TextIO
from typing import Callable, IO, AnyStr


filename = '<stdin>'
Expand All @@ -31,29 +29,6 @@ def func(name: str) -> IO[AnyStr]:
return func


def get_line_at(file: TextIO, location: concat.astutils.Location) -> str:
file.seek(0, io.SEEK_SET)
lines = [*file]
return lines[location[0] - 1]


def create_parsing_failure_message(
file: TextIO,
stream: Sequence[concat.lex.Token],
failure: concat.parser_combinators.FailureTree,
) -> str:
location = stream[failure.furthest_index].start
line = get_line_at(file, location)
message = f'Expected {failure.expected} at line {location[0]}, column {location[1] + 1}:\n{line.rstrip()}\n{" " * location[1] + "^"}'
if failure.children:
message += '\nbecause:'
for f in failure.children:
message += '\n' + textwrap.indent(
create_parsing_failure_message(file, stream, f), ' '
)
return message


arg_parser = argparse.ArgumentParser(description='Run a Concat program.')
arg_parser.add_argument(
'file',
Expand All @@ -68,6 +43,12 @@ def create_parsing_failure_message(
default=False,
help='turn stack debugging on',
)
arg_parser.add_argument(
'--verbose',
action='store_true',
default=False,
help='print internal logs and errors',
)
arg_parser.add_argument(
'--tokenize',
action='store_true',
Expand Down Expand Up @@ -103,11 +84,21 @@ def create_parsing_failure_message(
typecheck(concat_ast, source_dir)
python_ast = transpile_ast(concat_ast)
except concat.typecheck.StaticAnalysisError as e:
print('Static Analysis Error:\n')
if e.path is None:
in_path = ''
else:
in_path = ' in file ' + str(e.path)
print(f'Static Analysis Error{in_path}:\n')
print(e, 'in line:')
if e.location:
print(get_line_at(args.file, e.location), end='')
if e.path is not None:
with e.path.open() as f:
print(get_line_at(f, e.location), end='')
else:
print(get_line_at(args.file, e.location), end='')
print(' ' * e.location[1] + '^')
if args.verbose:
raise
except concat.parser_combinators.ParseError as e:
print('Parse Error:')
print(
Expand Down
28 changes: 28 additions & 0 deletions concat/error_reporting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import concat.astutils
import concat.parser_combinators
import io
import textwrap
from typing import Sequence, TextIO


def get_line_at(file: TextIO, location: concat.astutils.Location) -> str:
file.seek(0, io.SEEK_SET)
lines = [*file]
return lines[location[0] - 1]


def create_parsing_failure_message(
file: TextIO,
stream: Sequence[concat.lex.Token],
failure: concat.parser_combinators.FailureTree,
) -> str:
location = stream[failure.furthest_index].start
line = get_line_at(file, location)
message = f'Expected {failure.expected} at line {location[0]}, column {location[1] + 1}:\n{line.rstrip()}\n{" " * location[1] + "^"}'
if failure.children:
message += '\nbecause:'
for f in failure.children:
message += '\n' + textwrap.indent(
create_parsing_failure_message(file, stream, f), ' '
)
return message
8 changes: 6 additions & 2 deletions concat/examples/continuation.cat
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ from concat.stdlib.continuation import ContinuationMonad
from concat.stdlib.continuation import call_with_current_continuation
from concat.stdlib.continuation import eval_cont
from concat.stdlib.continuation import cont_pure
from concat.stdlib.continuation import map_cont
from concat.stdlib.continuation import bind_cont
from concat.stdlib.continuation import cont_from_cps
from concat.stdlib.pyinterop import to_dict
# from concat.stdlib.execution import loop # FIXME: This line should cause an error when there's no @@types in that module


def abort(k:forall *s. (*s x:`a -- *s n:none) -- n:none):
Expand All @@ -21,10 +21,14 @@ def abort(k:forall *s. (*s x:`a -- *s n:none) -- n:none):
# arguments.


def ignore_int(i:int -- n:none):
drop None


def ten_times(k:forall *s. (*s i:int -- *s c:ContinuationMonad[none, int]) -- c:ContinuationMonad[none, none]):
# k
0 $(k:forall *s. (*s i:int -- *s c:ContinuationMonad[none, int]) n:int:
1 + dup pick call eval_cont drop dup 10 <
1 + dup pick call $:~ignore_int map_cont eval_cont drop dup 10 <
) loop drop drop
$:~abort cont_from_cps

Expand Down
3 changes: 2 additions & 1 deletion concat/examples/strstr.cat
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def simple_str(obj:object -- string:str):
None swap None swap to_str

def strstr(haystack:str needle:str -- index:int):
[(), None, None] None to_dict swap pick$.find py_call cast (int) nip
# FIXME: These casts should be unnecessary
[(), None, None] None to_dict swap pick cast (str) $.find py_call cast (int) nip

get_input get_input strstr simple_str put_output
1 change: 0 additions & 1 deletion concat/examples/symmetric_tree.cat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# Tree input format: [<left subtree>, <root>, <right subtree>]
# None is the empty tree

from builtins import eval
from concat.stdlib.pyinterop import getitem
from concat.stdlib.pyinterop import to_str
from concat.stdlib.pyinterop import to_dict
Expand Down
55 changes: 55 additions & 0 deletions concat/kind-poly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Kind Polymorphism

* Will probably want kind variables in the future
* Introduce a kind `Kind` at that point
* And kind aliases, too

Probably, the easiest migration is to take the individual variable syntax:

```
`t
```

And use it to mean item-kinded variables instead.

## Prior art

* [Adding kind-polymorphism to the Scala programming language](https://codesync.global/media/adding-kind-polymorphism-to-the-scala-programming-language/)
* [`AnyKind`](https://www.scala-lang.org/api/current/scala/AnyKind.html)

## Kinds

* (?) Item: the kind of types of stack items
* Just use Individual?
* I use kinds for arity checking too, so I think that would make it harder
* Needed to exclude Sequence from kind-polymorphic type parameters where a
Sequence is invalid, e.g:
* `def drop[t : Item](*s t -- *s)` (possible syntax)
* Individual: the kind of zero-arity types of values
* Generic: type constructors
* they have arities
* they always construct an individual type
* I should change this
* they can be the type of a value, e.g. polymorphic functions
* Sequence: stack types

### Subkinding

```
Item
/ \
Individual Generic


Sequence
```

## Syntax

* `def drop[t : Item](*s t -- *s)`
* First thing I thought of
* Looks more natural to me
* Similar to type parameter syntax for classes
* `def drop(forall (t : Item). (*s t -- *s)):`
* Uses existing forall syntax, but extended
* Opens the door to allowing any type syntax as a type annotation
2 changes: 2 additions & 0 deletions concat/lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ def _tokens(self) -> Iterator['Token']:
continue
elif tok.value == '$':
tok.type = 'DOLLARSIGN'
elif tok.value == '!':
tok.type = 'EXCLAMATIONMARK'
elif tok.value in {'def', 'import', 'from'}:
tok.type = tok.value.upper()
tok.is_keyword = True
Expand Down
116 changes: 116 additions & 0 deletions concat/linked_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing import (
Callable,
Iterable,
Iterator,
List,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)
from typing_extensions import Never

_T_co = TypeVar('_T_co', covariant=True)
_T = TypeVar('_T')


class LinkedList(Sequence[_T_co]):
def __init__(
self, _val: Optional[Tuple[_T_co, 'LinkedList[_T_co]']]
) -> None:
self._val = _val
self._length: Optional[int] = None

@classmethod
def from_iterable(cls, iterable: Iterable[_T_co]) -> 'LinkedList[_T_co]':
if isinstance(iterable, cls):
return iterable
l: LinkedList[_T_co] = cls(None)
head = l
for el in iterable:
next_node = cls(None)
l._val = (el, next_node)
l = next_node
return head

@overload
def __getitem__(self, i: int) -> _T_co:
pass

@overload
def __getitem__(self, i: slice) -> 'LinkedList[_T_co]':
pass

def __getitem__(
self, i: Union[slice, int]
) -> Union['LinkedList[_T_co]', _T_co]:
if isinstance(i, slice):
raise NotImplementedError
for _ in range(i):
if self._val is None:
raise IndexError
self = self._val[1]
if self._val is None:
raise IndexError
return self._val[0]

def __len__(self) -> int:
if self._length is None:
node = self
length = 0
while node._val is not None:
node = node._val[1]
length += 1
self._length = length
return self._length

def __add__(self, other: 'LinkedList[_T_co]') -> 'LinkedList[_T_co]':
if not isinstance(other, LinkedList):
return NotImplemented
for el in reversed(list(self)):
other = LinkedList((el, other))
return other

def filter(self, p: Callable[[_T_co], bool]) -> 'LinkedList[_T_co]':
if self._val is None:
return self
# FIXME: Stack safety
# TODO: Reuse as much of tail as possible
if p(self._val[0]):
tail = self._val[1].filter(p)
return LinkedList((self._val[0], tail))
return self._val[1].filter(p)

def _tails(self) -> 'List[LinkedList[_T_co]]':
res: List[LinkedList[_T_co]] = []
while self._val is not None:
res.append(self)
self = self._val[1]
return res

def __iter__(self) -> Iterator[_T_co]:
while self._val is not None:
yield self._val[0]
self = self._val[1]

def __str__(self) -> str:
return str(list(self))

def __repr__(self) -> str:
return f'LinkedList.from_iterable({list(self)!r})'

# "supertype defines the argument type as object"
def __eq__(self, other: object) -> bool:
if not isinstance(other, LinkedList):
return NotImplemented
if len(self) != len(other):
return False
for a, b in zip(self, other):
if a != b:
return False
return True


empty_list = LinkedList[Never](None)
Loading
Loading