Compare commits
1 Commits
da6e306fad
...
6231c6b279
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6231c6b279 |
@ -108,12 +108,16 @@ def expression(inp: ourlang.Expression) -> str:
|
|||||||
if isinstance(inp.function, ourlang.StructConstructor):
|
if isinstance(inp.function, ourlang.StructConstructor):
|
||||||
return f'{inp.function.struct_type3.name}({args})'
|
return f'{inp.function.struct_type3.name}({args})'
|
||||||
|
|
||||||
# TODO: Broken after new type system
|
|
||||||
# if isinstance(inp.function, ourlang.TupleConstructor):
|
|
||||||
# return f'({args}, )'
|
|
||||||
|
|
||||||
return f'{inp.function.name}({args})'
|
return f'{inp.function.name}({args})'
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.TupleInstantiation):
|
||||||
|
args = ', '.join(
|
||||||
|
expression(arg)
|
||||||
|
for arg in inp.elements
|
||||||
|
)
|
||||||
|
|
||||||
|
return f'({args}, )'
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Subscript):
|
if isinstance(inp, ourlang.Subscript):
|
||||||
varref = expression(inp.varref)
|
varref = expression(inp.varref)
|
||||||
index = expression(inp.index)
|
index = expression(inp.index)
|
||||||
|
|||||||
@ -80,6 +80,10 @@ def type3(inp: type3types.Type3OrPlaceholder) -> wasm.WasmType:
|
|||||||
# Static Arrays are passed as pointer, which are i32
|
# Static Arrays are passed as pointer, which are i32
|
||||||
return wasm.WasmTypeInt32()
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
|
if inp.base == type3types.tuple:
|
||||||
|
# Tuples are passed as pointer, which are i32
|
||||||
|
return wasm.WasmTypeInt32()
|
||||||
|
|
||||||
raise NotImplementedError(type3, inp)
|
raise NotImplementedError(type3, inp)
|
||||||
|
|
||||||
# Operators that work for i32, i64, f32, f64
|
# Operators that work for i32, i64, f32, f64
|
||||||
@ -150,6 +154,45 @@ F64_OPERATOR_MAP = {
|
|||||||
'/': 'div' # Division by zero is a trap and the program will panic
|
'/': 'div' # Division by zero is a trap and the program will panic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def tuple_instantiation(wgn: WasmGenerator, inp: ourlang.TupleInstantiation) -> None:
|
||||||
|
assert isinstance(inp.type3, type3types.AppliedType3)
|
||||||
|
assert inp.type3.base is type3types.tuple
|
||||||
|
assert len(inp.elements) == len(inp.type3.args)
|
||||||
|
|
||||||
|
comment_elements = ''
|
||||||
|
for element in inp.elements:
|
||||||
|
assert isinstance(element.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
comment_elements += f'{element.type3.name}, '
|
||||||
|
|
||||||
|
tmp_var = wgn.temp_var_i32('tuple_adr')
|
||||||
|
wgn.add_statement('nop', comment=f'tmp_var := ({comment_elements})')
|
||||||
|
|
||||||
|
# Allocated the required amounts of bytes in memory
|
||||||
|
wgn.i32.const(_calculate_alloc_size(inp.type3))
|
||||||
|
wgn.call(stdlib_alloc.__alloc__)
|
||||||
|
wgn.local.set(tmp_var)
|
||||||
|
|
||||||
|
# Store each element individually
|
||||||
|
offset = 0
|
||||||
|
for element, exp_type3 in zip(inp.elements, inp.type3.args):
|
||||||
|
if isinstance(exp_type3, type3types.PlaceholderForType):
|
||||||
|
assert exp_type3.resolve_as is not None
|
||||||
|
exp_type3 = exp_type3.resolve_as
|
||||||
|
|
||||||
|
assert element.type3 == exp_type3
|
||||||
|
|
||||||
|
assert isinstance(exp_type3, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||||
|
mtyp = LOAD_STORE_TYPE_MAP[exp_type3.name]
|
||||||
|
|
||||||
|
wgn.local.get(tmp_var)
|
||||||
|
expression(wgn, element)
|
||||||
|
wgn.add_statement(f'{mtyp}.store', 'offset=' + str(offset))
|
||||||
|
|
||||||
|
offset += _calculate_alloc_size(exp_type3)
|
||||||
|
|
||||||
|
# Return the allocated address
|
||||||
|
wgn.local.get(tmp_var)
|
||||||
|
|
||||||
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
||||||
"""
|
"""
|
||||||
Compile: Any expression
|
Compile: Any expression
|
||||||
@ -311,6 +354,10 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
wgn.add_statement('call', '${}'.format(inp.function.name))
|
wgn.add_statement('call', '${}'.format(inp.function.name))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if isinstance(inp, ourlang.TupleInstantiation):
|
||||||
|
tuple_instantiation(wgn, inp)
|
||||||
|
return
|
||||||
|
|
||||||
if isinstance(inp, ourlang.Subscript):
|
if isinstance(inp, ourlang.Subscript):
|
||||||
assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
assert isinstance(inp.varref.type3, type3types.Type3), type3types.TYPE3_ASSERTION_ERROR
|
||||||
|
|
||||||
@ -336,11 +383,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
wgn.i32.mul()
|
wgn.i32.mul()
|
||||||
wgn.i32.add()
|
wgn.i32.add()
|
||||||
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(el_type.name)
|
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||||
if mtyp is None:
|
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, el_type)
|
|
||||||
|
|
||||||
wgn.add_statement(f'{mtyp}.load')
|
wgn.add_statement(f'{mtyp}.load')
|
||||||
return
|
return
|
||||||
@ -360,11 +404,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
|
|
||||||
expression(wgn, inp.varref)
|
expression(wgn, inp.varref)
|
||||||
|
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(el_type.name)
|
assert isinstance(el_type, type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||||
if mtyp is None:
|
mtyp = LOAD_STORE_TYPE_MAP[el_type.name]
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, el_type)
|
|
||||||
|
|
||||||
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
|
wgn.add_statement(f'{mtyp}.load', f'offset={offset}')
|
||||||
return
|
return
|
||||||
@ -372,11 +413,8 @@ def expression(wgn: WasmGenerator, inp: ourlang.Expression) -> None:
|
|||||||
raise NotImplementedError(expression, inp, inp.varref.type3)
|
raise NotImplementedError(expression, inp, inp.varref.type3)
|
||||||
|
|
||||||
if isinstance(inp, ourlang.AccessStructMember):
|
if isinstance(inp, ourlang.AccessStructMember):
|
||||||
mtyp = LOAD_STORE_TYPE_MAP.get(inp.struct_type3.members[inp.member].name)
|
assert isinstance(inp.struct_type3.members[inp.member], type3types.PrimitiveType3), NotImplementedError('Tuple of applied types / structs')
|
||||||
if mtyp is None:
|
mtyp = LOAD_STORE_TYPE_MAP[inp.struct_type3.members[inp.member].name]
|
||||||
# In the future might extend this by having structs or tuples
|
|
||||||
# as members of struct or tuples
|
|
||||||
raise NotImplementedError(expression, inp, inp.struct_type3)
|
|
||||||
|
|
||||||
expression(wgn, inp.varref)
|
expression(wgn, inp.varref)
|
||||||
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
|
wgn.add_statement(f'{mtyp}.load', 'offset=' + str(_calculate_member_offset(
|
||||||
@ -538,9 +576,7 @@ def function(inp: ourlang.Function) -> wasm.Function:
|
|||||||
|
|
||||||
wgn = WasmGenerator()
|
wgn = WasmGenerator()
|
||||||
|
|
||||||
if False: # TODO: isinstance(inp, ourlang.TupleConstructor):
|
if isinstance(inp, ourlang.StructConstructor):
|
||||||
pass # _generate_tuple_constructor(wgn, inp)
|
|
||||||
elif isinstance(inp, ourlang.StructConstructor):
|
|
||||||
_generate_struct_constructor(wgn, inp)
|
_generate_struct_constructor(wgn, inp)
|
||||||
else:
|
else:
|
||||||
for stat in inp.statements:
|
for stat in inp.statements:
|
||||||
@ -706,30 +742,6 @@ def module(inp: ourlang.Module) -> wasm.Module:
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# TODO: Broken after new type system
|
|
||||||
# def _generate_tuple_constructor(wgn: WasmGenerator, inp: ourlang.TupleConstructor) -> None:
|
|
||||||
# tmp_var = wgn.temp_var_i32('tuple_adr')
|
|
||||||
#
|
|
||||||
# # Allocated the required amounts of bytes in memory
|
|
||||||
# wgn.i32.const(inp.tuple.alloc_size())
|
|
||||||
# wgn.call(stdlib_alloc.__alloc__)
|
|
||||||
# wgn.local.set(tmp_var)
|
|
||||||
#
|
|
||||||
# # Store each member individually
|
|
||||||
# for member in inp.tuple.members:
|
|
||||||
# mtyp = LOAD_STORE_TYPE_MAP.get(member.type.__class__)
|
|
||||||
# if mtyp is None:
|
|
||||||
# # In the future might extend this by having structs or tuples
|
|
||||||
# # as members of struct or tuples
|
|
||||||
# raise NotImplementedError(expression, inp, member)
|
|
||||||
#
|
|
||||||
# wgn.local.get(tmp_var)
|
|
||||||
# wgn.add_statement('local.get', f'$arg{member.idx}')
|
|
||||||
# wgn.add_statement(f'{mtyp}.store', 'offset=' + str(member.offset))
|
|
||||||
#
|
|
||||||
# # Return the allocated address
|
|
||||||
# wgn.local.get(tmp_var)
|
|
||||||
|
|
||||||
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
def _generate_struct_constructor(wgn: WasmGenerator, inp: ourlang.StructConstructor) -> None:
|
||||||
tmp_var = wgn.temp_var_i32('struct_adr')
|
tmp_var = wgn.temp_var_i32('struct_adr')
|
||||||
|
|
||||||
@ -771,6 +783,17 @@ def _calculate_alloc_size(typ: Union[type3types.StructType3, type3types.Type3])
|
|||||||
for x in typ.members.values()
|
for x in typ.members.values()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(typ, type3types.AppliedType3):
|
||||||
|
if typ.base is type3types.tuple:
|
||||||
|
size = 0
|
||||||
|
for arg in typ.args:
|
||||||
|
if isinstance(arg, type3types.PlaceholderForType):
|
||||||
|
assert not arg.resolve_as is None
|
||||||
|
arg = arg.resolve_as
|
||||||
|
size += _calculate_alloc_size(arg)
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
raise NotImplementedError(_calculate_alloc_size, typ)
|
raise NotImplementedError(_calculate_alloc_size, typ)
|
||||||
|
|
||||||
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
def _calculate_member_offset(struct_type3: type3types.StructType3, member: str) -> int:
|
||||||
|
|||||||
@ -142,14 +142,14 @@ class TupleInstantiation(Expression):
|
|||||||
"""
|
"""
|
||||||
Instantiation a tuple
|
Instantiation a tuple
|
||||||
"""
|
"""
|
||||||
__slots__ = ('args', )
|
__slots__ = ('elements', )
|
||||||
|
|
||||||
args: List[Expression]
|
elements: List[Expression]
|
||||||
|
|
||||||
def __init__(self, args: List[Expression]) -> None:
|
def __init__(self, elements: List[Expression]) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.args = args
|
self.elements = elements
|
||||||
|
|
||||||
class Subscript(Expression):
|
class Subscript(Expression):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -60,14 +60,10 @@ class ConstraintBase:
|
|||||||
def __init__(self, comment: Optional[str] = None) -> None:
|
def __init__(self, comment: Optional[str] = None) -> None:
|
||||||
self.comment = comment
|
self.comment = comment
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
"""
|
"""
|
||||||
Checks if the constraint hold
|
Checks if the constraint hold
|
||||||
|
|
||||||
smap will contain a mapping from placeholders to types, for
|
|
||||||
placeholders discovered from either other constraints or from
|
|
||||||
earlier calls to check in this constraint.
|
|
||||||
|
|
||||||
This function can return an error, if the constraint does not hold,
|
This function can return an error, if the constraint does not hold,
|
||||||
which indicates an error in the typing of the input program.
|
which indicates an error in the typing of the input program.
|
||||||
|
|
||||||
@ -103,7 +99,7 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
assert len(type_list) > 1
|
assert len(type_list) > 1
|
||||||
self.type_list = [*type_list]
|
self.type_list = [*type_list]
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
known_types: List[types.Type3] = []
|
known_types: List[types.Type3] = []
|
||||||
placeholders = []
|
placeholders = []
|
||||||
do_applied_placeholder_check: bool = False
|
do_applied_placeholder_check: bool = False
|
||||||
@ -118,8 +114,8 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(typ, types.PlaceholderForType):
|
if isinstance(typ, types.PlaceholderForType):
|
||||||
if typ in smap:
|
if typ.resolve_as is not None:
|
||||||
known_types.append(smap[typ])
|
known_types.append(typ.resolve_as)
|
||||||
else:
|
else:
|
||||||
placeholders.append(typ)
|
placeholders.append(typ)
|
||||||
continue
|
continue
|
||||||
@ -159,6 +155,9 @@ class SameTypeConstraint(ConstraintBase):
|
|||||||
if not placeholders:
|
if not placeholders:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
for typ in placeholders:
|
||||||
|
typ.resolve_as = first_type
|
||||||
|
|
||||||
return {
|
return {
|
||||||
typ: first_type
|
typ: first_type
|
||||||
for typ in placeholders
|
for typ in placeholders
|
||||||
@ -193,14 +192,14 @@ class CastableConstraint(ConstraintBase):
|
|||||||
self.from_type3 = from_type3
|
self.from_type3 = from_type3
|
||||||
self.to_type3 = to_type3
|
self.to_type3 = to_type3
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
ftyp = self.from_type3
|
ftyp = self.from_type3
|
||||||
if isinstance(ftyp, types.PlaceholderForType) and ftyp in smap:
|
if isinstance(ftyp, types.PlaceholderForType) and ftyp.resolve_as is not None:
|
||||||
ftyp = smap[ftyp]
|
ftyp = ftyp.resolve_as
|
||||||
|
|
||||||
ttyp = self.to_type3
|
ttyp = self.to_type3
|
||||||
if isinstance(ttyp, types.PlaceholderForType) and ttyp in smap:
|
if isinstance(ttyp, types.PlaceholderForType) and ttyp.resolve_as is not None:
|
||||||
ttyp = smap[ttyp]
|
ttyp = ttyp.resolve_as
|
||||||
|
|
||||||
if isinstance(ftyp, types.PlaceholderForType) or isinstance(ttyp, types.PlaceholderForType):
|
if isinstance(ftyp, types.PlaceholderForType) or isinstance(ttyp, types.PlaceholderForType):
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
@ -248,10 +247,10 @@ class MustImplementTypeClassConstraint(ConstraintBase):
|
|||||||
self.type_class3 = type_class3
|
self.type_class3 = type_class3
|
||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
typ = self.type3
|
typ = self.type3
|
||||||
if isinstance(typ, types.PlaceholderForType) and typ in smap:
|
if isinstance(typ, types.PlaceholderForType) and typ.resolve_as is not None:
|
||||||
typ = smap[typ]
|
typ = typ.resolve_as
|
||||||
|
|
||||||
if isinstance(typ, types.PlaceholderForType):
|
if isinstance(typ, types.PlaceholderForType):
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
@ -293,7 +292,7 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
self.type3 = type3
|
self.type3 = type3
|
||||||
self.literal = literal
|
self.literal = literal
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
int_table: Dict[str, Tuple[int, bool]] = {
|
int_table: Dict[str, Tuple[int, bool]] = {
|
||||||
'u8': (1, False),
|
'u8': (1, False),
|
||||||
'u32': (4, False),
|
'u32': (4, False),
|
||||||
@ -309,10 +308,10 @@ class LiteralFitsConstraint(ConstraintBase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isinstance(self.type3, types.PlaceholderForType):
|
if isinstance(self.type3, types.PlaceholderForType):
|
||||||
if self.type3 not in smap:
|
if self.type3.resolve_as is None:
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
self.type3 = smap[self.type3]
|
self.type3 = self.type3.resolve_as
|
||||||
|
|
||||||
if self.type3.name in int_table:
|
if self.type3.name in int_table:
|
||||||
bts, sgn = int_table[self.type3.name]
|
bts, sgn = int_table[self.type3.name]
|
||||||
@ -440,12 +439,12 @@ class CanBeSubscriptedConstraint(ConstraintBase):
|
|||||||
self.index = index
|
self.index = index
|
||||||
self.index_type3 = index.type3
|
self.index_type3 = index.type3
|
||||||
|
|
||||||
def check(self, smap: SubstitutionMap) -> CheckResult:
|
def check(self) -> CheckResult:
|
||||||
if isinstance(self.type3, types.PlaceholderForType):
|
if isinstance(self.type3, types.PlaceholderForType):
|
||||||
if self.type3 not in smap:
|
if self.type3.resolve_as is None:
|
||||||
return RequireTypeSubstitutes()
|
return RequireTypeSubstitutes()
|
||||||
|
|
||||||
self.type3 = smap[self.type3]
|
self.type3 = self.type3.resolve_as
|
||||||
|
|
||||||
if isinstance(self.type3, types.AppliedType3):
|
if isinstance(self.type3, types.AppliedType3):
|
||||||
if self.type3.base == types.static_array:
|
if self.type3.base == types.static_array:
|
||||||
|
|||||||
@ -104,7 +104,7 @@ def expression(ctx: Context, inp: ourlang.Expression) -> Generator[ConstraintBas
|
|||||||
|
|
||||||
if isinstance(inp, ourlang.TupleInstantiation):
|
if isinstance(inp, ourlang.TupleInstantiation):
|
||||||
r_type = []
|
r_type = []
|
||||||
for arg in inp.args:
|
for arg in inp.elements:
|
||||||
yield from expression(ctx, arg)
|
yield from expression(ctx, arg)
|
||||||
r_type.append(arg.type3)
|
r_type.append(arg.type3)
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Entry point to the type3 system
|
Entry point to the type3 system
|
||||||
"""
|
"""
|
||||||
from typing import Dict, List
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
from .. import codestyle
|
from .. import codestyle
|
||||||
from .. import ourlang
|
from .. import ourlang
|
||||||
@ -35,7 +35,7 @@ def phasm_type3(inp: ourlang.Module, verbose: bool = False) -> None:
|
|||||||
|
|
||||||
new_constraint_list = []
|
new_constraint_list = []
|
||||||
for constraint in constraint_list:
|
for constraint in constraint_list:
|
||||||
check_result = constraint.check(placeholder_substitutes)
|
check_result = constraint.check()
|
||||||
if check_result is None:
|
if check_result is None:
|
||||||
if verbose:
|
if verbose:
|
||||||
print_constraint(placeholder_id_map, constraint)
|
print_constraint(placeholder_id_map, constraint)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Contains the final types for use in Phasm
|
|||||||
These are actual, instantiated types; not the abstract types that the
|
These are actual, instantiated types; not the abstract types that the
|
||||||
constraint generator works with.
|
constraint generator works with.
|
||||||
"""
|
"""
|
||||||
from typing import Any, Dict, Iterable, List, Protocol, Union
|
from typing import Any, Dict, Iterable, List, Optional, Protocol, Union
|
||||||
|
|
||||||
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
TYPE3_ASSERTION_ERROR = 'You must call phasm_type3 after calling phasm_parse before you can call any other method'
|
||||||
|
|
||||||
@ -73,12 +73,14 @@ class PlaceholderForType:
|
|||||||
"""
|
"""
|
||||||
A placeholder type, for when we don't know the final type yet
|
A placeholder type, for when we don't know the final type yet
|
||||||
"""
|
"""
|
||||||
__slots__ = ('update_on_substitution', )
|
__slots__ = ('update_on_substitution', 'resolve_as', )
|
||||||
|
|
||||||
update_on_substitution: List[ExpressionProtocol]
|
update_on_substitution: List[ExpressionProtocol]
|
||||||
|
resolve_as: Optional[Type3]
|
||||||
|
|
||||||
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
def __init__(self, update_on_substitution: Iterable[ExpressionProtocol]) -> None:
|
||||||
self.update_on_substitution = [*update_on_substitution]
|
self.update_on_substitution = [*update_on_substitution]
|
||||||
|
self.resolve_as = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
uos = ', '.join(repr(x) for x in self.update_on_substitution)
|
uos = ', '.join(repr(x) for x in self.update_on_substitution)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user