Skip to content

Commit 9ce717a

Browse files
committed
Forbids type reuse, closes #219
1 parent 02632ca commit 9ce717a

File tree

5 files changed

+84
-39
lines changed

5 files changed

+84
-39
lines changed

classes/contrib/mypy/features/typeclass.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,19 @@ def typeclass_def_return_type(ctx: MethodContext) -> MypyType:
105105
assert isinstance(ctx.default_return_type, Instance)
106106
assert isinstance(ctx.context, Decorator)
107107

108+
instance_args.mutate_typeclass_def(
109+
ctx.default_return_type,
110+
ctx.context.func.fullname,
111+
ctx,
112+
)
113+
108114
if isinstance(ctx.default_return_type.args[2], Instance):
109115
validate_associated_type.check_type(
110116
associated_type=ctx.default_return_type.args[2],
117+
typeclass=ctx.default_return_type,
111118
ctx=ctx,
112119
)
113120

114-
instance_args.mutate_typeclass_def(
115-
ctx.default_return_type,
116-
ctx.context.func.fullname,
117-
ctx,
118-
)
119121
return ctx.default_return_type
120122

121123

classes/contrib/mypy/validation/validate_associated_type.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@
1010
'Single direct subclass of "{0}" required; got "{1}"'
1111
)
1212

13+
_TYPE_REUSE_MSG: Final = (
14+
'AssociatedType "{0}" must not be reused, originally associated with "{1}"'
15+
)
16+
1317

1418
def check_type(
1519
associated_type: Instance,
20+
typeclass: Instance,
1621
ctx: MethodContext,
1722
) -> bool:
1823
"""
@@ -24,7 +29,7 @@ def check_type(
2429
"""
2530
return all([
2631
_check_base_class(associated_type, ctx),
27-
# TODO: check_type_reuse
32+
_check_type_reuse(associated_type, typeclass, ctx),
2833
# TODO: check_body
2934
# TODO: check_generics_match_definition
3035
# TODO: we also need to check type vars used on definition:
@@ -50,3 +55,26 @@ def _check_base_class(
5055
ctx.context,
5156
)
5257
return has_correct_base
58+
59+
60+
def _check_type_reuse(
61+
associated_type: Instance,
62+
typeclass: Instance,
63+
ctx: MethodContext,
64+
) -> bool:
65+
fullname = getattr(typeclass.args[3], 'value', None)
66+
metadata = associated_type.type.metadata.setdefault('classes', {})
67+
68+
has_reuse = (
69+
fullname is not None and
70+
'typeclass' in metadata and
71+
metadata['typeclass'] != fullname
72+
)
73+
if has_reuse:
74+
ctx.api.fail(
75+
_TYPE_REUSE_MSG.format(associated_type.type.fullname, fullname),
76+
ctx.context,
77+
)
78+
79+
metadata['typeclass'] = fullname
80+
return has_reuse
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
- case: typeclass_definied_by_wrong_type
2+
disable_cache: false
3+
main: |
4+
from classes import typeclass
5+
6+
class ToJson(object):
7+
...
8+
9+
@typeclass(ToJson)
10+
def to_json(instance, verbose: bool = False) -> str:
11+
...
12+
out: |
13+
main:6: error: Single direct subclass of "classes._typeclass.AssociatedType" required; got "main.ToJson"
14+
15+
16+
- case: typeclass_definied_by_multiple_parents
17+
disable_cache: false
18+
main: |
19+
from classes import typeclass, AssociatedType
20+
21+
class A(object):
22+
...
23+
24+
class ToJson(AssociatedType, A):
25+
...
26+
27+
@typeclass(ToJson)
28+
def to_json(instance, verbose: bool = False) -> str:
29+
...
30+
out: |
31+
main:9: error: Single direct subclass of "classes._typeclass.AssociatedType" required; got "main.ToJson"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
- case: associated_type_reuse
2+
disable_cache: false
3+
main: |
4+
from classes import typeclass, AssociatedType
5+
6+
class ToJson(AssociatedType):
7+
...
8+
9+
@typeclass(ToJson)
10+
def to_json(instance) -> str:
11+
...
12+
13+
@typeclass(ToJson)
14+
def from_json(instance) -> str:
15+
...
16+
out: |
17+
main:10: error: AssociatedType "main.ToJson" must not be reused, originally associated with "main.from_json"

typesafety/test_typeclass/test_typeclass.yml

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,6 @@
5555
main:10: error: Instance callback is incompatible "def (instance: builtins.str) -> builtins.int"; expected "def (instance: builtins.str, verbose: builtins.bool =) -> builtins.str"
5656
5757
58-
- case: typeclass_definied_by_wrong_type
59-
disable_cache: false
60-
main: |
61-
from classes import typeclass
62-
63-
class ToJson(object):
64-
...
65-
66-
@typeclass(ToJson)
67-
def to_json(instance, verbose: bool = False) -> str:
68-
...
69-
out: |
70-
main:6: error: Single direct subclass of "classes._typeclass.AssociatedType" required; got "main.ToJson"
71-
72-
73-
- case: typeclass_definied_by_multiple_parents
74-
disable_cache: false
75-
main: |
76-
from classes import typeclass, AssociatedType
77-
78-
class A(object):
79-
...
80-
81-
class ToJson(AssociatedType, A):
82-
...
83-
84-
@typeclass(ToJson)
85-
def to_json(instance, verbose: bool = False) -> str:
86-
...
87-
out: |
88-
main:9: error: Single direct subclass of "classes._typeclass.AssociatedType" required; got "main.ToJson"
89-
90-
9158
- case: typeclass_definied_by_literal
9259
disable_cache: false
9360
main: |

0 commit comments

Comments
 (0)