Skip to content

Commit

Permalink
Merge pull request #519 from dodona-edu/feature/property-assignment
Browse files Browse the repository at this point in the history
Add support for property/attribute assignment
  • Loading branch information
niknetniko authored Jun 3, 2024
2 parents 044f4db + 08850f3 commit 60b43a8
Show file tree
Hide file tree
Showing 19 changed files with 247 additions and 72 deletions.
50 changes: 33 additions & 17 deletions tested/dsl/ast_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@
NumberType,
ObjectKeyValuePair,
ObjectType,
PropertyAssignment,
SequenceType,
Statement,
Value,
VariableAssignment,
VariableType,
serialize_from_python,
)
Expand Down Expand Up @@ -95,7 +97,15 @@ def _is_type_cast(node: ast.expr) -> bool:

def _convert_ann_assignment(node: ast.AnnAssign) -> Assignment:
if not isinstance(node.target, ast.Name):
raise InvalidDslError("You can only assign to simple variables")
actual = ast.dump(node)
raise InvalidDslError(
f"""
You can only assign to simple variables when using type hints.
You are trying to assign to a target of type {type(node.target)}.
The full assignment is:
{actual}
"""
)
assert node.value
value = _convert_expression(node.value, False)
if isinstance(node.annotation, ast.Name):
Expand All @@ -110,40 +120,46 @@ def _convert_ann_assignment(node: ast.AnnAssign) -> Assignment:
if not is_our_type:
type_ = VariableType(data=type_)

return Assignment(
return VariableAssignment(
variable=node.target.id,
expression=value,
type=cast(VariableType | AllTypes, type_),
)


def _convert_assignment(node: ast.Assign) -> Assignment:
# raise InvalidDslError("You need to annotate the variable with a type.")
if n := len(node.targets) != 1:
raise InvalidDslError(
f"You must assign to exactly one variable, but got {n} variables."
)
variable = node.targets[0]
if not isinstance(variable, ast.Name):
actual = ast.dump(node)
raise InvalidDslError(f"You can only assign to simple variables, got: {actual}")
value = _convert_expression(node.value, False)

# Support a few obvious ones, such as constructor calls or literal values.
type_ = None
if isinstance(value, Value):
type_ = value.type
elif isinstance(value, FunctionCall) and value.type == FunctionType.CONSTRUCTOR:
type_ = VariableType(data=value.name)
if isinstance(variable, ast.Name):
# Support a few obvious ones, such as constructor calls or literal values.
type_ = None
if isinstance(value, Value):
type_ = value.type
elif isinstance(value, FunctionCall) and value.type == FunctionType.CONSTRUCTOR:
type_ = VariableType(data=value.name)

if not type_:
raise InvalidDslError(
f"Could not deduce the type of variable {variable.id}: add a type annotation."
)

if not type_:
assert isinstance(type_, AllTypes | VariableType)
return VariableAssignment(variable=variable.id, expression=value, type=type_)
elif isinstance(variable, ast.Attribute):
property_access = _convert_expression(variable, False)
assert isinstance(property_access, FunctionCall)
return PropertyAssignment(property=property_access, expression=value)
else:
actual = ast.dump(node)
raise InvalidDslError(
f"Could not deduce the type of variable {variable.id}: add a type annotation."
f"You can only assign to simple variables or attributes, got: {actual}."
)

assert isinstance(type_, AllTypes | VariableType)
return Assignment(variable=variable.id, expression=value, type=type_)


def _convert_call(node: ast.Call) -> FunctionCall:
# We consider function calls that start with a capital to be constructors.
Expand Down
4 changes: 2 additions & 2 deletions tested/languages/bash/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
)
from tested.languages.utils import convert_unknown_type
from tested.serialisation import (
Assignment,
FunctionCall,
FunctionType,
Identifier,
Statement,
StringType,
Value,
VariableAssignment,
)
from tested.testsuite import MainInput

Expand Down Expand Up @@ -153,7 +153,7 @@ def convert_statement(statement: Statement) -> str:
return convert_function_call(statement, index_fun, [])
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, VariableAssignment):
result = f"local {statement.variable}="
if isinstance(statement.expression, FunctionCall):
result += f"$({convert_statement(statement.expression)})"
Expand Down
4 changes: 2 additions & 2 deletions tested/languages/c/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
)
from tested.languages.utils import convert_unknown_type, is_special_void_call
from tested.serialisation import (
Assignment,
Expression,
FunctionCall,
FunctionType,
Expand All @@ -29,6 +28,7 @@
Statement,
StringType,
Value,
VariableAssignment,
VariableType,
as_basic_type,
)
Expand Down Expand Up @@ -149,7 +149,7 @@ def convert_statement(statement: Statement, full=False) -> str:
return convert_function_call(statement)
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, VariableAssignment):
if full:
prefix = convert_declaration(statement.type) + " "
else:
Expand Down
14 changes: 10 additions & 4 deletions tested/languages/csharp/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,19 @@
)
from tested.languages.utils import convert_unknown_type, is_special_void_call
from tested.serialisation import (
Assignment,
Expression,
FunctionCall,
FunctionType,
Identifier,
NamedArgument,
ObjectType,
PropertyAssignment,
SequenceType,
SpecialNumbers,
Statement,
StringType,
Value,
VariableAssignment,
VariableType,
as_basic_type,
)
Expand Down Expand Up @@ -245,13 +246,18 @@ def convert_statement(statement: Statement, full=False) -> str:
return convert_function_call(statement)
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, PropertyAssignment):
return (
f"{convert_statement(statement.property)} = "
f"{convert_statement(statement.expression)};"
)
elif isinstance(statement, VariableAssignment):
if full:
prefix = convert_declaration(statement.type, statement.expression)
else:
prefix = ""
return (
f"{prefix}{statement.variable} = "
f"{prefix} {statement.variable} = "
f"{convert_statement(statement.expression)}"
)
raise AssertionError(f"Unknown statement: {statement!r}")
Expand All @@ -269,7 +275,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit)
if (
not tc.testcase.is_main_testcase()
and isinstance(tc.input, PreparedTestcaseStatement)
and isinstance(tc.input.statement, Assignment)
and isinstance(tc.input.statement, VariableAssignment)
):
result += (
convert_declaration(
Expand Down
5 changes: 3 additions & 2 deletions tested/languages/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
prepare_expression,
)
from tested.parsing import get_converter
from tested.serialisation import Assignment, Expression, Statement, VariableType
from tested.serialisation import Expression, Statement, VariableType
from tested.testsuite import (
Context,
FileUrl,
Expand All @@ -39,6 +39,7 @@
Testcase,
TextData,
)
from tested.utils import is_statement_strict

if TYPE_CHECKING:
from tested.judge.planning import PlannedExecutionUnit
Expand Down Expand Up @@ -232,7 +233,7 @@ def generate_statement(bundle: Bundle, statement: Statement) -> str:
if isinstance(statement, Expression):
statement = prepare_expression(bundle, statement)
else:
assert isinstance(statement, Assignment)
assert is_statement_strict(statement)
statement = prepare_assignment(bundle, statement)

return bundle.language.generate_statement(statement)
Expand Down
4 changes: 2 additions & 2 deletions tested/languages/haskell/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
)
from tested.languages.utils import convert_unknown_type
from tested.serialisation import (
Assignment,
Expression,
FunctionCall,
Identifier,
Expand All @@ -30,6 +29,7 @@
Statement,
StringType,
Value,
VariableAssignment,
VariableType,
as_basic_type,
)
Expand Down Expand Up @@ -161,7 +161,7 @@ def convert_statement(statement: Statement, lifting=False) -> str:
result += ")"
return result
else:
assert isinstance(statement, Assignment)
assert isinstance(statement, VariableAssignment)
return f"let {statement.variable} = {convert_statement(statement.expression)}"


Expand Down
14 changes: 10 additions & 4 deletions tested/languages/java/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@
)
from tested.languages.utils import convert_unknown_type, is_special_void_call
from tested.serialisation import (
Assignment,
Expression,
FunctionCall,
FunctionType,
Identifier,
ObjectType,
PropertyAssignment,
SequenceType,
SpecialNumbers,
Statement,
StringType,
Value,
VariableAssignment,
VariableType,
as_basic_type,
)
Expand Down Expand Up @@ -239,7 +240,12 @@ def convert_statement(statement: Statement, full=False) -> str:
return convert_function_call(statement)
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, PropertyAssignment):
return (
f"{convert_statement(statement.property)} = "
f"{convert_statement(statement.expression)}"
)
elif isinstance(statement, VariableAssignment):
if full:
prefix = convert_declaration(statement.type, statement.expression) + " "
else:
Expand All @@ -259,12 +265,12 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit)
for tc in ctx.testcases:
result += "this.writeSeparator();\n"

# In Java, we need special code to make variables available outside of
# In Java, we need special code to make variables available outside
# the try-catch block.
if (
not tc.testcase.is_main_testcase()
and isinstance(tc.input, PreparedTestcaseStatement)
and isinstance(tc.input.statement, Assignment)
and isinstance(tc.input.statement, VariableAssignment)
):
result += (
convert_declaration(
Expand Down
12 changes: 9 additions & 3 deletions tested/languages/javascript/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@
)
from tested.languages.utils import convert_unknown_type
from tested.serialisation import (
Assignment,
Expression,
FunctionCall,
FunctionType,
Identifier,
ObjectType,
PropertyAssignment,
SequenceType,
SpecialNumbers,
Statement,
StringType,
Value,
VariableAssignment,
as_basic_type,
)
from tested.testsuite import MainInput
Expand Down Expand Up @@ -129,7 +130,12 @@ def convert_statement(statement: Statement, internal=False, full=False) -> str:
return convert_function_call(statement, internal)
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, PropertyAssignment):
return (
f"{convert_statement(statement.property, True)} = "
f"{convert_statement(statement.expression, True)}"
)
elif isinstance(statement, VariableAssignment):
if full:
prefix = "let "
else:
Expand Down Expand Up @@ -172,7 +178,7 @@ def _generate_internal_context(ctx: PreparedContext, pu: PreparedExecutionUnit)
if (
not tc.testcase.is_main_testcase()
and isinstance(tc.input, PreparedTestcaseStatement)
and isinstance(tc.input.statement, Assignment)
and isinstance(tc.input.statement, VariableAssignment)
):
result += f"let {tc.input.statement.variable}\n"

Expand Down
11 changes: 9 additions & 2 deletions tested/languages/kotlin/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@
Identifier,
NamedArgument,
ObjectType,
PropertyAssignment,
SequenceType,
SpecialNumbers,
Statement,
StringType,
Value,
VariableAssignment,
VariableType,
as_basic_type,
)
Expand Down Expand Up @@ -244,7 +246,12 @@ def convert_statement(statement: Statement, full=False) -> str:
return convert_function_call(statement)
elif isinstance(statement, Value):
return convert_value(statement)
elif isinstance(statement, Assignment):
elif isinstance(statement, PropertyAssignment):
return (
f"{convert_statement(statement.property)} = "
f"{convert_statement(statement.expression)};"
)
elif isinstance(statement, VariableAssignment):
prefix = "var " if full else ""
return (
f"{prefix}{statement.variable} = "
Expand Down Expand Up @@ -320,7 +327,7 @@ class {pu.unit.name}: AutoCloseable {{
if (
not tc.testcase.is_main_testcase()
and isinstance(tc.input, PreparedTestcaseStatement)
and isinstance(tc.input.statement, Assignment)
and isinstance(tc.input.statement, VariableAssignment)
):
decl = convert_declaration(
tc.input.statement.type, tc.input.statement.expression
Expand Down
Loading

0 comments on commit 60b43a8

Please sign in to comment.