[BUGFIX] Fix custom function lambda cycle detection (#1404)
Cycles detection did not take into account custom functions' variable usage. This is now fixed.
This commit is contained in:
parent
7ac525f875
commit
869fc0b836
4 changed files with 46 additions and 3 deletions
|
|
@ -66,6 +66,22 @@ class Script:
|
||||||
deps=deps + [dep.name],
|
deps=deps + [dep.name],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for custom_func in variable_dependency.custom_function_dependencies(
|
||||||
|
custom_function_definitions=self._functions
|
||||||
|
):
|
||||||
|
for dep in self._functions[custom_func.name].variables:
|
||||||
|
self._ensure_no_cycle(
|
||||||
|
name=variable_name,
|
||||||
|
dep=dep.name,
|
||||||
|
deps=deps + [custom_func.definition_name()],
|
||||||
|
definitions=self._variables,
|
||||||
|
)
|
||||||
|
self._traverse_variable_dependencies(
|
||||||
|
variable_name=variable_name,
|
||||||
|
variable_dependency=self._variables[dep.name],
|
||||||
|
deps=deps + [custom_func.definition_name(), dep.name],
|
||||||
|
)
|
||||||
|
|
||||||
def _ensure_no_variable_cycles(self, variables: Dict[str, SyntaxTree]):
|
def _ensure_no_variable_cycles(self, variables: Dict[str, SyntaxTree]):
|
||||||
for variable_name, variable_definition in variables.items():
|
for variable_name, variable_definition in variables.items():
|
||||||
self._traverse_variable_dependencies(
|
self._traverse_variable_dependencies(
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,14 @@ class NamedCustomFunction(NamedArgument, ABC):
|
||||||
class ParsedCustomFunction(NamedCustomFunction):
|
class ParsedCustomFunction(NamedCustomFunction):
|
||||||
num_input_args: int
|
num_input_args: int
|
||||||
|
|
||||||
|
def definition_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
The function definition name, including the %
|
||||||
|
"""
|
||||||
|
return f"%{self.name}"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FunctionType(NamedArgument, ABC):
|
class FunctionType(NamedArgument, ABC):
|
||||||
|
|
|
||||||
|
|
@ -160,9 +160,20 @@ class VariableDependency(ABC):
|
||||||
raise UNREACHABLE
|
raise UNREACHABLE
|
||||||
|
|
||||||
@final
|
@final
|
||||||
def _custom_function_dependencies(
|
def custom_function_dependencies(
|
||||||
self, custom_function_definitions: Dict[str, "VariableDependency"]
|
self, custom_function_definitions: Dict[str, "VariableDependency"]
|
||||||
) -> Set[ParsedCustomFunction]:
|
) -> Set[ParsedCustomFunction]:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
custom_function_definitions
|
||||||
|
Definition of all currently existing custom functions. Needed to check whether
|
||||||
|
a lambda function's input function is custom or not.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
All custom function dependencies
|
||||||
|
"""
|
||||||
custom_functions = self.custom_functions
|
custom_functions = self.custom_functions
|
||||||
for lambda_func in self.lambdas:
|
for lambda_func in self.lambdas:
|
||||||
if lambda_func.value in custom_function_definitions:
|
if lambda_func.value in custom_function_definitions:
|
||||||
|
|
@ -185,7 +196,7 @@ class VariableDependency(ABC):
|
||||||
True if it contains all input variables as a dependency. False otherwise.
|
True if it contains all input variables as a dependency. False otherwise.
|
||||||
"""
|
"""
|
||||||
# If there are lambdas, see if they are custom functions. If so, check them
|
# If there are lambdas, see if they are custom functions. If so, check them
|
||||||
for custom_function in self._custom_function_dependencies(custom_function_definitions):
|
for custom_function in self.custom_function_dependencies(custom_function_definitions):
|
||||||
if not custom_function_definitions[custom_function.name].is_subset_of(
|
if not custom_function_definitions[custom_function.name].is_subset_of(
|
||||||
variables=variables, custom_function_definitions=custom_function_definitions
|
variables=variables, custom_function_definitions=custom_function_definitions
|
||||||
):
|
):
|
||||||
|
|
@ -205,7 +216,7 @@ class VariableDependency(ABC):
|
||||||
True if it contains any of the input variables. False otherwise.
|
True if it contains any of the input variables. False otherwise.
|
||||||
"""
|
"""
|
||||||
# If there are lambdas, see if they are custom functions. If so, check them
|
# If there are lambdas, see if they are custom functions. If so, check them
|
||||||
for custom_function in self._custom_function_dependencies(custom_function_definitions):
|
for custom_function in self.custom_function_dependencies(custom_function_definitions):
|
||||||
if custom_function_definitions[custom_function.name].contains(
|
if custom_function_definitions[custom_function.name].contains(
|
||||||
variables=variables, custom_function_definitions=custom_function_definitions
|
variables=variables, custom_function_definitions=custom_function_definitions
|
||||||
):
|
):
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from ytdl_sub.script.script import Script
|
||||||
from ytdl_sub.script.script_output import ScriptOutput
|
from ytdl_sub.script.script_output import ScriptOutput
|
||||||
from ytdl_sub.script.types.array import Array
|
from ytdl_sub.script.types.array import Array
|
||||||
from ytdl_sub.script.types.resolvable import Integer
|
from ytdl_sub.script.types.resolvable import Integer
|
||||||
|
from ytdl_sub.script.utils.exceptions import CycleDetected
|
||||||
from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
|
from ytdl_sub.script.utils.exceptions import IncompatibleFunctionArguments
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -169,3 +170,10 @@ class TestLambdaFunctionIncompatibleNumArguments:
|
||||||
"%output": f"{{%array_enumerate(array1, {lambda_value})}}",
|
"%output": f"{{%array_enumerate(array1, {lambda_value})}}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_lambda_with_custom_function_cycle(self):
|
||||||
|
with pytest.raises(
|
||||||
|
CycleDetected,
|
||||||
|
match=re.escape("Cycle detected within these variables: two -> %times_two -> two"),
|
||||||
|
):
|
||||||
|
Script({"%times_two": "{%mul($0, two)}", "two": "{%times_two(2)}"})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue