From 665ae6bd791a13772111044710a1594d75958240 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 27 May 2022 16:01:26 +0300 Subject: [PATCH 01/78] compile to empty typescript files --- hephaestus.py | 8 +- src/args.py | 2 +- src/compilers/typescript.py | 37 ++++ src/ir/__init__.py | 4 +- src/ir/ast.py | 21 +++ src/ir/typescript_types.py | 343 ++++++++++++++++++++++++++++++++++ src/translators/typescript.py | 26 +++ 7 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 src/compilers/typescript.py create mode 100644 src/ir/typescript_types.py create mode 100644 src/translators/typescript.py diff --git a/hephaestus.py b/hephaestus.py index 268704ff..d9d7f0e0 100755 --- a/hephaestus.py +++ b/hephaestus.py @@ -17,9 +17,11 @@ from src.compilers.kotlin import KotlinCompiler from src.compilers.groovy import GroovyCompiler from src.compilers.java import JavaCompiler +from src.compilers.typescript import TypeScriptCompiler from src.translators.kotlin import KotlinTranslator from src.translators.groovy import GroovyTranslator from src.translators.java import JavaTranslator +from src.translators.typescript import TypeScriptTranslator from src.modules.processor import ProgramProcessor @@ -27,12 +29,14 @@ TRANSLATORS = { 'kotlin': KotlinTranslator, 'groovy': GroovyTranslator, - 'java': JavaTranslator + 'java': JavaTranslator, + 'typescript' : TypeScriptTranslator } COMPILERS = { 'kotlin': KotlinCompiler, 'groovy': GroovyCompiler, - 'java': JavaCompiler + 'java': JavaCompiler, + 'typescript': TypeScriptCompiler } STATS = { "Info": { diff --git a/src/args.py b/src/args.py index 094f176f..20e603be 100644 --- a/src/args.py +++ b/src/args.py @@ -107,7 +107,7 @@ parser.add_argument( "--language", default="kotlin", - choices=['kotlin', 'groovy', 'java'], + choices=['kotlin', 'groovy', 'java', 'typescript'], help="Select specific language" ) parser.add_argument( diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py new file mode 100644 index 00000000..0f6ebcd0 --- /dev/null +++ b/src/compilers/typescript.py @@ -0,0 +1,37 @@ +from collections import defaultdict +import re +import os +from src.compilers.base import BaseCompiler + +class TypeScriptCompiler(BaseCompiler): + + ERROR_REGEX = re.compile(".*") + + CRASH_REGEX = re.compile("123") + + def __init__(self, input_name): + super().__init__(input_name) + self.crash_msg = None + + @classmethod + def get_compiler_version(cls): + return ['tsc', '-v'] + + def get_compiler_cmd(self): + return ['tsc', self.input_name] + + def analyze_compiler_output(self, output): + return defaultdict(list) + """ self.crashed = None + failed = defaultdict(list) + matches = re.findall(self.ERROR_REGEX, output) + for match in matches: + filename = match[0] + error_msg = match[1] + failed[filename].append(error_msg) + + crash_match = re.search(self.CRASH_REGEX, output) + if crash_match and not matches: + self.crash_msg = output + return None + return failed """ \ No newline at end of file diff --git a/src/ir/__init__.py b/src/ir/__init__.py index f87d4e09..a8273c8d 100644 --- a/src/ir/__init__.py +++ b/src/ir/__init__.py @@ -1,9 +1,11 @@ from src.ir.kotlin_types import KotlinBuiltinFactory from src.ir.groovy_types import GroovyBuiltinFactory from src.ir.java_types import JavaBuiltinFactory +from src.ir.typescript_types import TypeScriptBuiltinFactory BUILTIN_FACTORIES = { "kotlin": KotlinBuiltinFactory(), "groovy": GroovyBuiltinFactory(), - "java": JavaBuiltinFactory() + "java": JavaBuiltinFactory(), + "typescript": TypeScriptBuiltinFactory() } diff --git a/src/ir/ast.py b/src/ir/ast.py index f3cb43b3..3520e246 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -1076,6 +1076,10 @@ class LogicalExpr(BinaryOp): "java": [ Operator('&&'), Operator('||') + ], + "typescript": [ + Operator('&&'), + Operator('||') ] } @@ -1103,6 +1107,11 @@ class EqualityExpr(BinaryOp): "java": [ Operator('=='), Operator('=', is_not=True) + ], + "typescript": [ + Operator('==='), + Operator('==', is_not=True), + Operator('=', is_not=True) ] } @@ -1132,6 +1141,12 @@ class ComparisonExpr(BinaryOp): Operator('>='), Operator('<'), Operator('<=') + ], + "typescript": [ + Operator('>'), + Operator('>='), + Operator('<'), + Operator('<=') ] } @@ -1161,6 +1176,12 @@ class ArithExpr(BinaryOp): Operator('-'), Operator('/'), Operator('*') + ], + "typescript": [ + Operator('+'), + Operator('-'), + Operator('/'), + Operator('*') ] } diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py new file mode 100644 index 00000000..7dfe3d57 --- /dev/null +++ b/src/ir/typescript_types.py @@ -0,0 +1,343 @@ +from src.ir.types import Builtin + + +import src.ir.builtins as bt +import src.ir.types as tp + +class TypeScriptBuiltinFactory(bt.BuiltinFactory): + def get_language(self): + return "typescript" + + # ! All types below are java types. + # TODO: Change them to typescript one by one + + def get_builtin(self): + return JavaBuiltin + + def get_void_type(self): + return VoidType() + + def get_any_type(self): + return ObjectType() + + def get_number_type(self): + return NumberType() + + def get_integer_type(self): + return IntegerType(primitive=False) + + def get_byte_type(self): + return ByteType(primitive=False) + + def get_short_type(self): + return ShortType(primitive=False) + + def get_long_type(self): + return LongType(primitive=False) + + def get_float_type(self): + return FloatType(primitive=False) + + def get_double_type(self): + return DoubleType(primitive=False) + + def get_big_decimal_type(self): + return DoubleType(primitive=False) + + def get_boolean_type(self): + return BooleanType(primitive=False) + + def get_char_type(self): + return CharType(primitive=False) + + def get_string_type(self): + return StringType() + + def get_array_type(self): + return ArrayType() + + def get_big_integer_type(self): + return IntegerType(primitive=False) + + def get_function_type(self, nr_parameters=0): + return FunctionType(nr_parameters) + + +# ! All types below are java types. + +class JavaBuiltin(Builtin): + def __init__(self, name, primitive): + super().__init__(name) + self.primitive = primitive + + def __str__(self): + if not self.is_primitive(): + return str(self.name) + "(java-builtin)" + return str(self.name).lower() + "(java-primitive)" + + def is_primitive(self): + return self.primitive + + def box_type(self): + raise NotImplementedError('box_type() must be implemented') + + +class ObjectType(JavaBuiltin): + def __init__(self, name="Object"): + super().__init__(name, False) + + def get_builtin_type(self): + return bt.Any + + def box_type(self): + return self + + +class VoidType(JavaBuiltin): + def __init__(self, name="void", primitive=False): + super().__init__(name, primitive) + if not self.primitive: + self.supertypes.append(ObjectType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Void + + def box_type(self): + return VoidType(self.name, primitive=False) + + +class NumberType(ObjectType): + def __init__(self, name="Number"): + super().__init__(name) + self.supertypes.append(ObjectType()) + + def get_builtin_type(self): + return bt.Number + + def box_type(self): + return self + + +class IntegerType(NumberType): + def __init__(self, name="Integer", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Integer + + def box_type(self): + return IntegerType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, IntegerType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "int" + return super().get_name() + + +class ShortType(NumberType): + def __init__(self, name="Short", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Short + + def box_type(self): + return ShortType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, ShortType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "short" + return super().get_name() + + +class LongType(NumberType): + def __init__(self, name="Long", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Long + + def box_type(self): + return LongType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, LongType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "long" + return super().get_name() + + +class ByteType(NumberType): + def __init__(self, name="Byte", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Byte + + def box_type(self): + return ByteType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, ByteType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "byte" + return super().get_name() + + +class FloatType(NumberType): + def __init__(self, name="Float", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Float + + def box_type(self): + return FloatType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, FloatType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "float" + return super().get_name() + + +class DoubleType(NumberType): + def __init__(self, name="Double", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(NumberType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Double + + def box_type(self): + return DoubleType(self.name, primitive=False) + + def is_assignable(self, other): + assignable_types = (NumberType, DoubleType,) + return self.is_subtype(other) or type(other) in assignable_types + + def get_name(self): + if self.is_primitive(): + return "double" + return super().get_name() + + +class CharType(ObjectType): + def __init__(self, name="Character", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(ObjectType()) + else: + self.supertypes = set() + + def get_builtin_type(self): + return bt.Char + + def box_type(self): + return CharType(self.name, primitive=False) + + def get_name(self): + if self.is_primitive(): + return "char" + return super().get_name() + + +class StringType(ObjectType): + def __init__(self, name="String"): + super().__init__(name) + self.supertypes.append(ObjectType()) + + def get_builtin_type(self): + return bt.String + + def box_type(self): + return self + + +class BooleanType(ObjectType): + def __init__(self, name="Boolean", primitive=False): + super().__init__(name) + self.primitive = primitive + if not self.primitive: + self.supertypes.append(ObjectType()) + else: + self.supertypes = [] + + def get_builtin_type(self): + return bt.Boolean + + def box_type(self): + return BooleanType(self.name, primitive=False) + + def get_name(self): + if self.is_primitive(): + return "boolean" + return super().get_name() + + +class ArrayType(tp.TypeConstructor, ObjectType): + def __init__(self, name="Array"): + # In Java, arrays are covariant. + super().__init__(name, [tp.TypeParameter( + "T", variance=tp.Covariant)]) + self.supertypes.append(ObjectType()) + + +class FunctionType(tp.TypeConstructor): + def __init__(self, nr_type_parameters: int): + name = "Function" + str(nr_type_parameters) + type_parameters = [ + tp.TypeParameter("A" + str(i)) + for i in range(1, nr_type_parameters + 1) + ] + [tp.TypeParameter("R")] + self.nr_type_parameters = nr_type_parameters + super().__init__(name, type_parameters) \ No newline at end of file diff --git a/src/translators/typescript.py b/src/translators/typescript.py new file mode 100644 index 00000000..956d61a4 --- /dev/null +++ b/src/translators/typescript.py @@ -0,0 +1,26 @@ +import src.utils as ut +from src.ir import ast, types as tp, type_utils as tu +from src.ir.context import get_decl +from src.transformations.base import change_namespace +from src.translators.base import BaseTranslator + +class TypeScriptTranslator(BaseTranslator): + filename = "Main.ts" + incorrect_filename = "Incorrect.ts" + executable = "Main.js" + ident_value = " " + + def __init__(self, package=None, options={}): + super().__init__(package, options) + self._children_res = [] + self.ident = 0 + self.context = None + + def get_filename(self): + return self.filename; + + def visit_program(self, node): + return + + def result(self): + return ""; \ No newline at end of file From 9e705485352ca9f8899ef2411ca2a6fc6b55a4d3 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 31 May 2022 17:12:11 +0300 Subject: [PATCH 02/78] Add representation for typescript types and lowercase object type --- src/ir/typescript_types.py | 358 +++++++++++-------------------------- 1 file changed, 108 insertions(+), 250 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 7dfe3d57..8c343860 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -6,13 +6,10 @@ class TypeScriptBuiltinFactory(bt.BuiltinFactory): def get_language(self): - return "typescript" - - # ! All types below are java types. - # TODO: Change them to typescript one by one - + return "typescript" + def get_builtin(self): - return JavaBuiltin + return TypeScriptBuiltin def get_void_type(self): return VoidType() @@ -21,323 +18,184 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType() + return NumberType(primitive=False) + + def get_boolean_type(self): + return BooleanType(primitive=False) + + def get_char_type(self): + return SymbolType(primitive=False) + + def get_string_type(self): + return StringType(primitive=False) + + def get_big_integer_type(self): + return BigIntegerType(primitive=False) + + def get_array_type(self): + return ArrayType() + + def get_function_type(self, nr_parameters=0): + return FunctionType(nr_parameters) + + def get_object_type(self): + return ObjectLowercaseType() + + def get_primitive_types(self): + return [ + NumberType(primitive=True), + StringType(primitive=True), + SymbolType(primitive=True), + BooleanType(primitive=True), + BigIntegerType(primitive=True) + ] def get_integer_type(self): - return IntegerType(primitive=False) + return NumberType(primitive=False) def get_byte_type(self): - return ByteType(primitive=False) + return NumberType(primitive=False) def get_short_type(self): - return ShortType(primitive=False) + return NumberType(primitive=False) def get_long_type(self): - return LongType(primitive=False) + return NumberType(primitive=False) def get_float_type(self): - return FloatType(primitive=False) + return NumberType(primitive=False) def get_double_type(self): - return DoubleType(primitive=False) + return NumberType(primitive=False) def get_big_decimal_type(self): - return DoubleType(primitive=False) + return NumberType(primitive=False) - def get_boolean_type(self): - return BooleanType(primitive=False) - - def get_char_type(self): - return CharType(primitive=False) - def get_string_type(self): - return StringType() - - def get_array_type(self): - return ArrayType() - - def get_big_integer_type(self): - return IntegerType(primitive=False) - - def get_function_type(self, nr_parameters=0): - return FunctionType(nr_parameters) - - -# ! All types below are java types. - -class JavaBuiltin(Builtin): +class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): super().__init__(name) self.primitive = primitive - + def __str__(self): if not self.is_primitive(): - return str(self.name) + "(java-builtin)" - return str(self.name).lower() + "(java-primitive)" - + return str(self.name) + "(typescript-builtin)" + return str(self.name).lower() + "(typescript-primitive)" + def is_primitive(self): return self.primitive - def box_type(self): - raise NotImplementedError('box_type() must be implemented') - -class ObjectType(JavaBuiltin): +class ObjectType(TypeScriptBuiltin): def __init__(self, name="Object"): super().__init__(name, False) - def get_builtin_type(self): - return bt.Any - - def box_type(self): - return self - - -class VoidType(JavaBuiltin): - def __init__(self, name="void", primitive=False): - super().__init__(name, primitive) - if not self.primitive: - self.supertypes.append(ObjectType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Void - - def box_type(self): - return VoidType(self.name, primitive=False) - - -class NumberType(ObjectType): - def __init__(self, name="Number"): - super().__init__(name) +class ObjectLowercaseType(TypeScriptBuiltin): + def __init__(self, name="object"): + super().__init__(name, False) self.supertypes.append(ObjectType()) - def get_builtin_type(self): - return bt.Number - - def box_type(self): - return self - - -class IntegerType(NumberType): - def __init__(self, name="Integer", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Integer - - def box_type(self): - return IntegerType(self.name, primitive=False) - def is_assignable(self, other): - assignable_types = (NumberType, IntegerType,) - return self.is_subtype(other) or type(other) in assignable_types - - def get_name(self): - if self.is_primitive(): - return "int" - return super().get_name() - - -class ShortType(NumberType): - def __init__(self, name="Short", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Short +class VoidType(TypeScriptBuiltin): + def __init__(self, name="void"): + super().__init__(name, False) + self.supertypes.append(ObjectType()) - def box_type(self): - return ShortType(self.name, primitive=False) +class NumberType(TypeScriptBuiltin): + def __init__(self, name="Number", primitive=False): + super().__init__(name, primitive) + self.supertypes.append(ObjectType()) + def is_assignable(self, other): - assignable_types = (NumberType, ShortType,) - return self.is_subtype(other) or type(other) in assignable_types - - def get_name(self): - if self.is_primitive(): - return "short" - return super().get_name() - - -class LongType(NumberType): - def __init__(self, name="Long", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Long + return type(other) is NumberType def box_type(self): - return LongType(self.name, primitive=False) - - def is_assignable(self, other): - assignable_types = (NumberType, LongType,) - return self.is_subtype(other) or type(other) in assignable_types - + return NumberType(self.name, primitive=False) + def get_name(self): - if self.is_primitive(): - return "long" + if self.is_primitive: + return "number" return super().get_name() - -class ByteType(NumberType): - def __init__(self, name="Byte", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Byte - - def box_type(self): - return ByteType(self.name, primitive=False) - +class BigIntegerType(TypeScriptBuiltin): + def __init__(self, name="BigInt", primitive=False): + super().__init__(name, primitive) + self.supertypes.append(ObjectType()) + def is_assignable(self, other): - assignable_types = (NumberType, ByteType,) + assignable_types= (NumberType, BigIntegerType) return self.is_subtype(other) or type(other) in assignable_types - - def get_name(self): - if self.is_primitive(): - return "byte" - return super().get_name() - - -class FloatType(NumberType): - def __init__(self, name="Float", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Float - + def box_type(self): - return FloatType(self.name, primitive=False) - - def is_assignable(self, other): - assignable_types = (NumberType, FloatType,) - return self.is_subtype(other) or type(other) in assignable_types - + return BigIntegerType(self.name, primitive=False) + def get_name(self): - if self.is_primitive(): - return "float" + if self.is_primitive: + return "bigint" return super().get_name() -class DoubleType(NumberType): - def __init__(self, name="Double", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(NumberType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Double - +class BooleanType(TypeScriptBuiltin): + def __init__(self, name="Boolean", primitive=False): + super().__init__(name, primitive) + self.supertypes.append(ObjectType()) + def box_type(self): - return DoubleType(self.name, primitive=False) - - def is_assignable(self, other): - assignable_types = (NumberType, DoubleType,) - return self.is_subtype(other) or type(other) in assignable_types - + return BooleanType(self.name, primitive=False) + def get_name(self): - if self.is_primitive(): - return "double" + if self.is_primitive: + return "boolean" return super().get_name() -class CharType(ObjectType): - def __init__(self, name="Character", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(ObjectType()) - else: - self.supertypes = set() - - def get_builtin_type(self): - return bt.Char - +class StringType(TypeScriptBuiltin): + def __init__(self, name="String", primitive=False): + super().__init__(name ,primitive) + self.supertypes.append(ObjectType()) + def box_type(self): - return CharType(self.name, primitive=False) - + return StringType(self.name, primitive=False) + def get_name(self): - if self.is_primitive(): - return "char" + if self.is_primitive: + return "string" return super().get_name() -class StringType(ObjectType): - def __init__(self, name="String"): - super().__init__(name) +class SymbolType(TypeScriptBuiltin): + def __init__(self, name="Symbol", primitive=False): + super().__init__(name, primitive) self.supertypes.append(ObjectType()) - def get_builtin_type(self): - return bt.String - - def box_type(self): - return self - - -class BooleanType(ObjectType): - def __init__(self, name="Boolean", primitive=False): - super().__init__(name) - self.primitive = primitive - if not self.primitive: - self.supertypes.append(ObjectType()) - else: - self.supertypes = [] - - def get_builtin_type(self): - return bt.Boolean - def box_type(self): - return BooleanType(self.name, primitive=False) + return SymbolType(self.name, primitive=False) def get_name(self): if self.is_primitive(): - return "boolean" + return "symbol" return super().get_name() class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): - # In Java, arrays are covariant. + # In TypeScript, arrays are covariant. super().__init__(name, [tp.TypeParameter( "T", variance=tp.Covariant)]) - self.supertypes.append(ObjectType()) -class FunctionType(tp.TypeConstructor): +class FunctionType(tp.TypeConstructor, ObjectType): def __init__(self, nr_type_parameters: int): name = "Function" + str(nr_type_parameters) + + # In Typescript, type parameters are covariant as to the return type + # and contravariant as to the arguments. + type_parameters = [ - tp.TypeParameter("A" + str(i)) + tp.TypeParameter("A" + str(i), tp.Contravariant) for i in range(1, nr_type_parameters + 1) - ] + [tp.TypeParameter("R")] + ] + [tp.TypeParameter("R", tp.Covariant)] self.nr_type_parameters = nr_type_parameters - super().__init__(name, type_parameters) \ No newline at end of file + super().__init__(name, type_parameters) + self.supertypes.append(ObjectType()) \ No newline at end of file From b4e430b28a0493b897f3ec6d32f4e97155b3c479 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 3 Jun 2022 17:45:30 +0300 Subject: [PATCH 03/78] delete dead code in Kotlin ClassDecleration visitor --- src/translators/kotlin.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/translators/kotlin.py b/src/translators/kotlin.py index beb0b1db..b99bdd35 100644 --- a/src/translators/kotlin.py +++ b/src/translators/kotlin.py @@ -169,10 +169,6 @@ def visit_class_decl(self, node): not is_sam) else "", p=class_prefix, n=node.name, - tps="<" + type_parameters_res + ">" if type_parameters_res else "", - fields="(" + ", ".join(field_res) + ")" if field_res else "", - s=": " + ", ".join(superclasses_res) if superclasses_res else "", - body=body ) if type_parameters_res: From cd0e2fff96bb059a7f59ee2a51510e4cd939924f Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 10 Jun 2022 11:52:11 +0300 Subject: [PATCH 04/78] remove dead kotlin translator code --- src/translators/kotlin.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/translators/kotlin.py b/src/translators/kotlin.py index b99bdd35..ddb8a6e7 100644 --- a/src/translators/kotlin.py +++ b/src/translators/kotlin.py @@ -154,12 +154,6 @@ def visit_class_decl(self, node): is_sam = tu.is_sam(self.context, cls_decl=node) class_prefix = "interface" if is_sam else node.get_class_prefix() - body = "" - if function_res: - body = " {{\n{function_res}\n{old_ident}}}".format( - function_res="\n\n".join(function_res), - old_ident=" " * old_ident - ) res = "{ident}{f}{o}{p} {n}".format( ident=" " * old_ident, From bc05ef276c2b7c6c8845b994a9423355d2367cbc Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 10 Jun 2022 11:53:00 +0300 Subject: [PATCH 05/78] Add argument validation for typescript, must use decline-use-site-variance flag --- src/args.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/args.py b/src/args.py index 20e603be..0a000597 100644 --- a/src/args.py +++ b/src/args.py @@ -236,6 +236,9 @@ def validate_args(args): if args.examine and not args.replay: sys.exit("You cannot use --examine option without the --replay option") + if args.language == "typescript" and not args.disable_use_site_variance: + sys.exit("\nTypeScript does not have use-site variance.\nRun Hephaestus again with the flag --disable-use-site-variance.") + def pre_process_args(args): # PRE-PROCESSING From 01245855283a48bed1e904b5e61b40204f10a2d4 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 20 Jul 2022 12:29:51 +0300 Subject: [PATCH 06/78] Change typescript equality operators --- src/ir/ast.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ir/ast.py b/src/ir/ast.py index 3520e246..a5d02a79 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -1110,8 +1110,7 @@ class EqualityExpr(BinaryOp): ], "typescript": [ Operator('==='), - Operator('==', is_not=True), - Operator('=', is_not=True) + Operator('==', is_not= True) ] } From 1cd0535a9ecc1c484797bf68a3fd5359dff1ddac Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 21 Jul 2022 19:54:06 +0300 Subject: [PATCH 07/78] Added most of typescript functionality. Changed generator so no default values in typescript params. --- src/compilers/typescript.py | 2 +- src/generators/generator.py | 7 +- src/ir/typescript_types.py | 2 +- src/translators/typescript.py | 638 +++++++++++++++++++++++++++++++++- 4 files changed, 635 insertions(+), 14 deletions(-) diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index 0f6ebcd0..73e3c54c 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -18,7 +18,7 @@ def get_compiler_version(cls): return ['tsc', '-v'] def get_compiler_cmd(self): - return ['tsc', self.input_name] + return ['tsc --target es2020', self.input_name] def analyze_compiler_output(self, output): return defaultdict(list) diff --git a/src/generators/generator.py b/src/generators/generator.py index 9ffc173d..976b87d1 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -273,7 +273,8 @@ def gen_func_decl(self, if ( ut.random.bool(prob=0.25) or self.language == 'java' or - self.language == 'groovy' and is_interface + self.language == 'groovy' and is_interface or + self.language == 'typescript' and is_interface ) else self._gen_func_params_with_default() ) @@ -1533,10 +1534,10 @@ def _gen_func_call(self, if not param.vararg: arg = self.generate_expr(expr_type, only_leaves, gen_bottom=gen_bottom) - if param.default: + if param.default and self.language != 'typescript': if self.language == 'kotlin' and ut.random.bool(): # Randomly skip some default arguments. - args.append(ast.CallArgument(arg, name=param.name)) + args.append(ast.CallArgument(arg, name=param.name)) else: args.append(ast.CallArgument(arg)) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 8c343860..ba67621c 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -124,7 +124,7 @@ def __init__(self, name="BigInt", primitive=False): self.supertypes.append(ObjectType()) def is_assignable(self, other): - assignable_types= (NumberType, BigIntegerType) + assignable_types= [BigIntegerType] return self.is_subtype(other) or type(other) in assignable_types def box_type(self): diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 956d61a4..c93fb35b 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -1,9 +1,18 @@ -import src.utils as ut -from src.ir import ast, types as tp, type_utils as tu -from src.ir.context import get_decl -from src.transformations.base import change_namespace +from ast import literal_eval +import enum +from gc import is_finalized +from shutil import register_archive_format +from src.ir import ast, typescript_types as tst, types as tp, type_utils as tu from src.translators.base import BaseTranslator +def append_to(visit): + def inner(self, node): + self._nodes_stack.append(node) + res = visit(self, node) + self._nodes_stack.pop() + return inner + + class TypeScriptTranslator(BaseTranslator): filename = "Main.ts" incorrect_filename = "Incorrect.ts" @@ -14,13 +23,624 @@ def __init__(self, package=None, options={}): super().__init__(package, options) self._children_res = [] self.ident = 0 + self.is_interface = False + self.is_void = False + self.is_lambda = False + self.current_class = None + self.current_function = None self.context = None + self._nodes_stack = [None] + + def _reset_state(self): + self._children_res = [] + self.ident = 0 + self.is_interface = False + self.is_void = False + self.is_lambda = False + self.current_class = None + self.current_function + self.context = None + self._nodes_stack = [None] + + @staticmethod + def get_filename(): + return TypeScriptTranslator.filename - def get_filename(self): - return self.filename; + @staticmethod + def get_incorrect_filename(): + return TypeScriptTranslator.incorrect_filename + + def type_arg2str(self, t_arg): + # TypeScript does not have a Wildcard type + a = self.get_type_name(t_arg) + return self.get_type_name(t_arg) + + def get_type_name(self, t): + t_constructor = getattr(t, 't_constructor', None) + if not t_constructor: + return t.get_name() + + if t_constructor.name.startswith("Function"): + param = ['p', 1] + res = "(" + + if len(t.type_args) == 1: + res += ") => " + self.type_arg2str(t.type_args[-1]) + return res + + for ta in t.type_args[:-1]: + res += param[0] + str(param[1]) + ": " + self.type_arg2str(ta) + ", " + param[1] += 1 + res = res[:-2] # removes , from end of string + res += ") => " + self.type_arg2str(t.type_args[-1]) + return res + + return "{}<{}>".format(t.name, ", ".join([self.type_arg2str(ta) + for ta in t.type_args])) + + def pop_children_res(self, children): + len_c = len(children) + if not len_c: + return [] + res = self._children_res[-len_c:] + self._children_res = self._children_res[:-len_c] + return res def visit_program(self, node): - return + self.context = node.context + self.class_decls = [decl for decl in node.get_declarations() + if isinstance(decl, ast.ClassDeclaration)] + children = node.children() + for c in children: + c.accept(self) + self.program = '\n\n'.join(self.pop_children_res(children)) + + @append_to + def visit_block(self, node): + children = node.children() + is_void = self.is_void + self.is_void = False + is_interface = self.is_interface + is_lambda = self.is_lambda + self.is_lambda = False + self.is_interface = False + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + + if is_interface: + self.is_interface = is_interface + self.is_lambda = is_lambda + self._children_res.append("") + return + + res = "{" if not is_lambda else "" + res += "\n" + ";\n".join(children_res[:-1]) + if children_res[:-1]: + res += ";\n" + ret_keyword = "return " if node.is_func_block and not is_void else "" + if children_res: + res += " "*self.ident + ret_keyword + " " + \ + children_res[-1].strip() + ";\n" + \ + " "*self.ident + else: + res += " "*self.ident + ret_keyword.strip() + ";\n" + \ + " "*self.ident + res += "}" if not is_lambda else "" + self.is_void = is_void + self.is_interface = is_interface + self.is_lambda = is_lambda + self._children_res.append(res) + + + @append_to + def visit_super_instantiation(self, node): + old_ident = self.ident + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + class_type = self.get_type_name(node.class_type) + super_call = None + + if node.args is not None: + children_res = [c.strip() for c in children_res] + super_call = "super(" + ", ".join(children_res) + ")" + + res = (class_type, super_call) + + self.ident = old_ident + self._children_res.append(res) + + @append_to + def visit_class_decl(self, node): + old_ident = self.ident + self.ident += 2 + prev_is_interface = self.is_interface + self.is_interface = node.is_interface() + prev_class = self.current_class + self.current_class = node + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + + + + field_res = [children_res[i] + for i, _ in enumerate(node.fields)] + len_fields = len(field_res) + + field_names = [field.name for field in node.fields] + + superclasses_res = [children_res[i + len_fields] + for i, _ in enumerate(node.superclasses)] + + len_supercls = len(superclasses_res) + + supertype, supercall = None, None + if len_supercls > 0: + supertype, supercall = superclasses_res[0] + + function_res = [children_res[i + len_fields + len_supercls] + for i, _ in enumerate(node.functions)] + len_functions = len(function_res) + + type_parameters_res = ", ".join( + children_res[len_fields + len_supercls + len_functions:]) + class_prefix = node.get_class_prefix() + + res = "{ident}{p} {n}".format( + ident=" " * old_ident, + p=class_prefix, + n=node.name, + ) + + if type_parameters_res: + res = "{}<{}>".format(res, type_parameters_res) + if supertype is not None: + inheritance = ( + " extends " + if node.is_interface() or node.superclasses[0].args is not None + else " implements " + ) + res += inheritance + supertype + + res += " {\n" + " "*old_ident + + # Makes constructor + if not self.is_interface: + class_ident = self.ident + self.ident += 2 + body = "{" + + if supercall is not None: + body += "\n" + " "*self.ident + supercall + stripped_fields = [field.strip() for field in field_res] + for var_name in field_names: + prefix = "\n" + self.ident * " " + body += prefix + "this." + var_name + " = " + var_name + body += "\n" + class_ident*" " + "}\n" + res += " "*class_ident + "constructor({f}) {b}".format( + f = ", ".join(stripped_fields), + b = body, + ) + self.ident = class_ident + + if field_res: + res += "\n\n".join( + field_res) + "\n" + if function_res: + res += "\n\n".join( + function_res) + "\n" + + res += old_ident*" " + "\n}" + + self.ident = old_ident + self.is_interface = prev_is_interface + self.current_class = prev_class + self._children_res.append(res) + + @append_to + def visit_type_param(self, node): + res = node.name + if node.bound: + res += " extends " + self.get_type_name(node.bound) + self._children_res.append(res) + + @append_to + def visit_var_decl(self, node): + old_ident = self.ident + prefix = " " * self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + var_type = "const " if node.is_final else "let " + res = prefix + var_type + node.name + + if node.var_type is not None: + res += ": " + self.get_type_name(node.var_type) + + res += " = " + children_res[0] + self.ident = old_ident + self._children_res.append(res) + + @append_to + def visit_call_argument(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in node.children(): + c.accept(self) + self.ident = old_ident + children_res = self.pop_children_res(children) + res = children_res[0] + self._children_res.append(res) + + @append_to + def visit_field_decl(self, node): + prefix = self.ident * " " + res = prefix + node.name + ": " + self.get_type_name(node.field_type) + self._children_res.append(res) + + @append_to + def visit_param_decl(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + self.ident = old_ident + + param_type = node.param_type + res = node.name + ": " + self.get_type_name(param_type) + + if len(children): + children_res = self.pop_children_res(children) + res += ("" if self.current_function.body is None + else " = " + children_res[0]) + + self._children_res.append(res) + + @append_to + def visit_func_decl(self, node): + old_ident = self.ident + self.ident += 2 + current_function = self.current_function + prev_function = self.current_function + self.current_function = node + is_in_class = node.func_type == ast.FunctionDeclaration.CLASS_METHOD + is_interface = self.is_interface + self.is_interface = False + prev_is_void = self.is_void + self.is_void = node.get_type() == tst.VoidType() + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + + param_res = [children_res[i] for i, _ in enumerate(node.params)] + len_params = len(node.params) + + len_type_params = len(node.type_parameters) + type_parameters_res = ", ".join( + children_res[len_params:len_type_params + len_params]) + + body_res = children_res[-1] if node.body else '' + + prefix = " " * old_ident + + if not is_in_class: + prefix += "function " + elif is_in_class and not node.body and not is_interface: + prefix += "abstract " + + type_params = ( + "<" + type_parameters_res + ">" if type_parameters_res else "") + + res = "" + + res = prefix + node.name + type_params + "(" + ", ".join( + param_res) + ")" + if node.ret_type: + res += ": " + self.get_type_name(node.ret_type) + if body_res and isinstance(node.body, ast.Block): + res += " \n" + body_res + elif body_res: + body_res = "return " + body_res.strip() \ + if not self.is_void \ + else body_res.strip() + res += "{\n" + " "*self.ident + \ + body_res + "\n" + " "*old_ident + \ + "}" + + self.ident = old_ident + self.current_function = prev_function + self.is_void = prev_is_void + self.is_interface = is_interface + self._children_res.append(res) + + @append_to + def visit_lambda(self, node): + + old_ident = self.ident + is_expression = not isinstance(node.body, ast.Block) + self.ident = 0 if is_expression else self.ident+2 + + children = node.children() + + prev_is_void = self.is_void + self.is_void = node.get_type() == tst.VoidType() + + prev_is_lambda = self.is_lambda + self.is_lambda = True + for c in children: + c.accept(self) + self.is_lambda = True + children_res = self.pop_children_res(children) + self.ident = old_ident + param_res = [children_res[i] for i, _ in enumerate (node.params)] + body_res = children_res[-1] if node.body else '' + + if not is_expression: + body_res = "{" + body_res + " "*old_ident + "}\n" + + + res = "({params}) => {body}".format( + params=", ".join(param_res), + body=body_res, + ) + + self.is_void = prev_is_void + self.is_lambda = prev_is_lambda + self._children_res.append(res) + + @append_to + def visit_bottom_constant(self, node): + bottom = "(undefined as unknown)" + + if node.t: + bottom = "(" + bottom + " as {})".format( + self.get_type_name(node.t) + ) + else: + bottom = "(undefined as never)" + + res = " "*self.ident + bottom + self._children_res.append(res) + + @append_to + def visit_integer_constant(self, node): + literal = "BigInt({})".format(str(node.literal)) \ + if isinstance(node.integer_type, tst.BigIntegerType) \ + else str(node.literal) + self._children_res.append(" "*self.ident + literal) + + @append_to + def visit_real_constant(self, node): + literal = str(node.literal) + self._children_res.append(" "*self.ident + literal) + + @append_to + def visit_char_constant(self, node): + # Symbol type in TypeScript + self._children_res.append("Symbol(\'{}\')".format( + node.literal)) + + @append_to + def visit_string_constant(self, node): + self._children_res.append('"{}"'.format(node.literal)) + + @append_to + def visit_boolean_constant(self, node): + self._children_res.append(str(node.literal)) + + @append_to + def visit_array_expr(self, node): + if not node.length: + self._children_res.append("[]") + return + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + self.ident = old_ident + return self._children_res.append("[{}]".format( + ", ".join(children_res))) + + + @append_to + def visit_variable(self, node): + res = node.name + if self.current_class and node.name in [f.name for f in self.current_class.fields]: + res = "this." + res + + self._children_res.append(" " * self.ident + res) + + @append_to + def visit_binary_op(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + res = "{}({} {} {})".format( + " "*old_ident, children_res[0], node.operator, + children_res[1]) + self.ident = old_ident + self._children_res.append(res) + + def visit_logical_expr(self, node): + self.visit_binary_op(node) + + def visit_equality_expr(self, node): + self.visit_binary_op(node) + + def visit_comparison_expr(self, node): + self.visit_binary_op(node) + + def visit_arith_expr(self, node): + self.visit_binary_op(node) + + @append_to + def visit_conditional(self, node): + old_ident = self.ident + self.ident += 2 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + res = "({}{} ? {} : {})".format( + " "*old_ident, children_res[0].strip(), + children_res[1].strip(), + children_res[2].strip()) + + self.ident = old_ident + self._children_res.append(res) + + @append_to + def visit_is(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + res = "{}{} {} {}".format( + " " * old_ident, children_res[0], str(node.operator), + node.rexpr.name) + self.ident = old_ident + self._children_res.append(res) + + @append_to + def visit_new(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + self.ident = old_ident + # Remove type arguments from Parameterized Type + if getattr(node.class_type, 'can_infer_type_args', None) is True: + self._children_res.append("new {}({})".format( + " " * self.ident + node.class_type.name, + ", ".join(children_res))) + else: + self._children_res.append("new {}({})".format( + " " * self.ident + self.get_type_name(node.class_type), + ", ".join(children_res))) + + @append_to + def visit_field_access(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + children_res = self.pop_children_res(children) + self.ident = old_ident + receiver_expr = (children_res[0] if children_res[0] + else "this") + res = "{}.{}".format(receiver_expr, node.field) + self._children_res.append(res) + + @append_to + def visit_func_ref(self, node): + old_ident = self.ident + + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + + self.ident = old_ident + children_res = self.pop_children_res(children) + this_prefix = children_res[0] if children_res else "" + if self.current_class is not None: + callable_funcs = self.current_class.get_callable_functions(self.class_decls) + function_names = [f.name for f in callable_funcs] + if node.receiver is None and node.func in function_names: + this_prefix += "this" + # FIXME Must check signatures and not names + # (for overwritten + overloaded functions) + + res = "{}{}{}".format(" "*self.ident, "({}).".format(this_prefix) if this_prefix else "", node.func) + + self._children_res.append(res) + + @append_to + def visit_func_call(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + self.ident = old_ident + children_res = self.pop_children_res(children) + type_args = "" + if not node.can_infer_type_args and node.type_args: + type_args += ( + "<" + ",".join( + [self.get_type_name(t) for t in node.type_args]) \ + + ">" + ) + + this_prefix = "" + if self.current_class is not None: + callable_funcs = self.current_class.get_callable_functions(self.class_decls) + function_names = [f.name for f in callable_funcs] + if node.receiver is None and node.func in function_names: + this_prefix += "this." + # FIXME Must check signatures and not names + # (for overwritten + overloaded functions) + + if node.receiver: + receiver_expr = children_res[0] + res = "{}{}.{}{}({})".format( + " " * self.ident, receiver_expr, node.func, + type_args, + ", ".join(children_res[1:])) + else: + res = "{}{}{}{}({})".format( + " " * self.ident, + this_prefix, node.func, type_args, + ", ".join(children_res)) + self._children_res.append(res) + + @append_to + def visit_assign(self, node): + old_ident = self.ident + self.ident = 0 + children = node.children() + for c in children: + c.accept(self) + self.ident = old_ident + children_res = self.pop_children_res(children) + + is_field = False + if self.current_class: + is_field = node.name in [f.name for f in self.current_class.fields] - def result(self): - return ""; \ No newline at end of file + if node.receiver or is_field: + receiver_expr = children_res[0] if node.receiver else "this" + expr = children_res[1] if node.receiver else children_res[0] + res = "{}{}.{} = {}".format( + " " * old_ident, + receiver_expr, + node.name, + expr + ) + else: + res = "{}{} = {}".format( + " " * old_ident, + node.name, + children_res[0] + ) + self.ident = old_ident + self._children_res.append(res) \ No newline at end of file From 93b0d26b7edf8d3ea57692ba91240265d570b941 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 25 Jul 2022 17:03:09 +0300 Subject: [PATCH 08/78] Add file for typescript keywords --- src/resources/typescript_keywords | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/resources/typescript_keywords diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords new file mode 100644 index 00000000..9b921370 --- /dev/null +++ b/src/resources/typescript_keywords @@ -0,0 +1 @@ +frames \ No newline at end of file From 581c5c456912092c9eccb9d2799a080241314915 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 25 Jul 2022 18:43:56 +0300 Subject: [PATCH 09/78] add typescript translator functionality --- src/ir/typescript_types.py | 2 +- src/resources/typescript_keywords | 3 +- src/translators/typescript.py | 150 +++++++++++++++++++++++++----- 3 files changed, 129 insertions(+), 26 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index ba67621c..8e5e4dae 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -24,7 +24,7 @@ def get_boolean_type(self): return BooleanType(primitive=False) def get_char_type(self): - return SymbolType(primitive=False) + return StringType(primitive=False) def get_string_type(self): return StringType(primitive=False) diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords index 9b921370..b09c0a40 100644 --- a/src/resources/typescript_keywords +++ b/src/resources/typescript_keywords @@ -1 +1,2 @@ -frames \ No newline at end of file +frames +Record \ No newline at end of file diff --git a/src/translators/typescript.py b/src/translators/typescript.py index c93fb35b..ba5af136 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -1,8 +1,12 @@ from ast import literal_eval +from cgitb import reset import enum from gc import is_finalized +from json.encoder import py_encode_basestring from shutil import register_archive_format from src.ir import ast, typescript_types as tst, types as tp, type_utils as tu +from src.transformations.base import change_namespace +from src.ir.context import get_decl from src.translators.base import BaseTranslator def append_to(visit): @@ -29,6 +33,7 @@ def __init__(self, package=None, options={}): self.current_class = None self.current_function = None self.context = None + self._namespace: tuple = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] def _reset_state(self): @@ -40,6 +45,7 @@ def _reset_state(self): self.current_class = None self.current_function self.context = None + self._namespace = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] @staticmethod @@ -88,12 +94,16 @@ def pop_children_res(self, children): def visit_program(self, node): self.context = node.context - self.class_decls = [decl for decl in node.get_declarations() + self.class_decls = [decl for decl in node.declarations if isinstance(decl, ast.ClassDeclaration)] children = node.children() for c in children: c.accept(self) - self.program = '\n\n'.join(self.pop_children_res(children)) + res = '\n\n'.join(self.pop_children_res(children)) + self.program = ( + res if self.package is None + else "module {} {{\n{}\n}}".format(self.package, res) + ) @append_to def visit_block(self, node): @@ -153,6 +163,7 @@ def visit_super_instantiation(self, node): self._children_res.append(res) @append_to + @change_namespace def visit_class_decl(self, node): old_ident = self.ident self.ident += 2 @@ -183,9 +194,30 @@ def visit_class_decl(self, node): supertype, supercall = superclasses_res[0] function_res = [children_res[i + len_fields + len_supercls] - for i, _ in enumerate(node.functions)] + for i, _ in enumerate(node.functions)] len_functions = len(function_res) + if node.is_abstract() and supertype is not None and node.superclasses[0].args is None: + # TypeScript requires abstract classes that implement interfaces + # to either implement all methods + # or to re-write the signatures with "abstract" in front. + # This code block resolves the class declaration of the + # interface supertype that the abstract class (node) + # implements and re-visits its FunctionDeclaration children + # in order to add them to its function_res + abstract_funcs = [f for f in node.get_abstract_functions(self.class_decls) + if f.name not in [f.name for f in node.functions]] + + #implemented_funcs = [f.name for f in node.functions] + #supertype_funcs = [f for f in abstract_funcs if f.name not in implemented_funcs] + + for func in abstract_funcs: + func.accept(self) + supertype_func_res = self.pop_children_res(abstract_funcs) + + function_res += supertype_func_res + + type_parameters_res = ", ".join( children_res[len_fields + len_supercls + len_functions:]) class_prefix = node.get_class_prefix() @@ -297,6 +329,8 @@ def visit_param_decl(self, node): param_type = node.param_type res = node.name + ": " + self.get_type_name(param_type) + res = "..." + res if node.vararg else res + if len(children): children_res = self.pop_children_res(children) res += ("" if self.current_function.body is None @@ -305,6 +339,7 @@ def visit_param_decl(self, node): self._children_res.append(res) @append_to + @change_namespace def visit_func_decl(self, node): old_ident = self.ident self.ident += 2 @@ -316,9 +351,16 @@ def visit_func_decl(self, node): self.is_interface = False prev_is_void = self.is_void self.is_void = node.get_type() == tst.VoidType() + + param_len = len(node.params) + children = node.children() - for c in children: + for i, c in enumerate(children): + prev_namespace = self._namespace + if i < param_len: + self._namespace = self._namespace[:-1] c.accept(self) + self._namespace = prev_namespace children_res = self.pop_children_res(children) param_res = [children_res[i] for i, _ in enumerate(node.params)] @@ -363,6 +405,7 @@ def visit_func_decl(self, node): self._children_res.append(res) @append_to + @change_namespace def visit_lambda(self, node): old_ident = self.ident @@ -456,9 +499,16 @@ def visit_array_expr(self, node): @append_to def visit_variable(self, node): res = node.name - if self.current_class and node.name in [f.name for f in self.current_class.fields]: - res = "this." + res + + decl = get_decl(self.context, self._namespace, node.name) + + assert decl is not None + _, decl = decl + + if isinstance(decl, ast.FieldDeclaration): + res = "this." + res + self._children_res.append(" " * self.ident + res) @append_to @@ -491,9 +541,28 @@ def visit_arith_expr(self, node): def visit_conditional(self, node): old_ident = self.ident self.ident += 2 + prev_namespace = self._namespace children = node.children() - for c in children: - c.accept(self) + + + cond = children[0] + cond.accept(self) + + true_branch = children[1] + false_branch = children[2] + + + if isinstance(cond, ast.Is): + self._namespace = prev_namespace + ('true_block',) + true_branch.accept(self) + self._namespace = prev_namespace + ('false_block',) + false_branch.accept(self) + else: + true_branch.accept(self) + false_branch.accept(self) + + self._namespace = prev_namespace + children_res = self.pop_children_res(children) res = "({}{} ? {} : {})".format( " "*old_ident, children_res[0].strip(), @@ -552,6 +621,17 @@ def visit_field_access(self, node): @append_to def visit_func_ref(self, node): + def needs_this_prefix(node, decl): + if node.receiver is not None: + return False + elif decl is None: + return True # Function is an inherited method + elif isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): + return True # Function is method of current class + elif isinstance(decl, ast.FieldDeclaration) and decl.get_type().name[:-1] == tst.TypeScriptBuiltinFactory().get_function_type().name[:-1]: + return True # Function is callable field + return False + old_ident = self.ident self.ident = 0 @@ -561,14 +641,18 @@ def visit_func_ref(self, node): self.ident = old_ident children_res = self.pop_children_res(children) + this_prefix = children_res[0] if children_res else "" - if self.current_class is not None: - callable_funcs = self.current_class.get_callable_functions(self.class_decls) - function_names = [f.name for f in callable_funcs] - if node.receiver is None and node.func in function_names: - this_prefix += "this" - # FIXME Must check signatures and not names - # (for overwritten + overloaded functions) + + decl = get_decl(self.context, self._namespace, node.func) + + if decl is not None: + _, decl = decl + + if needs_this_prefix(node, decl): + this_prefix += "this" + # FIXME Must check signatures and not names + # (for overwritten + overloaded functions) res = "{}{}{}".format(" "*self.ident, "({}).".format(this_prefix) if this_prefix else "", node.func) @@ -576,6 +660,19 @@ def visit_func_ref(self, node): @append_to def visit_func_call(self, node): + def needs_this_prefix(node, decl): + if node.receiver is not None: + return False + + elif decl is None: # Function is an inherited method + return True + elif isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): # Function is method of current class + return True + elif isinstance(decl, ast.FieldDeclaration) and decl.get_type().name[:-1] == tst.TypeScriptBuiltinFactory().get_function_type().name[:-1]: # Function is callable field + return True + + return False + old_ident = self.ident self.ident = 0 children = node.children() @@ -592,10 +689,13 @@ def visit_func_call(self, node): ) this_prefix = "" - if self.current_class is not None: - callable_funcs = self.current_class.get_callable_functions(self.class_decls) - function_names = [f.name for f in callable_funcs] - if node.receiver is None and node.func in function_names: + + decl = get_decl(self.context, self._namespace, node.name) + + if decl is not None: + _, decl = decl + + if needs_this_prefix(node, decl): this_prefix += "this." # FIXME Must check signatures and not names # (for overwritten + overloaded functions) @@ -623,12 +723,13 @@ def visit_assign(self, node): self.ident = old_ident children_res = self.pop_children_res(children) - is_field = False - if self.current_class: - is_field = node.name in [f.name for f in self.current_class.fields] + decl = get_decl(self.context, self._namespace, node.name) - if node.receiver or is_field: - receiver_expr = children_res[0] if node.receiver else "this" + if decl is not None: + _, decl = decl + + if node.receiver or decl is None or isinstance(decl, ast.FieldDeclaration): + receiver_expr = (children_res[0] if node.receiver else "this") expr = children_res[1] if node.receiver else children_res[0] res = "{}{}.{} = {}".format( " " * old_ident, @@ -642,5 +743,6 @@ def visit_assign(self, node): node.name, children_res[0] ) + self.ident = old_ident self._children_res.append(res) \ No newline at end of file From ffe988011b2b695a9505d7346d4e025d84e270d6 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 26 Jul 2022 16:42:49 +0300 Subject: [PATCH 10/78] add regex for tsc errors --- src/compilers/typescript.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index 73e3c54c..9af9c595 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -5,7 +5,7 @@ class TypeScriptCompiler(BaseCompiler): - ERROR_REGEX = re.compile(".*") + ERROR_REGEX = re.compile(r'([A-Za-z0-9_\-\/]+\.ts):\d+:\d+ - error (TS\d+): (.+)') CRASH_REGEX = re.compile("123") @@ -21,17 +21,17 @@ def get_compiler_cmd(self): return ['tsc --target es2020', self.input_name] def analyze_compiler_output(self, output): - return defaultdict(list) - """ self.crashed = None + self.crashed = None failed = defaultdict(list) matches = re.findall(self.ERROR_REGEX, output) for match in matches: filename = match[0] - error_msg = match[1] + error_msg = match[2] failed[filename].append(error_msg) + crash_match = re.search(self.CRASH_REGEX, output) if crash_match and not matches: self.crash_msg = output return None - return failed """ \ No newline at end of file + return failed \ No newline at end of file From b2350baf1da596562deea951e736a4fd8c555a66 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Tue, 26 Jul 2022 17:22:59 +0300 Subject: [PATCH 11/78] Fix regex for identifying TS errors --- src/compilers/typescript.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index 9af9c595..edcc1bb4 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -3,9 +3,11 @@ import os from src.compilers.base import BaseCompiler + class TypeScriptCompiler(BaseCompiler): - ERROR_REGEX = re.compile(r'([A-Za-z0-9_\-\/]+\.ts):\d+:\d+ - error (TS\d+): (.+)') + ERROR_REGEX = re.compile( + r'[.\/]+(/[A-Za-z0-9_\-\/]+\.ts).*error.*(TS\d+): (.+)') CRASH_REGEX = re.compile("123") @@ -18,7 +20,8 @@ def get_compiler_version(cls): return ['tsc', '-v'] def get_compiler_cmd(self): - return ['tsc --target es2020', self.input_name] + return ['tsc --target es2020', os.path.join( + self.input_name, '**', '*.ts')] def analyze_compiler_output(self, output): self.crashed = None @@ -28,10 +31,9 @@ def analyze_compiler_output(self, output): filename = match[0] error_msg = match[2] failed[filename].append(error_msg) - crash_match = re.search(self.CRASH_REGEX, output) if crash_match and not matches: self.crash_msg = output return None - return failed \ No newline at end of file + return failed From 875ac187de4de40d5484ca078f01b63887e5f9d0 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 27 Jul 2022 17:31:22 +0300 Subject: [PATCH 12/78] added regex for compiler crashes --- src/compilers/typescript.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index edcc1bb4..ec91d42f 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -9,7 +9,7 @@ class TypeScriptCompiler(BaseCompiler): ERROR_REGEX = re.compile( r'[.\/]+(/[A-Za-z0-9_\-\/]+\.ts).*error.*(TS\d+): (.+)') - CRASH_REGEX = re.compile("123") + CRASH_REGEX = re.compile(r'(.+)(\n(\s+at .+))+') def __init__(self, input_name): super().__init__(input_name) @@ -20,7 +20,7 @@ def get_compiler_version(cls): return ['tsc', '-v'] def get_compiler_cmd(self): - return ['tsc --target es2020', os.path.join( + return ['tsc --target es2020 --pretty false', os.path.join( self.input_name, '**', '*.ts')] def analyze_compiler_output(self, output): From 652bbed0cb7f19f809c37a57e35b54f0f776b1f4 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 28 Jul 2022 11:08:31 +0300 Subject: [PATCH 13/78] add braces around binary operands --- src/translators/typescript.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index ba5af136..49dacdcd 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -519,7 +519,7 @@ def visit_binary_op(self, node): for c in children: c.accept(self) children_res = self.pop_children_res(children) - res = "{}({} {} {})".format( + res = "{}(({}) {} ({}))".format( " "*old_ident, children_res[0], node.operator, children_res[1]) self.ident = old_ident From 0cc8d646226b58bdcf6dd9e21ae5026e6d5908a0 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 28 Jul 2022 13:18:28 +0300 Subject: [PATCH 14/78] style & code changes based on comments from typescript-translator PR#1 --- src/args.py | 2 +- src/ir/typescript_types.py | 39 +++--- src/resources/typescript_keywords | 7 +- src/translators/kotlin.py | 9 +- src/translators/typescript.py | 191 ++++++++++++------------------ src/translators/utils.py | 6 + 6 files changed, 107 insertions(+), 147 deletions(-) create mode 100644 src/translators/utils.py diff --git a/src/args.py b/src/args.py index 0a000597..33f8f1b6 100644 --- a/src/args.py +++ b/src/args.py @@ -237,7 +237,7 @@ def validate_args(args): sys.exit("You cannot use --examine option without the --replay option") if args.language == "typescript" and not args.disable_use_site_variance: - sys.exit("\nTypeScript does not have use-site variance.\nRun Hephaestus again with the flag --disable-use-site-variance.") + sys.exit("\nTypeScript does not support use-site variance.\nRun Hephaestus again with the flag --disable-use-site-variance.") def pre_process_args(args): diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 8e5e4dae..f39c852d 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -7,7 +7,7 @@ class TypeScriptBuiltinFactory(bt.BuiltinFactory): def get_language(self): return "typescript" - + def get_builtin(self): return TypeScriptBuiltin @@ -19,7 +19,7 @@ def get_any_type(self): def get_number_type(self): return NumberType(primitive=False) - + def get_boolean_type(self): return BooleanType(primitive=False) @@ -28,7 +28,7 @@ def get_char_type(self): def get_string_type(self): return StringType(primitive=False) - + def get_big_integer_type(self): return BigIntegerType(primitive=False) @@ -37,10 +37,10 @@ def get_array_type(self): def get_function_type(self, nr_parameters=0): return FunctionType(nr_parameters) - + def get_object_type(self): return ObjectLowercaseType() - + def get_primitive_types(self): return [ NumberType(primitive=True), @@ -76,12 +76,12 @@ class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): super().__init__(name) self.primitive = primitive - + def __str__(self): if not self.is_primitive(): return str(self.name) + "(typescript-builtin)" return str(self.name).lower() + "(typescript-primitive)" - + def is_primitive(self): return self.primitive @@ -106,30 +106,31 @@ class NumberType(TypeScriptBuiltin): def __init__(self, name="Number", primitive=False): super().__init__(name, primitive) self.supertypes.append(ObjectType()) - + def is_assignable(self, other): - return type(other) is NumberType + return isinstance(other, NumberType) def box_type(self): return NumberType(self.name, primitive=False) - + def get_name(self): if self.is_primitive: return "number" return super().get_name() + class BigIntegerType(TypeScriptBuiltin): def __init__(self, name="BigInt", primitive=False): super().__init__(name, primitive) self.supertypes.append(ObjectType()) - + def is_assignable(self, other): assignable_types= [BigIntegerType] return self.is_subtype(other) or type(other) in assignable_types - + def box_type(self): return BigIntegerType(self.name, primitive=False) - + def get_name(self): if self.is_primitive: return "bigint" @@ -140,10 +141,10 @@ class BooleanType(TypeScriptBuiltin): def __init__(self, name="Boolean", primitive=False): super().__init__(name, primitive) self.supertypes.append(ObjectType()) - + def box_type(self): return BooleanType(self.name, primitive=False) - + def get_name(self): if self.is_primitive: return "boolean" @@ -152,12 +153,12 @@ def get_name(self): class StringType(TypeScriptBuiltin): def __init__(self, name="String", primitive=False): - super().__init__(name ,primitive) + super().__init__(name , primitive) self.supertypes.append(ObjectType()) - + def box_type(self): return StringType(self.name, primitive=False) - + def get_name(self): if self.is_primitive: return "string" @@ -198,4 +199,4 @@ def __init__(self, nr_type_parameters: int): ] + [tp.TypeParameter("R", tp.Covariant)] self.nr_type_parameters = nr_type_parameters super().__init__(name, type_parameters) - self.supertypes.append(ObjectType()) \ No newline at end of file + self.supertypes.append(ObjectType()) diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords index b09c0a40..4969da8a 100644 --- a/src/resources/typescript_keywords +++ b/src/resources/typescript_keywords @@ -1,2 +1,7 @@ frames -Record \ No newline at end of file +Record +undefined +delete +Delete +debugger +arguments \ No newline at end of file diff --git a/src/translators/kotlin.py b/src/translators/kotlin.py index ddb8a6e7..7ae8c002 100644 --- a/src/translators/kotlin.py +++ b/src/translators/kotlin.py @@ -1,13 +1,6 @@ from src.ir import ast, kotlin_types as kt, types as tp, type_utils as tu from src.translators.base import BaseTranslator - - -def append_to(visit): - def inner(self, node): - self._nodes_stack.append(node) - res = visit(self, node) - self._nodes_stack.pop() - return inner +from src.translators.utils import append_to class KotlinTranslator(BaseTranslator): diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 49dacdcd..7c295555 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -1,20 +1,8 @@ -from ast import literal_eval -from cgitb import reset -import enum -from gc import is_finalized -from json.encoder import py_encode_basestring -from shutil import register_archive_format -from src.ir import ast, typescript_types as tst, types as tp, type_utils as tu +from src.ir import ast, typescript_types as tst from src.transformations.base import change_namespace from src.ir.context import get_decl from src.translators.base import BaseTranslator - -def append_to(visit): - def inner(self, node): - self._nodes_stack.append(node) - res = visit(self, node) - self._nodes_stack.pop() - return inner +from src.translators.utils import append_to class TypeScriptTranslator(BaseTranslator): @@ -35,7 +23,7 @@ def __init__(self, package=None, options={}): self.context = None self._namespace: tuple = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] - + def _reset_state(self): self._children_res = [] self.ident = 0 @@ -43,42 +31,55 @@ def _reset_state(self): self.is_void = False self.is_lambda = False self.current_class = None - self.current_function + self.current_function = None self.context = None self._namespace = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] - + + def needs_this_prefix(self, node, decl): + func_name = tst.TypeScriptBuiltinFactory().get_function_type().name[:-1] + if node.receiver is not None: + return False + + if decl is None: + return True # Function is an inherited method + + if isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): + return True # Function is method of current class + + if (isinstance(decl, ast.FieldDeclaration) and + decl.get_type().name[:-1] == func_name): + + return True # Function is callable field + return False @staticmethod def get_filename(): return TypeScriptTranslator.filename - + @staticmethod def get_incorrect_filename(): return TypeScriptTranslator.incorrect_filename - + def type_arg2str(self, t_arg): # TypeScript does not have a Wildcard type - a = self.get_type_name(t_arg) return self.get_type_name(t_arg) - + def get_type_name(self, t): t_constructor = getattr(t, 't_constructor', None) if not t_constructor: return t.get_name() - if t_constructor.name.startswith("Function"): - param = ['p', 1] - res = "(" - - if len(t.type_args) == 1: - res += ") => " + self.type_arg2str(t.type_args[-1]) - return res - - for ta in t.type_args[:-1]: - res += param[0] + str(param[1]) + ": " + self.type_arg2str(ta) + ", " - param[1] += 1 - res = res[:-2] # removes , from end of string - res += ") => " + self.type_arg2str(t.type_args[-1]) + func_name = tst.TypeScriptBuiltinFactory().get_function_type().name[:-1] + if t_constructor.name.startswith(func_name): + param_types = t.type_args[:-1] + ret_type = t.type_args[-1] + res = "({}) => {}".format( + ",".join([ + "p" + str(i) + ": " + str(self.type_arg2str(pt)) + for i, pt in enumerate(param_types) + ]), + self.type_arg2str(ret_type) + ) return res return "{}<{}>".format(t.name, ", ".join([self.type_arg2str(ta) @@ -91,7 +92,7 @@ def pop_children_res(self, children): res = self._children_res[-len_c:] self._children_res = self._children_res[:-len_c] return res - + def visit_program(self, node): self.context = node.context self.class_decls = [decl for decl in node.declarations @@ -102,9 +103,10 @@ def visit_program(self, node): res = '\n\n'.join(self.pop_children_res(children)) self.program = ( res if self.package is None - else "module {} {{\n{}\n}}".format(self.package, res) + else f"module {self.package} {{\n{res}\n}}" ) - + self._reset_state() + @append_to def visit_block(self, node): children = node.children() @@ -117,7 +119,7 @@ def visit_block(self, node): for c in children: c.accept(self) children_res = self.pop_children_res(children) - + if is_interface: self.is_interface = is_interface self.is_lambda = is_lambda @@ -142,7 +144,6 @@ def visit_block(self, node): self.is_lambda = is_lambda self._children_res.append(res) - @append_to def visit_super_instantiation(self, node): old_ident = self.ident @@ -156,7 +157,7 @@ def visit_super_instantiation(self, node): if node.args is not None: children_res = [c.strip() for c in children_res] super_call = "super(" + ", ".join(children_res) + ")" - + res = (class_type, super_call) self.ident = old_ident @@ -175,18 +176,13 @@ def visit_class_decl(self, node): for c in children: c.accept(self) children_res = self.pop_children_res(children) - - - field_res = [children_res[i] for i, _ in enumerate(node.fields)] len_fields = len(field_res) - field_names = [field.name for field in node.fields] - + superclasses_res = [children_res[i + len_fields] for i, _ in enumerate(node.superclasses)] - len_supercls = len(superclasses_res) supertype, supercall = None, None @@ -194,7 +190,7 @@ def visit_class_decl(self, node): supertype, supercall = superclasses_res[0] function_res = [children_res[i + len_fields + len_supercls] - for i, _ in enumerate(node.functions)] + for i, _ in enumerate(node.functions)] len_functions = len(function_res) if node.is_abstract() and supertype is not None and node.superclasses[0].args is None: @@ -208,16 +204,12 @@ def visit_class_decl(self, node): abstract_funcs = [f for f in node.get_abstract_functions(self.class_decls) if f.name not in [f.name for f in node.functions]] - #implemented_funcs = [f.name for f in node.functions] - #supertype_funcs = [f for f in abstract_funcs if f.name not in implemented_funcs] - for func in abstract_funcs: func.accept(self) supertype_func_res = self.pop_children_res(abstract_funcs) function_res += supertype_func_res - type_parameters_res = ", ".join( children_res[len_fields + len_supercls + len_functions:]) class_prefix = node.get_class_prefix() @@ -232,7 +224,7 @@ def visit_class_decl(self, node): res = "{}<{}>".format(res, type_parameters_res) if supertype is not None: inheritance = ( - " extends " + " extends " if node.is_interface() or node.superclasses[0].args is not None else " implements " ) @@ -251,7 +243,7 @@ def visit_class_decl(self, node): stripped_fields = [field.strip() for field in field_res] for var_name in field_names: prefix = "\n" + self.ident * " " - body += prefix + "this." + var_name + " = " + var_name + body += prefix + "this." + var_name + " = " + var_name body += "\n" + class_ident*" " + "}\n" res += " "*class_ident + "constructor({f}) {b}".format( f = ", ".join(stripped_fields), @@ -260,12 +252,10 @@ def visit_class_decl(self, node): self.ident = class_ident if field_res: - res += "\n\n".join( - field_res) + "\n" + res += "\n\n".join(field_res) + "\n" if function_res: - res += "\n\n".join( - function_res) + "\n" - + res += "\n\n".join(function_res) + "\n" + res += old_ident*" " + "\n}" self.ident = old_ident @@ -291,10 +281,10 @@ def visit_var_decl(self, node): children_res = self.pop_children_res(children) var_type = "const " if node.is_final else "let " res = prefix + var_type + node.name - + if node.var_type is not None: res += ": " + self.get_type_name(node.var_type) - + res += " = " + children_res[0] self.ident = old_ident self._children_res.append(res) @@ -343,7 +333,6 @@ def visit_param_decl(self, node): def visit_func_decl(self, node): old_ident = self.ident self.ident += 2 - current_function = self.current_function prev_function = self.current_function self.current_function = node is_in_class = node.func_type == ast.FunctionDeclaration.CLASS_METHOD @@ -369,7 +358,7 @@ def visit_func_decl(self, node): len_type_params = len(node.type_parameters) type_parameters_res = ", ".join( children_res[len_params:len_type_params + len_params]) - + body_res = children_res[-1] if node.body else '' prefix = " " * old_ident @@ -381,14 +370,12 @@ def visit_func_decl(self, node): type_params = ( "<" + type_parameters_res + ">" if type_parameters_res else "") - - res = "" res = prefix + node.name + type_params + "(" + ", ".join( param_res) + ")" if node.ret_type: res += ": " + self.get_type_name(node.ret_type) - if body_res and isinstance(node.body, ast.Block): + if body_res and isinstance(node.body, ast.Block): res += " \n" + body_res elif body_res: body_res = "return " + body_res.strip() \ @@ -430,9 +417,9 @@ def visit_lambda(self, node): if not is_expression: body_res = "{" + body_res + " "*old_ident + "}\n" - - res = "({params}) => {body}".format( + res = "({params}): {ret} => {body}".format( params=", ".join(param_res), + ret=self.get_type_name(node.ret_type), body=body_res, ) @@ -445,7 +432,7 @@ def visit_bottom_constant(self, node): bottom = "(undefined as unknown)" if node.t: - bottom = "(" + bottom + " as {})".format( + bottom = "(" + bottom + " as {})".format( self.get_type_name(node.t) ) else: @@ -469,8 +456,7 @@ def visit_real_constant(self, node): @append_to def visit_char_constant(self, node): # Symbol type in TypeScript - self._children_res.append("Symbol(\'{}\')".format( - node.literal)) + self._children_res.append(f"'{node.literal}'") @append_to def visit_string_constant(self, node): @@ -483,8 +469,7 @@ def visit_boolean_constant(self, node): @append_to def visit_array_expr(self, node): if not node.length: - self._children_res.append("[]") - return + return self._children_res.append("[]") old_ident = self.ident self.ident = 0 children = node.children() @@ -495,20 +480,14 @@ def visit_array_expr(self, node): return self._children_res.append("[{}]".format( ", ".join(children_res))) - @append_to def visit_variable(self, node): res = node.name - decl = get_decl(self.context, self._namespace, node.name) - assert decl is not None - _, decl = decl - if isinstance(decl, ast.FieldDeclaration): res = "this." + res - self._children_res.append(" " * self.ident + res) @append_to @@ -544,23 +523,19 @@ def visit_conditional(self, node): prev_namespace = self._namespace children = node.children() - cond = children[0] cond.accept(self) - true_branch = children[1] false_branch = children[2] - if isinstance(cond, ast.Is): self._namespace = prev_namespace + ('true_block',) - true_branch.accept(self) + true_branch.accept(self) self._namespace = prev_namespace + ('false_block',) false_branch.accept(self) else: true_branch.accept(self) false_branch.accept(self) - self._namespace = prev_namespace children_res = self.pop_children_res(children) @@ -568,7 +543,7 @@ def visit_conditional(self, node): " "*old_ident, children_res[0].strip(), children_res[1].strip(), children_res[2].strip()) - + self.ident = old_ident self._children_res.append(res) @@ -621,17 +596,6 @@ def visit_field_access(self, node): @append_to def visit_func_ref(self, node): - def needs_this_prefix(node, decl): - if node.receiver is not None: - return False - elif decl is None: - return True # Function is an inherited method - elif isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): - return True # Function is method of current class - elif isinstance(decl, ast.FieldDeclaration) and decl.get_type().name[:-1] == tst.TypeScriptBuiltinFactory().get_function_type().name[:-1]: - return True # Function is callable field - return False - old_ident = self.ident self.ident = 0 @@ -649,30 +613,21 @@ def needs_this_prefix(node, decl): if decl is not None: _, decl = decl - if needs_this_prefix(node, decl): + if self.needs_this_prefix(node, decl): this_prefix += "this" - # FIXME Must check signatures and not names + # TODO Must check signatures and not names # (for overwritten + overloaded functions) - res = "{}{}{}".format(" "*self.ident, "({}).".format(this_prefix) if this_prefix else "", node.func) + res = "{}{}{}".format( + " "*self.ident, + "({}).".format(this_prefix) if this_prefix else "", + node.func + ) self._children_res.append(res) @append_to def visit_func_call(self, node): - def needs_this_prefix(node, decl): - if node.receiver is not None: - return False - - elif decl is None: # Function is an inherited method - return True - elif isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): # Function is method of current class - return True - elif isinstance(decl, ast.FieldDeclaration) and decl.get_type().name[:-1] == tst.TypeScriptBuiltinFactory().get_function_type().name[:-1]: # Function is callable field - return True - - return False - old_ident = self.ident self.ident = 0 children = node.children() @@ -687,7 +642,7 @@ def needs_this_prefix(node, decl): [self.get_type_name(t) for t in node.type_args]) \ + ">" ) - + this_prefix = "" decl = get_decl(self.context, self._namespace, node.name) @@ -695,10 +650,10 @@ def needs_this_prefix(node, decl): if decl is not None: _, decl = decl - if needs_this_prefix(node, decl): - this_prefix += "this." - # FIXME Must check signatures and not names - # (for overwritten + overloaded functions) + if self.needs_this_prefix(node, decl): + this_prefix += "this." + # FIXME Must check signatures and not names + # (for overwritten + overloaded functions) if node.receiver: receiver_expr = children_res[0] @@ -708,7 +663,7 @@ def needs_this_prefix(node, decl): ", ".join(children_res[1:])) else: res = "{}{}{}{}({})".format( - " " * self.ident, + " " * self.ident, this_prefix, node.func, type_args, ", ".join(children_res)) self._children_res.append(res) @@ -745,4 +700,4 @@ def visit_assign(self, node): ) self.ident = old_ident - self._children_res.append(res) \ No newline at end of file + self._children_res.append(res) diff --git a/src/translators/utils.py b/src/translators/utils.py new file mode 100644 index 00000000..783f37a9 --- /dev/null +++ b/src/translators/utils.py @@ -0,0 +1,6 @@ +def append_to(visit): + def inner(self, node): + self._nodes_stack.append(node) + visit(self, node) + self._nodes_stack.pop() + return inner \ No newline at end of file From dd9cc5658aad55f6bdedaa7dd436d6ea518d9109 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Thu, 28 Jul 2022 14:23:51 +0300 Subject: [PATCH 15/78] Fix some minor stylish issues --- src/ir/typescript_types.py | 4 ++- src/translators/typescript.py | 49 +++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index f39c852d..a4a850cd 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -4,6 +4,7 @@ import src.ir.builtins as bt import src.ir.types as tp + class TypeScriptBuiltinFactory(bt.BuiltinFactory): def get_language(self): return "typescript" @@ -90,6 +91,7 @@ class ObjectType(TypeScriptBuiltin): def __init__(self, name="Object"): super().__init__(name, False) + class ObjectLowercaseType(TypeScriptBuiltin): def __init__(self, name="object"): super().__init__(name, False) @@ -153,7 +155,7 @@ def get_name(self): class StringType(TypeScriptBuiltin): def __init__(self, name="String", primitive=False): - super().__init__(name , primitive) + super().__init__(name, primitive) self.supertypes.append(ObjectType()) def box_type(self): diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 7c295555..82badbea 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -42,16 +42,17 @@ def needs_this_prefix(self, node, decl): return False if decl is None: - return True # Function is an inherited method + return True # Function is an inherited method if isinstance(decl, ast.FunctionDeclaration) and decl.is_class_method(): - return True # Function is method of current class + return True # Function is method of current class if (isinstance(decl, ast.FieldDeclaration) and decl.get_type().name[:-1] == func_name): - return True # Function is callable field + return True # Function is callable field return False + @staticmethod def get_filename(): return TypeScriptTranslator.filename @@ -193,7 +194,8 @@ def visit_class_decl(self, node): for i, _ in enumerate(node.functions)] len_functions = len(function_res) - if node.is_abstract() and supertype is not None and node.superclasses[0].args is None: + if node.is_abstract() and supertype is not None and ( + node.superclasses[0].args is None): # TypeScript requires abstract classes that implement interfaces # to either implement all methods # or to re-write the signatures with "abstract" in front. @@ -201,8 +203,10 @@ def visit_class_decl(self, node): # interface supertype that the abstract class (node) # implements and re-visits its FunctionDeclaration children # in order to add them to its function_res - abstract_funcs = [f for f in node.get_abstract_functions(self.class_decls) - if f.name not in [f.name for f in node.functions]] + abstract_funcs = [ + f for f in node.get_abstract_functions(self.class_decls) + if f.name not in [f.name for f in node.functions] + ] for func in abstract_funcs: func.accept(self) @@ -246,8 +250,8 @@ def visit_class_decl(self, node): body += prefix + "this." + var_name + " = " + var_name body += "\n" + class_ident*" " + "}\n" res += " "*class_ident + "constructor({f}) {b}".format( - f = ", ".join(stripped_fields), - b = body, + f=", ".join(stripped_fields), + b=body, ) self.ident = class_ident @@ -323,8 +327,10 @@ def visit_param_decl(self, node): if len(children): children_res = self.pop_children_res(children) - res += ("" if self.current_function.body is None - else " = " + children_res[0]) + res += ( + "" if self.current_function.body is None + else " = " + children_res[0] + ) self._children_res.append(res) @@ -378,12 +384,11 @@ def visit_func_decl(self, node): if body_res and isinstance(node.body, ast.Block): res += " \n" + body_res elif body_res: - body_res = "return " + body_res.strip() \ - if not self.is_void \ - else body_res.strip() - res += "{\n" + " "*self.ident + \ - body_res + "\n" + " "*old_ident + \ - "}" + body_res = ("return " + body_res.strip() + if not self.is_void + else body_res.strip()) + res += "{\n" + " " * self.ident + \ + body_res + "\n" + " " * old_ident + "}" self.ident = old_ident self.current_function = prev_function @@ -411,7 +416,7 @@ def visit_lambda(self, node): self.is_lambda = True children_res = self.pop_children_res(children) self.ident = old_ident - param_res = [children_res[i] for i, _ in enumerate (node.params)] + param_res = [children_res[i] for i, _ in enumerate(node.params)] body_res = children_res[-1] if node.body else '' if not is_expression: @@ -590,7 +595,7 @@ def visit_field_access(self, node): children_res = self.pop_children_res(children) self.ident = old_ident receiver_expr = (children_res[0] if children_res[0] - else "this") + else "this") res = "{}.{}".format(receiver_expr, node.field) self._children_res.append(res) @@ -637,10 +642,9 @@ def visit_func_call(self, node): children_res = self.pop_children_res(children) type_args = "" if not node.can_infer_type_args and node.type_args: - type_args += ( + type_args += ( "<" + ",".join( - [self.get_type_name(t) for t in node.type_args]) \ - + ">" + [self.get_type_name(t) for t in node.type_args]) + ">" ) this_prefix = "" @@ -683,7 +687,8 @@ def visit_assign(self, node): if decl is not None: _, decl = decl - if node.receiver or decl is None or isinstance(decl, ast.FieldDeclaration): + if node.receiver or decl is None or isinstance( + decl, ast.FieldDeclaration): receiver_expr = (children_res[0] if node.receiver else "this") expr = children_res[1] if node.receiver else children_res[0] res = "{}{}.{} = {}".format( From 6cd52e1fbc69317ba003fa44c2da7b5201ad5d69 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 28 Jul 2022 16:14:33 +0300 Subject: [PATCH 16/78] Add arrow functions for nested functions in methods, update .gitignore --- .gitignore | 8 + pylintrc | 504 ---------------------------------- src/translators/typescript.py | 26 +- 3 files changed, 28 insertions(+), 510 deletions(-) delete mode 100644 pylintrc diff --git a/.gitignore b/.gitignore index 70391cd2..dc1e850c 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,11 @@ dmypy.json # Pyre type checker .pyre/ + +# VSCode / pylint +.vscode/ +./pylintrc + +# Local Folder to Store Possible Bugs +possible_bugs/ + diff --git a/pylintrc b/pylintrc deleted file mode 100644 index 21394a9f..00000000 --- a/pylintrc +++ /dev/null @@ -1,504 +0,0 @@ -[MASTER] - -# Specify a score threshold to be exceeded before program exits with error. -fail-under=9.5 - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=tests,pickle_it.py,setup.py,examples,.eggs - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the -# number of processors available to use. -jobs=1 - -# Control the amount of potential inferred values when inferring a single -# object. This can help the performance when dealing with large functions or -# complex, nested conditions. -limit-inference-results=100 - -# List of plugins (as comma separated values of python module names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once). You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use "--disable=all --enable=classes -# --disable=W". -disable=missing-function-docstring, - missing-class-docstring, - missing-module-docstring, - too-many-public-methods - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a score less than or equal to 10. You -# have access to the variables 'error', 'warning', 'refactor', and 'convention' -# which contain the number of messages in each category, as well as 'statement' -# which is the total number of statements analyzed. This score is used by the -# global evaluation report (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details. -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio). You can also give a reporter class, e.g. -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages. -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=sys.exit - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# Tells whether to warn about missing members when the owner of the attribute -# is inferred to be None. -ignore-none=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis). It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - -# List of decorators that change the signature of a decorated function. -signature-mutators= - - -[BASIC] - -# Naming style matching correct argument names. -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style. -#argument-rgx= - -# Naming style matching correct attribute names. -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style. -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma. -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Bad variable names regexes, separated by a comma. If names match any regex, -# they will always be refused -bad-names-rgxs= - -# Naming style matching correct class attribute names. -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style. -#class-attribute-rgx= - -# Naming style matching correct class names. -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming- -# style. -#class-rgx= - -# Naming style matching correct constant names. -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style. -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names. -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style. -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma. -good-names=i, - j, - k, - ex, - Run, - _ - -# Good variable names regexes, separated by a comma. If names match any regex, -# they will always be accepted -good-names-rgxs= - -# Include a hint for the correct naming format with invalid-name. -include-naming-hint=no - -# Naming style matching correct inline iteration names. -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style. -#inlinevar-rgx= - -# Naming style matching correct method names. -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style. -#method-rgx= - -# Naming style matching correct module names. -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style. -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -# These decorators are taken in consideration only for invalid-name. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names. -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style. -variable-rgx=([a-z_][a-z0-9_]{2,30}$|c|ns|tp|op|e1|e2) - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes. -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it work, -# install the python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains the private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to the private dictionary (see the -# --spelling-private-dict-file option) instead of raising a message. -spelling-store-unknown-words=no - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module. -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[STRING] - -# This flag controls whether inconsistent-quotes generates a warning when the -# character used as a quote delimiter is used inconsistently within a module. -check-quote-consistency=no - -# This flag controls whether the implicit-str-concat should generate a warning -# on implicit string concatenation in sequences defined over several lines. -check-str-concat-over-line-jumps=no - - -[LOGGING] - -# The type of string formatting that logging methods do. `old` means using % -# formatting, `new` is for `{}` formatting. -logging-format-style=old - -# Logging modules to check that the string format arguments are in logging -# function parameter format. -logging-modules=logging - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid defining new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expected to -# not be used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore. -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - -# Regular expression of note tags to take in consideration. -#notes-rgx= - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp, - __post_init__ - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=cls - - -[DESIGN] - -# Maximum number of arguments for function / method. -max-args=9 - -# Maximum number of attributes for a class (see R0902). -max-attributes=10 - -# Maximum number of boolean expressions in an if statement (see R0916). -max-bool-expr=5 - -# Maximum number of branch for function / method body. -max-branches=12 - -# Maximum number of locals for function / method body. -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body. -max-returns=6 - -# Maximum number of statements in function / method body. -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[IMPORTS] - -# List of modules that can be imported at any level, not just the top level -# one. -allow-any-import-level= - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma. -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled). -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled). -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled). -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - -# Couples of modules and preferred modules, separated by a comma. -preferred-modules= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "BaseException, Exception". -overgeneral-exceptions=BaseException, - Exception diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 82badbea..1e4a167d 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -53,6 +53,13 @@ def needs_this_prefix(self, node, decl): return True # Function is callable field return False + def is_in_class(self, namespace): + # Checks if node is any nth descendant of a class + for i in range(1, len(namespace)-1): + if namespace[i][0].isupper(): + return True + return False + @staticmethod def get_filename(): return TypeScriptTranslator.filename @@ -341,7 +348,7 @@ def visit_func_decl(self, node): self.ident += 2 prev_function = self.current_function self.current_function = node - is_in_class = node.func_type == ast.FunctionDeclaration.CLASS_METHOD + is_method = node.func_type == ast.FunctionDeclaration.CLASS_METHOD is_interface = self.is_interface self.is_interface = False prev_is_void = self.is_void @@ -368,20 +375,27 @@ def visit_func_decl(self, node): body_res = children_res[-1] if node.body else '' prefix = " " * old_ident + arrow_func = "" - if not is_in_class: + if not is_method and not self.is_in_class(self._namespace): prefix += "function " - elif is_in_class and not node.body and not is_interface: + elif not is_method: + prefix += f"let " + arrow_func = " = " + elif is_method and not node.body and not is_interface: prefix += "abstract " type_params = ( "<" + type_parameters_res + ">" if type_parameters_res else "") - res = prefix + node.name + type_params + "(" + ", ".join( - param_res) + ")" + res = prefix + node.name + arrow_func + type_params + \ + "(" + ", ".join(param_res) + ")" + if node.ret_type: res += ": " + self.get_type_name(node.ret_type) - if body_res and isinstance(node.body, ast.Block): + if body_res and arrow_func: + res += " => \n" + body_res + elif body_res and isinstance(node.body, ast.Block): res += " \n" + body_res elif body_res: body_res = ("return " + body_res.strip() From 7b2f22d9073a8034789bfd48c1883f71a536e4f9 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 28 Jul 2022 17:07:06 +0300 Subject: [PATCH 17/78] Modify compiler to filter out error patterns and add reserved words --- .gitignore | 1 + src/compilers/typescript.py | 24 +++++++----------------- src/resources/typescript_keywords | 4 +++- src/translators/typescript.py | 4 +++- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index dc1e850c..78eb851a 100644 --- a/.gitignore +++ b/.gitignore @@ -144,4 +144,5 @@ dmypy.json # Local Folder to Store Possible Bugs possible_bugs/ +patterns.txt diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index ec91d42f..11a4f184 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -11,9 +11,8 @@ class TypeScriptCompiler(BaseCompiler): CRASH_REGEX = re.compile(r'(.+)(\n(\s+at .+))+') - def __init__(self, input_name): - super().__init__(input_name) - self.crash_msg = None + def __init__(self, input_name, filter_patterns=None): + super().__init__(input_name, filter_patterns) @classmethod def get_compiler_version(cls): @@ -23,17 +22,8 @@ def get_compiler_cmd(self): return ['tsc --target es2020 --pretty false', os.path.join( self.input_name, '**', '*.ts')] - def analyze_compiler_output(self, output): - self.crashed = None - failed = defaultdict(list) - matches = re.findall(self.ERROR_REGEX, output) - for match in matches: - filename = match[0] - error_msg = match[2] - failed[filename].append(error_msg) - - crash_match = re.search(self.CRASH_REGEX, output) - if crash_match and not matches: - self.crash_msg = output - return None - return failed + def get_filename(self, match): + return match[0] + + def get_error_msg(self, match): + return f"{match[1]}{match[2]}" diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords index 4969da8a..566f5ffc 100644 --- a/src/resources/typescript_keywords +++ b/src/resources/typescript_keywords @@ -4,4 +4,6 @@ undefined delete Delete debugger -arguments \ No newline at end of file +arguments +let +export \ No newline at end of file diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 1e4a167d..0ec5ba19 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -70,7 +70,9 @@ def get_incorrect_filename(): def type_arg2str(self, t_arg): # TypeScript does not have a Wildcard type - return self.get_type_name(t_arg) + if not t_arg.is_wildcard(): + return self.get_type_name(t_arg) + return "unknown" def get_type_name(self, t): t_constructor = getattr(t, 't_constructor', None) From 2f8c31e80708c9096a4b06cd7c114c7ece44b466 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 29 Jul 2022 14:09:34 +0300 Subject: [PATCH 18/78] Add null type and support in typescript + kotlin. Add undefined type in typescript --- src/generators/generator.py | 3 ++- src/generators/generators.py | 1 + src/ir/ast.py | 9 +++++++++ src/ir/builtins.py | 6 +++++- src/ir/groovy_types.py | 4 ++++ src/ir/java_types.py | 4 ++++ src/ir/kotlin_types.py | 14 ++++++++++++++ src/ir/typescript_types.py | 33 +++++++++++++++++++++++++++++++++ src/ir/visitors.py | 7 +++++++ src/translators/typescript.py | 4 ++++ 10 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 976b87d1..87425caa 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -711,7 +711,7 @@ def _gen_func_from_existing(self, def _gen_type_params_from_existing(self, func: ast.FunctionDeclaration, type_var_map - ) -> (List[tp.TypeParameter], tu.TypeVarMap): + ) -> Tuple[List[tp.TypeParameter], tu.TypeVarMap]: """Gen type parameters for a function that overrides a parameterized function. @@ -1891,6 +1891,7 @@ def gen_fun_call(etype): self.bt_factory.get_array_type().name: ( lambda x: self.gen_array_expr(x, only_leaves, subtype=subtype) ), + self.bt_factory.get_null_type().name: lambda x: ast.Null } binary_ops = { self.bt_factory.get_boolean_type(): [ diff --git a/src/generators/generators.py b/src/generators/generators.py index 32661cd0..c81a39e8 100644 --- a/src/generators/generators.py +++ b/src/generators/generators.py @@ -8,6 +8,7 @@ """ from src import utils from src.ir import ast +import src.ir.typescript_types as tst from src.generators import utils as gu diff --git a/src/ir/ast.py b/src/ir/ast.py index a5d02a79..da225dc3 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -860,6 +860,15 @@ def is_bottom(self): return True +class NullConstant(Constant): + def __init__(self): + super().__init__("null") + + def is_equal(self, other): + return isinstance(other, NullConstant) + +Null = NullConstant() + class IntegerConstant(Constant): # TODO: Support Hex Integer literals, binary integer literals? def __init__(self, literal: int, integer_type): diff --git a/src/ir/builtins.py b/src/ir/builtins.py index dd41b356..fc382255 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -77,6 +77,10 @@ def get_array_type(self): def get_function_type(self, nr_parameters=0): pass + @abstractmethod + def get_null_type(self): + pass + def get_non_nothing_types(self): return [ self.get_any_type(), @@ -92,7 +96,7 @@ def get_non_nothing_types(self): self.get_boolean_type(), self.get_char_type(), self.get_string_type(), - self.get_array_type() + self.get_array_type(), ] def get_number_types(self): diff --git a/src/ir/groovy_types.py b/src/ir/groovy_types.py index 80923c5d..3809fda5 100644 --- a/src/ir/groovy_types.py +++ b/src/ir/groovy_types.py @@ -72,6 +72,10 @@ def get_primitive_types(self): BooleanType(primitive=True) ] + def get_null_type(self): + # FIXME + raise Exception("Groovy does not support null types") + def get_non_nothing_types(self): return super().get_non_nothing_types() + self.get_primitive_types() diff --git a/src/ir/java_types.py b/src/ir/java_types.py index 7a95b734..651ae890 100644 --- a/src/ir/java_types.py +++ b/src/ir/java_types.py @@ -62,6 +62,10 @@ def get_big_integer_type(self): def get_function_type(self, nr_parameters=0): return FunctionType(nr_parameters) + def get_null_type(self): + # FIXME + raise Exception("Java does not support null types") + def get_primitive_types(self): return [ ByteType(primitive=True), diff --git a/src/ir/kotlin_types.py b/src/ir/kotlin_types.py index a7383081..70193bd8 100644 --- a/src/ir/kotlin_types.py +++ b/src/ir/kotlin_types.py @@ -63,6 +63,9 @@ def get_function_type(self, nr_parameters=0): def get_nothing(self): return NothingType() + def get_null_type(self): + return NullType() + def get_non_nothing_types(self): types = super().get_non_nothing_types() types.extend([ @@ -123,6 +126,17 @@ def get_builtin_type(self): return bt.Number +class NullType(KotlinBuiltin): + def __init__(self, name="null", primitive=False): + super().__init__(name, primitive) + + def box_type(self): + return NullType(self.name) + + def get_name(self): + return 'null' + + class IntegerType(NumberType): def __init__(self, name="Int"): super().__init__(name) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index a4a850cd..f50dab24 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -72,6 +72,17 @@ def get_double_type(self): def get_big_decimal_type(self): return NumberType(primitive=False) + def get_null_type(self): + return NullType() + + def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types + types = super().get_non_nothing_types() + types.extend([ + self.get_null_type(), + UndefinedType(), + ]) + return types + class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): @@ -181,6 +192,28 @@ def get_name(self): return super().get_name() +class NullType(TypeScriptBuiltin): + def __init__(self, name="null", primitive=False): + super().__init__(name, primitive) + + def box_type(self): + return NullType(self.name) + + def get_name(self): + return 'null' + + +class UndefinedType(TypeScriptBuiltin): + def __init__(self, name="undefined", primitive=False): + super().__init__(name, primitive) + + def box_type(self): + return UndefinedType(self.name) + + def get_name(self): + return 'undefined' + + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): # In TypeScript, arrays are covariant. diff --git a/src/ir/visitors.py b/src/ir/visitors.py index 52629ab8..cbf3cf78 100644 --- a/src/ir/visitors.py +++ b/src/ir/visitors.py @@ -21,6 +21,7 @@ def visit(self, node): ast.FunctionReference: self.visit_func_ref, ast.BottomConstant: self.visit_bottom_constant, ast.IntegerConstant: self.visit_integer_constant, + ast.NullConstant: self.visit_null_constant, ast.RealConstant: self.visit_real_constant, ast.CharConstant: self.visit_char_constant, ast.StringConstant: self.visit_string_constant, @@ -90,6 +91,9 @@ def visit_integer_constant(self, node): raise NotImplementedError( 'visit_integer_constant() must be implemented') + def visit_null_constant(self, node): + raise NotImplementedError('visit_null_constant() must be implemented') + def visit_real_constant(self, node): raise NotImplementedError('visit_real_constant() must be implemented') @@ -195,6 +199,9 @@ def visit_bottom_constant(self, node): def visit_integer_constant(self, node): return self._visit_node(node) + def visit_null_constant(self, node): + return self._visit_node(node) + def visit_real_constant(self, node): return self._visit_node(node) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 0ec5ba19..f920fc1a 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -283,6 +283,10 @@ def visit_type_param(self, node): res += " extends " + self.get_type_name(node.bound) self._children_res.append(res) + @append_to + def visit_null_constant(self, node): + self._children_res.append(node.literal) + @append_to def visit_var_decl(self, node): old_ident = self.ident From f48061d965580c384ea62edc2af1bf80b70dda68 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 9 Aug 2022 14:56:36 +0300 Subject: [PATCH 19/78] Change supertypes of NullType in typescript and kotlin, change supertype of UndefinedType in typescript --- src/ir/groovy_types.py | 1 - src/ir/java_types.py | 1 - src/ir/kotlin_types.py | 6 +++--- src/ir/typescript_types.py | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/ir/groovy_types.py b/src/ir/groovy_types.py index 3809fda5..288ad781 100644 --- a/src/ir/groovy_types.py +++ b/src/ir/groovy_types.py @@ -73,7 +73,6 @@ def get_primitive_types(self): ] def get_null_type(self): - # FIXME raise Exception("Groovy does not support null types") def get_non_nothing_types(self): diff --git a/src/ir/java_types.py b/src/ir/java_types.py index 651ae890..2878f545 100644 --- a/src/ir/java_types.py +++ b/src/ir/java_types.py @@ -63,7 +63,6 @@ def get_function_type(self, nr_parameters=0): return FunctionType(nr_parameters) def get_null_type(self): - # FIXME raise Exception("Java does not support null types") def get_primitive_types(self): diff --git a/src/ir/kotlin_types.py b/src/ir/kotlin_types.py index 70193bd8..367f4734 100644 --- a/src/ir/kotlin_types.py +++ b/src/ir/kotlin_types.py @@ -126,9 +126,9 @@ def get_builtin_type(self): return bt.Number -class NullType(KotlinBuiltin): - def __init__(self, name="null", primitive=False): - super().__init__(name, primitive) +class NullType(AnyType): + def __init__(self, name="null"): + super().__init__(name) def box_type(self): return NullType(self.name) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index f50dab24..14e11877 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -192,9 +192,9 @@ def get_name(self): return super().get_name() -class NullType(TypeScriptBuiltin): +class NullType(ObjectType): def __init__(self, name="null", primitive=False): - super().__init__(name, primitive) + super().__init__(name) def box_type(self): return NullType(self.name) @@ -203,9 +203,9 @@ def get_name(self): return 'null' -class UndefinedType(TypeScriptBuiltin): +class UndefinedType(ObjectType): def __init__(self, name="undefined", primitive=False): - super().__init__(name, primitive) + super().__init__(name) def box_type(self): return UndefinedType(self.name) From 9271b168bb558d205ee0601684d46a88a5ae3d76 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 16 Aug 2022 19:21:21 +0300 Subject: [PATCH 20/78] Correct the primitive status of typescript built-in types --- src/ir/typescript_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 14e11877..cf7a215d 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -19,19 +19,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_boolean_type(self): - return BooleanType(primitive=False) + return BooleanType(primitive=True) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=False) + return StringType(primitive=True) def get_big_integer_type(self): - return BigIntegerType(primitive=False) + return BigIntegerType(primitive=True) def get_array_type(self): return ArrayType() @@ -52,25 +52,25 @@ def get_primitive_types(self): ] def get_integer_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_byte_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_short_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_long_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_float_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_double_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_big_decimal_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_null_type(self): return NullType() From ee4ddca72102dc5dae0dddb8fdf605223637d14c Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 16 Aug 2022 19:43:06 +0300 Subject: [PATCH 21/78] Change NullType and UndefinedType to primitives --- src/ir/typescript_types.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index cf7a215d..2752706b 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -48,7 +48,9 @@ def get_primitive_types(self): StringType(primitive=True), SymbolType(primitive=True), BooleanType(primitive=True), - BigIntegerType(primitive=True) + BigIntegerType(primitive=True), + NullType(primitive=True), + UndefinedType(primitive=True) ] def get_integer_type(self): @@ -73,13 +75,13 @@ def get_big_decimal_type(self): return NumberType(primitive=True) def get_null_type(self): - return NullType() + return NullType(primitive=True) def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types types = super().get_non_nothing_types() types.extend([ self.get_null_type(), - UndefinedType(), + UndefinedType(primitive=True), ]) return types From e752905867dc677943624784114a6a711bbc838a Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 24 Aug 2022 10:57:13 +0300 Subject: [PATCH 22/78] change primitive status for typescript types --- src/ir/typescript_types.py | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 2752706b..19d2e567 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -19,19 +19,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_boolean_type(self): - return BooleanType(primitive=True) + return BooleanType(primitive=False) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=True) + return StringType(primitive=False) def get_big_integer_type(self): - return BigIntegerType(primitive=True) + return BigIntegerType(primitive=False) def get_array_type(self): return ArrayType() @@ -44,44 +44,44 @@ def get_object_type(self): def get_primitive_types(self): return [ - NumberType(primitive=True), - StringType(primitive=True), - SymbolType(primitive=True), - BooleanType(primitive=True), - BigIntegerType(primitive=True), - NullType(primitive=True), - UndefinedType(primitive=True) + NumberType(primitive=False), + StringType(primitive=False), + SymbolType(primitive=False), + BooleanType(primitive=False), + BigIntegerType(primitive=False), + NullType(primitive=False), + UndefinedType(primitive=False) ] def get_integer_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_byte_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_short_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_long_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_float_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_double_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_big_decimal_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_null_type(self): - return NullType(primitive=True) + return NullType(primitive=False) def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types types = super().get_non_nothing_types() types.extend([ self.get_null_type(), - UndefinedType(primitive=True), + UndefinedType(primitive=False), ]) return types From 81a677abf243106544092e01052cdec35007e601 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 24 Aug 2022 20:47:39 +0300 Subject: [PATCH 23/78] (WIP) Add AST Nodes for type alias decls and begin adapting generator --- src/generators/generator.py | 30 ++++++++++++++++++++++++++++++ src/ir/ast.py | 19 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/generators/generator.py b/src/generators/generator.py index 87425caa..4b74e294 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -113,6 +113,7 @@ def gen_top_level_declaration(self): self.gen_variable_decl, self.gen_class_decl, self.gen_func_decl, + self.gen_type_alias_decl, ] gen_func = ut.random.choice(candidates) gen_func() @@ -542,6 +543,7 @@ def _add_node_to_parent(self, parent_namespace, node): ast.VariableDeclaration: self.context.add_var, ast.FieldDeclaration: self.context.add_var, ast.ParameterDeclaration: self.context.add_var, + ast.TypeAliasDeclaration: self.context.add_var, ast.Lambda: self.context.add_lambda, } if parent_namespace == ast.GLOBAL_NAMESPACE: @@ -822,6 +824,34 @@ def gen_variable_decl(self, self._add_node_to_parent(self.namespace, var_decl) return var_decl + def gen_type_alias_decl(self, + etype=None, + expr=None) -> ast.TypeAliasDeclaration: + """Generate a Type Declaration (Type Alias) + + Args: + etype: the type(s) that the type alias describes + expr: an expression to assign to the type alias + """ + alias = etype if etype else self.select_type() + initial_depth = self.depth + self.depth += 1 + + # Below, 'True' is passed to self.generate_expr + # in place of the only_leaves parameter + # in order for no leaves other + # than expr to be generated. + expr = expr or self.generate_expr(alias, True, sam_coercion=True) + + self.depth = initial_depth + type_alias_decl = ast.TypeAliasDeclaration( + name=gu.gen_identifier('lower'), + type_descr=alias, + expr=expr) + self._add_node_to_parent(self.namespace, type_alias_decl) + return type_alias_decl + + ##### Expressions ##### def _get_class_attributes(self, class_decl, attr_name): diff --git a/src/ir/ast.py b/src/ir/ast.py index da225dc3..6929b56c 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -263,6 +263,25 @@ def is_equal(self, other): return False +class TypeAliasDeclaration(Declaration): + def __init__(self, name: str, + type_descr: types.Type, + expr: Expr,): + self.name = name + self.type_descr = type_descr + self.expr = expr + + def children(self): + return [self.expr] + + def get_type(self): + return self.type_descr + + def update_children(self, children): + super().update_children(children) + self.expr = children[0] + + class SuperClassInstantiation(Node): def __init__(self, class_type: types.Type, args: List[Expr] = []): assert not isinstance(class_type, types.AbstractType) From 830c47c7122ad45dee8835b8dc437554c31c34c5 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 25 Aug 2022 12:31:48 +0300 Subject: [PATCH 24/78] Pass bt_factory to ast.Program node to circumvent circular imports --- src/ir/ast.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/ir/ast.py b/src/ir/ast.py index 6929b56c..ea331b16 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -5,7 +5,6 @@ import src.ir.type_utils as tu import src.ir.types as types from src import utils -from src.ir import BUILTIN_FACTORIES from src.ir.builtins import BuiltinFactory, FunctionType from src.ir.node import Node @@ -32,10 +31,10 @@ class Expr(Node): class Program(Node): # Set default value to kotlin for backward compatibility - def __init__(self, context, language): + def __init__(self, context, language, bt_factory): self.context = context self.language = language - self.bt_factory: BuiltinFactory = BUILTIN_FACTORIES[language] + self.bt_factory: BuiltinFactory = bt_factory def children(self): return self.context.get_declarations(GLOBAL_NAMESPACE, @@ -263,25 +262,6 @@ def is_equal(self, other): return False -class TypeAliasDeclaration(Declaration): - def __init__(self, name: str, - type_descr: types.Type, - expr: Expr,): - self.name = name - self.type_descr = type_descr - self.expr = expr - - def children(self): - return [self.expr] - - def get_type(self): - return self.type_descr - - def update_children(self, children): - super().update_children(children) - self.expr = children[0] - - class SuperClassInstantiation(Node): def __init__(self, class_type: types.Type, args: List[Expr] = []): assert not isinstance(class_type, types.AbstractType) From c9bc3f854a16b38eafa2c77c543fbe7179444ac0 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 25 Aug 2022 12:34:58 +0300 Subject: [PATCH 25/78] add type alias ast node to typescript-specific AST file extension --- src/ir/typescript_ast.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/ir/typescript_ast.py diff --git a/src/ir/typescript_ast.py b/src/ir/typescript_ast.py new file mode 100644 index 00000000..45dbbcf0 --- /dev/null +++ b/src/ir/typescript_ast.py @@ -0,0 +1,20 @@ +from src.ir import ast +import src.ir.types as types + +class TypeAliasDeclaration(ast.Declaration): + def __init__(self, name: str, + type_descr: types.Type, + expr: ast.Expr,): + self.name = name + self.type_descr = type_descr + self.expr = expr + + def children(self): + return [self.expr] + + def get_type(self): + return self.type_descr + + def update_children(self, children): + super().update_children(children) + self.expr = children[0] From 23d266bfcb644ca33bde9b3e74a227a552e487df Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 25 Aug 2022 21:41:25 +0300 Subject: [PATCH 26/78] (WIP) Add TypeAlias type, extend generator, context, and visitors for language specific features --- src/generators/generator.py | 62 ++++++++++++++++---------------- src/ir/builtins.py | 21 +++++++++++ src/ir/typescript_ast.py | 14 ++++---- src/ir/typescript_types.py | 67 ++++++++++++++++++++++++++++++++++- src/translators/typescript.py | 53 +++++++++++++++++++++++++-- 5 files changed, 174 insertions(+), 43 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 4b74e294..abd98516 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -94,7 +94,7 @@ def generate(self, context=None) -> ast.Program: cfg.limits.max_top_level): self.gen_top_level_declaration() self.generate_main_func() - return ast.Program(self.context, self.language) + return ast.Program(self.context, self.language, self.bt_factory) def gen_top_level_declaration(self): """Generate a top-level declaration and add it in the context. @@ -113,10 +113,21 @@ def gen_top_level_declaration(self): self.gen_variable_decl, self.gen_class_decl, self.gen_func_decl, - self.gen_type_alias_decl, ] + lang_specific_decls = self.bt_factory.get_decl_candidates(self) + candidates.extend(lang_specific_decls) gen_func = ut.random.choice(candidates) - gen_func() + if gen_func in lang_specific_decls: + # If the randomly chosen decl generator is + # provided by the builtin factory then it + # generates a decl for a language-specific + # feature. Hence, we must provide the generator's + # instance (self) as an argumet manually so the + # function has access to the generator's attributes + # and methods. + gen_func(self) + else: + gen_func() def generate_main_func(self) -> ast.FunctionDeclaration: """Generate the main function. @@ -543,17 +554,31 @@ def _add_node_to_parent(self, parent_namespace, node): ast.VariableDeclaration: self.context.add_var, ast.FieldDeclaration: self.context.add_var, ast.ParameterDeclaration: self.context.add_var, - ast.TypeAliasDeclaration: self.context.add_var, ast.Lambda: self.context.add_lambda, } - if parent_namespace == ast.GLOBAL_NAMESPACE: + lang_specific_pairs = self.bt_factory.update_add_node_to_parent() + node_type.update(lang_specific_pairs) + lang_specific = type(node) in lang_specific_pairs + # If the node is a language-specific AST node + # then we must provide the self.context + # as well so the method provided by the language + # builtin factory has access to the context's + # other attributes and methods + + if parent_namespace == ast.GLOBAL_NAMESPACE and not lang_specific: node_type[type(node)](parent_namespace, node.name, node) return + elif parent_namespace == ast.GLOBAL_NAMESPACE: + node_type[type(node)](self.context, parent_namespace, node.name, node) + return parent = self.context.get_decl(parent_namespace[:-1], parent_namespace[-1]) if parent and isinstance(parent, ast.ClassDeclaration): self._add_node_to_class(parent, node) + if lang_specific: + node_type[type(node)](self.context, parent_namespace, node.name, node) + return node_type[type(node)](parent_namespace, node.name, node) @@ -824,33 +849,6 @@ def gen_variable_decl(self, self._add_node_to_parent(self.namespace, var_decl) return var_decl - def gen_type_alias_decl(self, - etype=None, - expr=None) -> ast.TypeAliasDeclaration: - """Generate a Type Declaration (Type Alias) - - Args: - etype: the type(s) that the type alias describes - expr: an expression to assign to the type alias - """ - alias = etype if etype else self.select_type() - initial_depth = self.depth - self.depth += 1 - - # Below, 'True' is passed to self.generate_expr - # in place of the only_leaves parameter - # in order for no leaves other - # than expr to be generated. - expr = expr or self.generate_expr(alias, True, sam_coercion=True) - - self.depth = initial_depth - type_alias_decl = ast.TypeAliasDeclaration( - name=gu.gen_identifier('lower'), - type_descr=alias, - expr=expr) - self._add_node_to_parent(self.namespace, type_alias_decl) - return type_alias_decl - ##### Expressions ##### diff --git a/src/ir/builtins.py b/src/ir/builtins.py index fc382255..bd963764 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -111,6 +111,27 @@ def get_number_types(self): self.get_big_integer_type(), ] + def get_decl_candidates(self, gen_object): + """ Overwrite this method to return a list + with language-specific AST declaration nodes. + + See implementation in typescript_types.py and + TS-specific AST nodes in typescript_ast.py + + """ + return [] + + def update_add_node_to_parent(self, gen_object): + """ Overwrite this to update the dict 'node_type' + on src.generators.generator._add_node_to_parent + with the respective + key-value pair for the language-specific AST nodes. + + See implementation in typescript_types.py + + """ + return {} + def get_function_types(self, max_parameters): return [self.get_function_type(i) for i in range(0, max_parameters+1)] diff --git a/src/ir/typescript_ast.py b/src/ir/typescript_ast.py index 45dbbcf0..7e77acb7 100644 --- a/src/ir/typescript_ast.py +++ b/src/ir/typescript_ast.py @@ -1,20 +1,18 @@ -from src.ir import ast +import src.ir.ast as ast import src.ir.types as types class TypeAliasDeclaration(ast.Declaration): def __init__(self, name: str, - type_descr: types.Type, - expr: ast.Expr,): + alias: types.Type): self.name = name - self.type_descr = type_descr - self.expr = expr + self.alias = alias def children(self): - return [self.expr] + return [self.alias] def get_type(self): - return self.type_descr + return self.alias def update_children(self, children): super().update_children(children) - self.expr = children[0] + self.alias = children[0] diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 19d2e567..db762286 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -1,8 +1,9 @@ from src.ir.types import Builtin - +import src.ir.typescript_ast as ts_ast import src.ir.builtins as bt import src.ir.types as tp +import src.generators.utils as gu class TypeScriptBuiltinFactory(bt.BuiltinFactory): @@ -85,6 +86,14 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific ]) return types + def get_decl_candidates(self, gen_object): + return [gen_type_alias_decl,] + + def update_add_node_to_parent(self): + return { + TypeAlias: add_type_alias, + } + class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): @@ -216,6 +225,28 @@ def get_name(self): return 'undefined' +class TypeAlias(ObjectType): + def __init__(self, alias, name="TypeAlias", primitive=False): + super().__init__(name) + self.alias = alias + self.name = name + self.primitive = primitive + + def get_type(self): + return self.alias + + def is_subtype(self, other): + import pdb + pdb.set_trace() + return isinstance(other, self.alias.get_type()) + + def box_type(self): + return TypeAlias(self.name) + + def get_name(self): + return self.name + + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): # In TypeScript, arrays are covariant. @@ -237,3 +268,37 @@ def __init__(self, nr_type_parameters: int): self.nr_type_parameters = nr_type_parameters super().__init__(name, type_parameters) self.supertypes.append(ObjectType()) + + +# Generator Extension + +""" The below functions are all passed as candidate +generation functions to the Hephaestus generator +in order for it to be able to work with language-specific +features of typescript. +""" + +def gen_type_alias_decl(gen_object, + etype=None) -> ts_ast.TypeAliasDeclaration: + """ Generate a Type Declaration (Type Alias) + + Args: + etype: the type(s) that the type alias describes + + Returns: + An AST node that describes a type alias declaration + as defined in src.ir.typescript_ast.py + """ + alias_type = etype if etype else gen_object.select_type() + initial_depth = gen_object.depth + gen_object.depth += 1 + + gen_object.depth = initial_depth + type_alias_decl = ts_ast.TypeAliasDeclaration( + name=gu.gen_identifier('lower'), + alias=alias_type) + gen_object._add_node_to_parent(gen_object.namespace, TypeAlias(alias_type, type_alias_decl.name)) + return type_alias_decl + +def add_type_alias(context, namespace, type_name, t): + context._add_entity(namespace, 'types', type_name, t) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index f920fc1a..f3af7ce8 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -1,9 +1,9 @@ -from src.ir import ast, typescript_types as tst +from src.ir import ast, typescript_types as tst, types from src.transformations.base import change_namespace from src.ir.context import get_decl from src.translators.base import BaseTranslator from src.translators.utils import append_to - +import src.ir.typescript_ast as ts_ast class TypeScriptTranslator(BaseTranslator): filename = "Main.ts" @@ -36,6 +36,51 @@ def _reset_state(self): self._namespace = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] + def visit(self, node): + # Overwriting method of ASTVisitor class + # to add typescript-specific visitors + visitors = { + ast.SuperClassInstantiation: self.visit_super_instantiation, + ast.ClassDeclaration: self.visit_class_decl, + types.TypeParameter: self.visit_type_param, + ast.CallArgument: self.visit_call_argument, + ast.FieldDeclaration: self.visit_field_decl, + ast.VariableDeclaration: self.visit_var_decl, + ast.ParameterDeclaration: self.visit_param_decl, + ast.FunctionDeclaration: self.visit_func_decl, + ast.Lambda: self.visit_lambda, + ast.FunctionReference: self.visit_func_ref, + ast.BottomConstant: self.visit_bottom_constant, + ast.IntegerConstant: self.visit_integer_constant, + ast.NullConstant: self.visit_null_constant, + ast.RealConstant: self.visit_real_constant, + ast.CharConstant: self.visit_char_constant, + ast.StringConstant: self.visit_string_constant, + ast.ArrayExpr: self.visit_array_expr, + ast.BooleanConstant: self.visit_boolean_constant, + ast.Variable: self.visit_variable, + ast.LogicalExpr: self.visit_logical_expr, + ast.EqualityExpr: self.visit_equality_expr, + ast.ComparisonExpr: self.visit_comparison_expr, + ast.ArithExpr: self.visit_arith_expr, + ast.Conditional: self.visit_conditional, + ast.Is: self.visit_is, + ast.New: self.visit_new, + ast.FieldAccess: self.visit_field_access, + ast.FunctionCall: self.visit_func_call, + ast.Assignment: self.visit_assign, + ast.Program: self.visit_program, + ast.Block: self.visit_block, + } + visitors.update({ + ts_ast.TypeAliasDeclaration: self.visit_type_alias_decl, + }) + visitor = visitors.get(node.__class__) + if visitor is None: + raise Exception( + "Cannot find visitor for instance node " + str(node.__class__)) + return visitor(node) + def needs_this_prefix(self, node, decl): func_name = tst.TypeScriptBuiltinFactory().get_function_type().name[:-1] if node.receiver is not None: @@ -726,3 +771,7 @@ def visit_assign(self, node): self.ident = old_ident self._children_res.append(res) + + @append_to + def visit_type_alias_decl(self, node): + raise NotImplementedError('Must Implement Type Alias Visitor!') From dd50a30b6eafe20477221939fff8fff987ac1df8 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 26 Aug 2022 15:57:08 +0300 Subject: [PATCH 27/78] Add support for type alias, extend typescript translator for builting typescript features --- src/ir/typescript_ast.py | 3 ++- src/ir/typescript_types.py | 17 ++++++++--------- src/ir/visitors.py | 15 +++++++++------ src/translators/typescript.py | 16 +++++++++------- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/ir/typescript_ast.py b/src/ir/typescript_ast.py index 7e77acb7..56c6268a 100644 --- a/src/ir/typescript_ast.py +++ b/src/ir/typescript_ast.py @@ -1,5 +1,6 @@ import src.ir.ast as ast import src.ir.types as types +import src.ir.typescript_types as tst class TypeAliasDeclaration(ast.Declaration): def __init__(self, name: str, @@ -11,7 +12,7 @@ def children(self): return [self.alias] def get_type(self): - return self.alias + return tst.TypeAlias(self.alias, self.name) def update_children(self, children): super().update_children(children) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index db762286..2acf374f 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -91,7 +91,7 @@ def get_decl_candidates(self, gen_object): def update_add_node_to_parent(self): return { - TypeAlias: add_type_alias, + ts_ast.TypeAliasDeclaration: add_type_alias, } @@ -226,7 +226,7 @@ def get_name(self): class TypeAlias(ObjectType): - def __init__(self, alias, name="TypeAlias", primitive=False): + def __init__(self, alias, name, primitive=False): super().__init__(name) self.alias = alias self.name = name @@ -236,12 +236,10 @@ def get_type(self): return self.alias def is_subtype(self, other): - import pdb - pdb.set_trace() - return isinstance(other, self.alias.get_type()) + return self.alias.is_subtype(other) def box_type(self): - return TypeAlias(self.name) + return TypeAlias(self.alias, self.name) def get_name(self): return self.name @@ -297,8 +295,9 @@ def gen_type_alias_decl(gen_object, type_alias_decl = ts_ast.TypeAliasDeclaration( name=gu.gen_identifier('lower'), alias=alias_type) - gen_object._add_node_to_parent(gen_object.namespace, TypeAlias(alias_type, type_alias_decl.name)) + gen_object._add_node_to_parent(gen_object.namespace, type_alias_decl) return type_alias_decl -def add_type_alias(context, namespace, type_name, t): - context._add_entity(namespace, 'types', type_name, t) +def add_type_alias(context, namespace, type_name, ta_decl): + context._add_entity(namespace, 'types', type_name, ta_decl.get_type()) + context._add_entity(namespace, 'decls', type_name, ta_decl) diff --git a/src/ir/visitors.py b/src/ir/visitors.py index cbf3cf78..63ba87a2 100644 --- a/src/ir/visitors.py +++ b/src/ir/visitors.py @@ -8,7 +8,15 @@ def result(self): raise NotImplementedError('result() must be implemented') def visit(self, node): - visitors = { + visitors = self.get_visitors() + visitor = visitors.get(node.__class__) + if visitor is None: + raise Exception( + "Cannot find visitor for instance node " + str(node.__class__)) + return visitor(node) + + def get_visitors(self): + return { ast.SuperClassInstantiation: self.visit_super_instantiation, ast.ClassDeclaration: self.visit_class_decl, types.TypeParameter: self.visit_type_param, @@ -41,11 +49,6 @@ def visit(self, node): ast.Program: self.visit_program, ast.Block: self.visit_block, } - visitor = visitors.get(node.__class__) - if visitor is None: - raise Exception( - "Cannot find visitor for instance node " + str(node.__class__)) - return visitor(node) def visit_program(self, node): raise NotImplementedError('visit_program() must be implemented') diff --git a/src/translators/typescript.py b/src/translators/typescript.py index f3af7ce8..576e34f7 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -36,7 +36,7 @@ def _reset_state(self): self._namespace = ast.GLOBAL_NAMESPACE self._nodes_stack = [None] - def visit(self, node): + def get_visitors(self): # Overwriting method of ASTVisitor class # to add typescript-specific visitors visitors = { @@ -75,11 +75,7 @@ def visit(self, node): visitors.update({ ts_ast.TypeAliasDeclaration: self.visit_type_alias_decl, }) - visitor = visitors.get(node.__class__) - if visitor is None: - raise Exception( - "Cannot find visitor for instance node " + str(node.__class__)) - return visitor(node) + return visitors def needs_this_prefix(self, node, decl): func_name = tst.TypeScriptBuiltinFactory().get_function_type().name[:-1] @@ -774,4 +770,10 @@ def visit_assign(self, node): @append_to def visit_type_alias_decl(self, node): - raise NotImplementedError('Must Implement Type Alias Visitor!') + old_ident = self.ident + prefix = " " * self.ident + self.ident = 0 + res = prefix + "type " + node.name + res += " = " + self.get_type_name(node.alias) + self.ident = old_ident + self._children_res.append(res) From e74b680b4047bc75ac870cde5a2140a41268f219 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 16 Aug 2022 19:21:21 +0300 Subject: [PATCH 28/78] Correct the primitive status of typescript built-in types --- src/ir/typescript_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 19d2e567..7a1880b6 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -19,19 +19,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_boolean_type(self): - return BooleanType(primitive=False) + return BooleanType(primitive=True) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=False) + return StringType(primitive=True) def get_big_integer_type(self): - return BigIntegerType(primitive=False) + return BigIntegerType(primitive=True) def get_array_type(self): return ArrayType() @@ -54,25 +54,25 @@ def get_primitive_types(self): ] def get_integer_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_byte_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_short_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_long_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_float_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_double_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_big_decimal_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_null_type(self): return NullType(primitive=False) From 221114283e74225bd412f691a914a15d9b66e968 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 23 Aug 2022 21:57:56 +0300 Subject: [PATCH 29/78] Merge support for literal types and null, undefined types --- src/generators/generator.py | 2 + src/generators/generators.py | 1 - src/ir/typescript_types.py | 119 ++++++++++++++++++++++++++++++----- 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 87425caa..c665eeb8 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -1893,6 +1893,8 @@ def gen_fun_call(etype): ), self.bt_factory.get_null_type().name: lambda x: ast.Null } + if self.language == 'typescript': + constant_candidates.update(self.bt_factory.constant_candidates(self)) binary_ops = { self.bt_factory.get_boolean_type(): [ lambda x: self.gen_logical_expr(x, only_leaves), diff --git a/src/generators/generators.py b/src/generators/generators.py index c81a39e8..32661cd0 100644 --- a/src/generators/generators.py +++ b/src/generators/generators.py @@ -8,7 +8,6 @@ """ from src import utils from src.ir import ast -import src.ir.typescript_types as tst from src.generators import utils as gu diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 7a1880b6..9a1ec52c 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -1,9 +1,8 @@ from src.ir.types import Builtin - - import src.ir.builtins as bt import src.ir.types as tp - +import src.utils as ut +import random class TypeScriptBuiltinFactory(bt.BuiltinFactory): def get_language(self): @@ -19,19 +18,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_boolean_type(self): - return BooleanType(primitive=True) + return BooleanType(primitive=False) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=True) + return StringType(primitive=False) def get_big_integer_type(self): - return BigIntegerType(primitive=True) + return BigIntegerType(primitive=False) def get_array_type(self): return ArrayType() @@ -54,25 +53,25 @@ def get_primitive_types(self): ] def get_integer_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_byte_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_short_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_long_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_float_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_double_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_big_decimal_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_null_type(self): return NullType(primitive=False) @@ -82,9 +81,18 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types.extend([ self.get_null_type(), UndefinedType(primitive=False), + literal_types.get_literal(), ]) return types + def constant_candidates(self, gen_object): + #from src.ir.ast import IntegerConstant, StringConstant + from src.ir import ast + return { + "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.name), + "StringLiteralType": lambda etype: ast.StringConstant(etype.name), + } + class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): @@ -216,6 +224,84 @@ def get_name(self): return 'undefined' +class NumberLiteralType(ObjectType): + def __init__(self, name, primitive=False): + super().__init__(str(name)) + + def get_name(self): + return self.name; + + def is_string_literal(self): + return False + + +class StringLiteralType(ObjectType): + def __init__(self, name, primitive=False): + super().__init__(name) + + def get_name(self): + return '"' + self.name + '"' + + def is_string_literal(self): + return True + + +class LiteralTypes: + def __init__(self, str_limit, num_limit): + self.literals = [] + self.str_literals = [] + self.num_literals = [] + # Define max number for generated literals + self.str_limit = str_limit + self.num_limit = num_limit + + def get_literal(self): + if ut.random.bool(): + return self.get_string_literal() + return self.get_number_literal() + + def get_string_literal(self): + lit = None + if (len(self.str_literals) == 0 or + (len(self.str_literals) < self.str_limit and + ut.random.bool())): + # If the limit for generated literals + # has not been surpassed, we can randomly + # generate a new one. + lit = StringLiteralType(ut.random.word().lower()) + self.literals.append(lit) + self.str_literals.append(lit) + else: + lit = random.choice(self.str_literals) + return lit + + def get_number_literal(self): + lit = None + if (len(self.num_literals) == 0 or + (len(self.num_literals) < self.num_limit and + ut.random.bool())): + # If the limit for generated literals + # has not been surpassed, we can randomly + # generate a new one. + lit = NumberLiteralType(ut.random.integer(-100, 100)) + self.literals.append(lit) + self.num_literals.append(lit) + else: + lit = random.choice(self.num_literals) + return lit + + def get_generated_literals(self): + return self.literals + + def get_string_literals(self): # Returns all GENERATED string literals + return [l for l in self.literals + if l.is_string_literal()] + + def get_number_literals(self): # Returns all GENERATED number literals + return [l for l in self.literals + if not l.is_string_literal()] + + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): # In TypeScript, arrays are covariant. @@ -237,3 +323,8 @@ def __init__(self, nr_type_parameters: int): self.nr_type_parameters = nr_type_parameters super().__init__(name, type_parameters) self.supertypes.append(ObjectType()) + + +# Literal Types + +literal_types = LiteralTypes(10, 10) \ No newline at end of file From 38316d1a2d677ba405667de6c626a9dcfeafad9c Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 24 Aug 2022 18:20:14 +0300 Subject: [PATCH 30/78] remove comment --- src/ir/typescript_types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 9a1ec52c..0c9e3e5d 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -86,7 +86,6 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific return types def constant_candidates(self, gen_object): - #from src.ir.ast import IntegerConstant, StringConstant from src.ir import ast return { "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.name), From b18dc82129d286d4af9b9ea8b093ec8cc73fa799 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 25 Aug 2022 15:20:59 +0300 Subject: [PATCH 31/78] add newline to end of file --- src/ir/typescript_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 0c9e3e5d..9f9ec719 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -326,4 +326,4 @@ def __init__(self, nr_type_parameters: int): # Literal Types -literal_types = LiteralTypes(10, 10) \ No newline at end of file +literal_types = LiteralTypes(10, 10) From 6535c3f8b6b4bff3502685736d840b9b4e2cede2 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 29 Aug 2022 13:14:14 +0300 Subject: [PATCH 32/78] Add full support for typescript number and string literal types --- src/generators/generator.py | 7 ++- src/ir/ast.py | 5 +- src/ir/builtins.py | 6 +++ src/ir/typescript_types.py | 88 ++++++++++++++++++++--------------- src/translators/typescript.py | 6 +++ 5 files changed, 68 insertions(+), 44 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index c665eeb8..4a3af147 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -28,7 +28,7 @@ from src.generators import generators as gens from src.generators import utils as gu from src.generators.config import cfg -from src.ir import ast, types as tp, type_utils as tu, kotlin_types as kt +from src.ir import ast, types as tp, type_utils as tu, kotlin_types as kt, typescript_types as tst from src.ir.context import Context from src.ir.builtins import BuiltinFactory from src.ir import BUILTIN_FACTORIES @@ -94,7 +94,7 @@ def generate(self, context=None) -> ast.Program: cfg.limits.max_top_level): self.gen_top_level_declaration() self.generate_main_func() - return ast.Program(self.context, self.language) + return ast.Program(self.context, self.language, self.bt_factory) def gen_top_level_declaration(self): """Generate a top-level declaration and add it in the context. @@ -1893,8 +1893,7 @@ def gen_fun_call(etype): ), self.bt_factory.get_null_type().name: lambda x: ast.Null } - if self.language == 'typescript': - constant_candidates.update(self.bt_factory.constant_candidates(self)) + constant_candidates.update(self.bt_factory.get_constant_candidates(self)) binary_ops = { self.bt_factory.get_boolean_type(): [ lambda x: self.gen_logical_expr(x, only_leaves), diff --git a/src/ir/ast.py b/src/ir/ast.py index da225dc3..ea331b16 100644 --- a/src/ir/ast.py +++ b/src/ir/ast.py @@ -5,7 +5,6 @@ import src.ir.type_utils as tu import src.ir.types as types from src import utils -from src.ir import BUILTIN_FACTORIES from src.ir.builtins import BuiltinFactory, FunctionType from src.ir.node import Node @@ -32,10 +31,10 @@ class Expr(Node): class Program(Node): # Set default value to kotlin for backward compatibility - def __init__(self, context, language): + def __init__(self, context, language, bt_factory): self.context = context self.language = language - self.bt_factory: BuiltinFactory = BUILTIN_FACTORIES[language] + self.bt_factory: BuiltinFactory = bt_factory def children(self): return self.context.get_declarations(GLOBAL_NAMESPACE, diff --git a/src/ir/builtins.py b/src/ir/builtins.py index fc382255..00e9f24f 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -117,6 +117,12 @@ def get_function_types(self, max_parameters): def get_nothing(self): raise NotImplementedError + def get_constant_candidates(self): + """ Overwrite this function to update the generator + constants with language-specific. + """ + return {} + class AnyType(Builtin): def __init__(self, name="Any"): diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 9f9ec719..a6c301b7 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -1,6 +1,7 @@ from src.ir.types import Builtin import src.ir.builtins as bt import src.ir.types as tp +import src.ir.ast as ast import src.utils as ut import random @@ -85,13 +86,12 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific ]) return types - def constant_candidates(self, gen_object): - from src.ir import ast + def get_constant_candidates(self, gen_object): return { - "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.name), - "StringLiteralType": lambda etype: ast.StringConstant(etype.name), + "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.literal, NumberLiteralType), + "StringLiteralType": lambda etype: ast.StringConstant(etype.literal), } - + class TypeScriptBuiltin(Builtin): def __init__(self, name, primitive): @@ -130,6 +130,8 @@ def __init__(self, name="Number", primitive=False): self.supertypes.append(ObjectType()) def is_assignable(self, other): + if isinstance(other, NumberLiteralType): + return False return isinstance(other, NumberType) def box_type(self): @@ -181,6 +183,11 @@ def __init__(self, name="String", primitive=False): def box_type(self): return StringType(self.name, primitive=False) + def is_assignable(self, other): + if isinstance(other, StringLiteralType): + return False + return isinstance(other, StringType) + def get_name(self): if self.is_primitive: return "string" @@ -204,6 +211,7 @@ def get_name(self): class NullType(ObjectType): def __init__(self, name="null", primitive=False): super().__init__(name) + self.primitive = primitive def box_type(self): return NullType(self.name) @@ -215,6 +223,7 @@ def get_name(self): class UndefinedType(ObjectType): def __init__(self, name="undefined", primitive=False): super().__init__(name) + self.primitive = primitive def box_type(self): return UndefinedType(self.name) @@ -223,31 +232,44 @@ def get_name(self): return 'undefined' -class NumberLiteralType(ObjectType): - def __init__(self, name, primitive=False): - super().__init__(str(name)) +class NumberLiteralType(TypeScriptBuiltin): + def __init__(self, literal, name="NumberLiteralType", primitive=False): + super().__init__(name, primitive) + self.literal = literal + self.supertypes.extend([ObjectType(), NumberType()]) + + def get_literal(self): + return self.literal + + def is_assignable(self, other): + return ((isinstance(other, NumberLiteralType) and + other.get_literal() == self.get_literal()) or + isinstance(other, NumberType)) def get_name(self): - return self.name; + return self.name - def is_string_literal(self): - return False +class StringLiteralType(TypeScriptBuiltin): + def __init__(self, literal, name="StringLiteralType", primitive=False): + super().__init__(name, primitive) + self.literal = literal + self.supertypes.extend([ObjectType(), StringType()]) -class StringLiteralType(ObjectType): - def __init__(self, name, primitive=False): - super().__init__(name) + def get_literal(self): + return '"' + self.literal + '"' - def get_name(self): - return '"' + self.name + '"' + def is_assignable(self, other): + return ((isinstance(other, StringLiteralType) and + other.get_literal() == self.get_literal()) or + isinstance(other, StringType)) - def is_string_literal(self): - return True + def get_name(self): + return self.name class LiteralTypes: def __init__(self, str_limit, num_limit): - self.literals = [] self.str_literals = [] self.num_literals = [] # Define max number for generated literals @@ -255,11 +277,13 @@ def __init__(self, str_limit, num_limit): self.num_limit = num_limit def get_literal(self): + sl = self.gen_string_literal() + nl = self.gen_number_literal() if ut.random.bool(): - return self.get_string_literal() - return self.get_number_literal() + return sl + return nl - def get_string_literal(self): + def gen_string_literal(self): lit = None if (len(self.str_literals) == 0 or (len(self.str_literals) < self.str_limit and @@ -268,13 +292,12 @@ def get_string_literal(self): # has not been surpassed, we can randomly # generate a new one. lit = StringLiteralType(ut.random.word().lower()) - self.literals.append(lit) self.str_literals.append(lit) else: lit = random.choice(self.str_literals) return lit - def get_number_literal(self): + def gen_number_literal(self): lit = None if (len(self.num_literals) == 0 or (len(self.num_literals) < self.num_limit and @@ -283,23 +306,11 @@ def get_number_literal(self): # has not been surpassed, we can randomly # generate a new one. lit = NumberLiteralType(ut.random.integer(-100, 100)) - self.literals.append(lit) self.num_literals.append(lit) else: lit = random.choice(self.num_literals) return lit - def get_generated_literals(self): - return self.literals - - def get_string_literals(self): # Returns all GENERATED string literals - return [l for l in self.literals - if l.is_string_literal()] - - def get_number_literals(self): # Returns all GENERATED number literals - return [l for l in self.literals - if not l.is_string_literal()] - class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): @@ -326,4 +337,7 @@ def __init__(self, nr_type_parameters: int): # Literal Types -literal_types = LiteralTypes(10, 10) +# TODO make these limits user-configurable +MAX_STRING_LITERAL_TYPES = 10 +MAX_NUM_LITERAL_TYPES = 10 +literal_types = LiteralTypes(MAX_STRING_LITERAL_TYPES, MAX_NUM_LITERAL_TYPES) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index f920fc1a..a6a4c3f9 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -70,12 +70,18 @@ def get_incorrect_filename(): def type_arg2str(self, t_arg): # TypeScript does not have a Wildcard type + if (isinstance(t_arg, tst.StringLiteralType) or + isinstance(t_arg, tst.NumberLiteralType)): + return str(t_arg.get_literal()) if not t_arg.is_wildcard(): return self.get_type_name(t_arg) return "unknown" def get_type_name(self, t): t_constructor = getattr(t, 't_constructor', None) + if (isinstance(t, tst.NumberLiteralType) or + isinstance(t, tst.StringLiteralType)): + return str(t.get_literal()) if not t_constructor: return t.get_name() From 15fdb86d2bc38e7e090994cc5f611f8ad3e6e0da Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 30 Aug 2022 13:35:09 +0300 Subject: [PATCH 33/78] Provide generator with both a string and number literal type, change num and string LiteralType supertypes, add comments --- src/ir/typescript_types.py | 53 +++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index a6c301b7..35f74fb1 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -81,9 +81,8 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types = super().get_non_nothing_types() types.extend([ self.get_null_type(), - UndefinedType(primitive=False), - literal_types.get_literal(), - ]) + UndefinedType(primitive=False) + ]+literal_types.get_literal()) return types def get_constant_candidates(self, gen_object): @@ -236,14 +235,30 @@ class NumberLiteralType(TypeScriptBuiltin): def __init__(self, literal, name="NumberLiteralType", primitive=False): super().__init__(name, primitive) self.literal = literal - self.supertypes.extend([ObjectType(), NumberType()]) + self.supertypes.append(NumberType()) def get_literal(self): return self.literal def is_assignable(self, other): + """ A number literal type is assignable to any + supertype of type 'number'. + + It is also assignable to other number literal types, + as long as the other type's literal is the same. + + eg. let num: number + let litA: 23 = 23 + let litB: 23 + num = litA (correct) + litB = litA (correct) + + litA is assignable to litB because their literal + is the same, 23. + + """ return ((isinstance(other, NumberLiteralType) and - other.get_literal() == self.get_literal()) or + other.get_literal() == self.get_literal()) or isinstance(other, NumberType)) def get_name(self): @@ -254,21 +269,37 @@ class StringLiteralType(TypeScriptBuiltin): def __init__(self, literal, name="StringLiteralType", primitive=False): super().__init__(name, primitive) self.literal = literal - self.supertypes.extend([ObjectType(), StringType()]) + self.supertypes.append(StringType()) def get_literal(self): return '"' + self.literal + '"' def is_assignable(self, other): + """ A string literal type is assignable to any + supertype of type 'string'. + + It is also assignablde to other string literal types, + as long as the other type's literal is the same. + + eg. let str: string + let litA: "PULL" = "PULL" + let litB: "PULL" + str = litA (correct) + litB = litA (correct) + + litA is assignable to litB because their literal + is the same, "PULL". + + """ return ((isinstance(other, StringLiteralType) and - other.get_literal() == self.get_literal()) or + other.get_literal() == self.get_literal()) or isinstance(other, StringType)) def get_name(self): return self.name -class LiteralTypes: +class LiteralTypeFactory: def __init__(self, str_limit, num_limit): self.str_literals = [] self.num_literals = [] @@ -279,9 +310,7 @@ def __init__(self, str_limit, num_limit): def get_literal(self): sl = self.gen_string_literal() nl = self.gen_number_literal() - if ut.random.bool(): - return sl - return nl + return [sl, nl] def gen_string_literal(self): lit = None @@ -340,4 +369,4 @@ def __init__(self, nr_type_parameters: int): # TODO make these limits user-configurable MAX_STRING_LITERAL_TYPES = 10 MAX_NUM_LITERAL_TYPES = 10 -literal_types = LiteralTypes(MAX_STRING_LITERAL_TYPES, MAX_NUM_LITERAL_TYPES) +literal_types = LiteralTypeFactory(MAX_STRING_LITERAL_TYPES, MAX_NUM_LITERAL_TYPES) From 94b2ea102f8a36ea55d51b18d664c063a705eefd Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 30 Aug 2022 13:55:39 +0300 Subject: [PATCH 34/78] Rename method and add spaces --- src/ir/typescript_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 35f74fb1..5725b091 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -82,7 +82,7 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types.extend([ self.get_null_type(), UndefinedType(primitive=False) - ]+literal_types.get_literal()) + ] + literal_types.get_literal_types()) return types def get_constant_candidates(self, gen_object): @@ -307,7 +307,7 @@ def __init__(self, str_limit, num_limit): self.str_limit = str_limit self.num_limit = num_limit - def get_literal(self): + def get_literal_types(self): sl = self.gen_string_literal() nl = self.gen_number_literal() return [sl, nl] From dd45e0a47033475efcc09fc7e2ba704ed9b2f881 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 15 Sep 2022 12:39:31 +0300 Subject: [PATCH 35/78] Fix subtyping bugfor type aliases --- src/ir/types.py | 2 +- src/ir/typescript_types.py | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/ir/types.py b/src/ir/types.py index c145b12c..4a48f87f 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -276,7 +276,7 @@ def get_bound_rec(self, factory): def is_subtype(self, other): if not self.bound: return False - return self.bound == other + return self.bound.is_subtype(other) def __eq__(self, other): return (self.__class__ == other.__class__ and diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 2acf374f..b75660c9 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -4,6 +4,7 @@ import src.ir.builtins as bt import src.ir.types as tp import src.generators.utils as gu +import src.utils as ut class TypeScriptBuiltinFactory(bt.BuiltinFactory): @@ -226,8 +227,8 @@ def get_name(self): class TypeAlias(ObjectType): - def __init__(self, alias, name, primitive=False): - super().__init__(name) + def __init__(self, alias, name="TypeAlias", primitive=False): + super().__init__() self.alias = alias self.name = name self.primitive = primitive @@ -236,6 +237,8 @@ def get_type(self): return self.alias def is_subtype(self, other): + if isinstance(other, TypeAlias): + return self.alias.is_subtype(other.alias) return self.alias.is_subtype(other) def box_type(self): @@ -244,6 +247,13 @@ def box_type(self): def get_name(self): return self.name + def __eq__(self, other): + return (isinstance(other, TypeAlias) and + self.alias == other.alias) + + def __hash__(self): + return hash(str(self.name) + str(self.alias)) + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): @@ -287,14 +297,24 @@ def gen_type_alias_decl(gen_object, An AST node that describes a type alias declaration as defined in src.ir.typescript_ast.py """ - alias_type = etype if etype else gen_object.select_type() + candidates = [ + NumberType(), + BooleanType(), + StringType(), + NullType(), + UndefinedType(primitive=False), + ] + alias_type = (etype if etype else + ut.random.choice(candidates) + ) initial_depth = gen_object.depth gen_object.depth += 1 gen_object.depth = initial_depth type_alias_decl = ts_ast.TypeAliasDeclaration( name=gu.gen_identifier('lower'), - alias=alias_type) + alias=alias_type + ) gen_object._add_node_to_parent(gen_object.namespace, type_alias_decl) return type_alias_decl From d25ca76487ce99efdbb6c15ebe2879fa029d6bb8 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 16 Sep 2022 19:25:12 +0300 Subject: [PATCH 36/78] Add requested type alias changes --- src/generators/generator.py | 62 ++++++++++------------------------- src/ir/builtins.py | 4 +-- src/ir/typescript_ast.py | 10 +++++- src/ir/typescript_types.py | 43 ++++++++++-------------- src/translators/typescript.py | 34 +------------------ 5 files changed, 48 insertions(+), 105 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 94297855..4a4cd16a 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -110,24 +110,13 @@ def gen_top_level_declaration(self): declarations. """ candidates = [ - self.gen_variable_decl, - self.gen_class_decl, - self.gen_func_decl, + lambda gen: self.gen_variable_decl(), + lambda gen: self.gen_class_decl(), + lambda gen: self.gen_func_decl(), ] - lang_specific_decls = self.bt_factory.get_decl_candidates(self) - candidates.extend(lang_specific_decls) + candidates.extend(self.bt_factory.get_decl_candidates()) gen_func = ut.random.choice(candidates) - if gen_func in lang_specific_decls: - # If the randomly chosen decl generator is - # provided by the builtin factory then it - # generates a decl for a language-specific - # feature. Hence, we must provide the generator's - # instance (self) as an argumet manually so the - # function has access to the generator's attributes - # and methods. - gen_func(self) - else: - gen_func() + gen_func(self) def generate_main_func(self) -> ast.FunctionDeclaration: """Generate the main function. @@ -549,37 +538,22 @@ def _add_node_to_class(self, cls, node): def _add_node_to_parent(self, parent_namespace, node): node_type = { - ast.FunctionDeclaration: self.context.add_func, - ast.ClassDeclaration: self.context.add_class, - ast.VariableDeclaration: self.context.add_var, - ast.FieldDeclaration: self.context.add_var, - ast.ParameterDeclaration: self.context.add_var, - ast.Lambda: self.context.add_lambda, + ast.FunctionDeclaration: lambda gen, p, n, nd: self.context.add_func(p, n, nd), + ast.ClassDeclaration: lambda gen, p, n, nd: self.context.add_class(p, n, nd), + ast.VariableDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), + ast.FieldDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), + ast.ParameterDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), + ast.Lambda: lambda gen, p, n, nd: self.context.add_lambda(p, n, nd), } - lang_specific_pairs = self.bt_factory.update_add_node_to_parent() - node_type.update(lang_specific_pairs) - lang_specific = type(node) in lang_specific_pairs - # If the node is a language-specific AST node - # then we must provide the self.context - # as well so the method provided by the language - # builtin factory has access to the context's - # other attributes and methods - - if parent_namespace == ast.GLOBAL_NAMESPACE and not lang_specific: - node_type[type(node)](parent_namespace, node.name, node) - return - elif parent_namespace == ast.GLOBAL_NAMESPACE: - node_type[type(node)](self.context, parent_namespace, node.name, node) + node_type.update(self.bt_factory.update_add_node_to_parent()) + if parent_namespace == ast.GLOBAL_NAMESPACE: + node_type[type(node)](self, parent_namespace, node.name, node) return parent = self.context.get_decl(parent_namespace[:-1], parent_namespace[-1]) if parent and isinstance(parent, ast.ClassDeclaration): self._add_node_to_class(parent, node) - - if lang_specific: - node_type[type(node)](self.context, parent_namespace, node.name, node) - return - node_type[type(node)](parent_namespace, node.name, node) + node_type[type(node)](self, parent_namespace, node.name, node) # And @@ -1565,7 +1539,7 @@ def _gen_func_call(self, if param.default and self.language != 'typescript': if self.language == 'kotlin' and ut.random.bool(): # Randomly skip some default arguments. - args.append(ast.CallArgument(arg, name=param.name)) + args.append(ast.CallArgument(arg, name=param.name)) else: args.append(ast.CallArgument(arg)) @@ -1919,9 +1893,9 @@ def gen_fun_call(etype): self.bt_factory.get_array_type().name: ( lambda x: self.gen_array_expr(x, only_leaves, subtype=subtype) ), - self.bt_factory.get_null_type().name: lambda x: ast.Null + self.bt_factory.get_null_type().name: lambda x: ast.Null } - constant_candidates.update(self.bt_factory.get_constant_candidates(self)) + constant_candidates.update(self.bt_factory.get_constant_candidates()) binary_ops = { self.bt_factory.get_boolean_type(): [ lambda x: self.gen_logical_expr(x, only_leaves), diff --git a/src/ir/builtins.py b/src/ir/builtins.py index 084416e5..12fa9c77 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -111,7 +111,7 @@ def get_number_types(self): self.get_big_integer_type(), ] - def get_decl_candidates(self, gen_object): + def get_decl_candidates(self): """ Overwrite this method to return a list with language-specific AST declaration nodes. @@ -121,7 +121,7 @@ def get_decl_candidates(self, gen_object): """ return [] - def update_add_node_to_parent(self, gen_object): + def update_add_node_to_parent(self): """ Overwrite this to update the dict 'node_type' on src.generators.generator._add_node_to_parent with the respective diff --git a/src/ir/typescript_ast.py b/src/ir/typescript_ast.py index 56c6268a..51e989d1 100644 --- a/src/ir/typescript_ast.py +++ b/src/ir/typescript_ast.py @@ -12,8 +12,16 @@ def children(self): return [self.alias] def get_type(self): - return tst.TypeAlias(self.alias, self.name) + return tst.AliasType(self.alias, self.name) def update_children(self, children): super().update_children(children) self.alias = children[0] + + def is_equal(self, other): + if isinstance(other, TypeAliasDeclaration): + return (self.name == other.name and + self.alias == other.alias) + + def __str__(self): + return f'{self.name} (TypeAliasDecl<{str(self.alias)}>)' diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 1bb86b1c..4a6bace1 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -85,7 +85,7 @@ def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific ] + literal_types.get_literal_types()) return types - def get_decl_candidates(self, gen_object): + def get_decl_candidates(self): return [gen_type_alias_decl,] def update_add_node_to_parent(self): @@ -93,7 +93,7 @@ def update_add_node_to_parent(self): ts_ast.TypeAliasDeclaration: add_type_alias, } - def get_constant_candidates(self, gen_object): + def get_constant_candidates(self): return { "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.literal, NumberLiteralType), "StringLiteralType": lambda etype: ast.StringConstant(etype.literal), @@ -387,8 +387,8 @@ def __init__(self, nr_type_parameters: int): self.supertypes.append(ObjectType()) -class TypeAlias(ObjectType): - def __init__(self, alias, name="TypeAlias", primitive=False): +class AliasType(ObjectType): + def __init__(self, alias, name="AliasType", primitive=False): super().__init__() self.alias = alias self.name = name @@ -398,18 +398,18 @@ def get_type(self): return self.alias def is_subtype(self, other): - if isinstance(other, TypeAlias): + if isinstance(other, AliasType): return self.alias.is_subtype(other.alias) return self.alias.is_subtype(other) def box_type(self): - return TypeAlias(self.alias, self.name) + return AliasType(self.alias, self.name) def get_name(self): return self.name def __eq__(self, other): - return (isinstance(other, TypeAlias) and + return (isinstance(other, AliasType) and self.alias == other.alias) def __hash__(self): @@ -424,7 +424,7 @@ def __hash__(self): features of typescript. """ -def gen_type_alias_decl(gen_object, +def gen_type_alias_decl(gen, etype=None) -> ts_ast.TypeAliasDeclaration: """ Generate a Type Declaration (Type Alias) @@ -434,31 +434,24 @@ def gen_type_alias_decl(gen_object, Returns: An AST node that describes a type alias declaration as defined in src.ir.typescript_ast.py + """ - candidates = [ - NumberType(), - BooleanType(), - StringType(), - NullType(), - UndefinedType(primitive=False), - ] + literal_types.get_literal_types() alias_type = (etype if etype else - ut.random.choice(candidates) + gen.select_type() ) - initial_depth = gen_object.depth - gen_object.depth += 1 - - gen_object.depth = initial_depth + initial_depth = gen.depth + gen.depth += 1 + gen.depth = initial_depth type_alias_decl = ts_ast.TypeAliasDeclaration( - name=gu.gen_identifier('lower'), + name=gu.gen_identifier('capitalize'), alias=alias_type ) - gen_object._add_node_to_parent(gen_object.namespace, type_alias_decl) + gen._add_node_to_parent(gen.namespace, type_alias_decl) return type_alias_decl -def add_type_alias(context, namespace, type_name, ta_decl): - context._add_entity(namespace, 'types', type_name, ta_decl.get_type()) - context._add_entity(namespace, 'decls', type_name, ta_decl) +def add_type_alias(gen, namespace, type_name, ta_decl): + gen.context._add_entity(namespace, 'types', type_name, ta_decl.get_type()) + gen.context._add_entity(namespace, 'decls', type_name, ta_decl) # Literal Types diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 70a65afb..b47ee2ea 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -39,39 +39,7 @@ def _reset_state(self): def get_visitors(self): # Overwriting method of ASTVisitor class # to add typescript-specific visitors - visitors = { - ast.SuperClassInstantiation: self.visit_super_instantiation, - ast.ClassDeclaration: self.visit_class_decl, - types.TypeParameter: self.visit_type_param, - ast.CallArgument: self.visit_call_argument, - ast.FieldDeclaration: self.visit_field_decl, - ast.VariableDeclaration: self.visit_var_decl, - ast.ParameterDeclaration: self.visit_param_decl, - ast.FunctionDeclaration: self.visit_func_decl, - ast.Lambda: self.visit_lambda, - ast.FunctionReference: self.visit_func_ref, - ast.BottomConstant: self.visit_bottom_constant, - ast.IntegerConstant: self.visit_integer_constant, - ast.NullConstant: self.visit_null_constant, - ast.RealConstant: self.visit_real_constant, - ast.CharConstant: self.visit_char_constant, - ast.StringConstant: self.visit_string_constant, - ast.ArrayExpr: self.visit_array_expr, - ast.BooleanConstant: self.visit_boolean_constant, - ast.Variable: self.visit_variable, - ast.LogicalExpr: self.visit_logical_expr, - ast.EqualityExpr: self.visit_equality_expr, - ast.ComparisonExpr: self.visit_comparison_expr, - ast.ArithExpr: self.visit_arith_expr, - ast.Conditional: self.visit_conditional, - ast.Is: self.visit_is, - ast.New: self.visit_new, - ast.FieldAccess: self.visit_field_access, - ast.FunctionCall: self.visit_func_call, - ast.Assignment: self.visit_assign, - ast.Program: self.visit_program, - ast.Block: self.visit_block, - } + visitors = super().get_visitors() visitors.update({ ts_ast.TypeAliasDeclaration: self.visit_type_alias_decl, }) From 53ac9b18ecd3a28c53738ef898278ae1704030d5 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 19 Sep 2022 13:01:02 +0300 Subject: [PATCH 37/78] Add Type Alias Unit Tests --- tests/test_typescript.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 tests/test_typescript.py diff --git a/tests/test_typescript.py b/tests/test_typescript.py new file mode 100644 index 00000000..60ad51f8 --- /dev/null +++ b/tests/test_typescript.py @@ -0,0 +1,14 @@ +import src.ir.typescript_types as tst +import src.ir.typescript_ast as ts_ast + +def test_type_alias_with_literals(): + string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringType()).get_type() + number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberType()).get_type() + + string_lit = tst.StringLiteralType("foo") + number_lit = tst.NumberLiteralType(5) + + assert string_lit.is_subtype(string_alias) + assert not string_alias.is_subtype(string_lit) + assert number_lit.is_subtype(number_alias) + assert not number_alias.is_subtype(number_lit) From a3350e9ddbe254f78dc6b832d0b6dab806d726cb Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 19 Sep 2022 14:04:31 +0300 Subject: [PATCH 38/78] Move method gen_identifier to src.utils.RandomUtils --- src/generators/generator.py | 12 ++--- src/generators/generators.py | 2 +- src/generators/utils.py | 20 -------- src/ir/typescript_types.py | 3 +- src/utils.py | 19 ++++++++ tests/resources/program1.py | 2 +- tests/resources/program10.py | 2 +- tests/resources/program11.py | 2 +- tests/resources/program12.py | 2 +- tests/resources/program2.py | 2 +- tests/resources/program3.py | 2 +- tests/resources/program4.py | 2 +- tests/resources/program5.py | 2 +- tests/resources/program6.py | 2 +- tests/resources/program7.py | 2 +- tests/resources/program8.py | 2 +- tests/resources/program9.py | 2 +- tests/resources/type_analysis_programs.py | 58 +++++++++++------------ tests/test_type_utils.py | 4 +- 19 files changed, 70 insertions(+), 72 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 4a4cd16a..f96601ce 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -216,7 +216,7 @@ def gen_func_decl(self, Returns: A function declaration node. """ - func_name = func_name or gu.gen_identifier('lower') + func_name = func_name or ut.random.identifier('lower') initial_namespace = self.namespace if namespace: @@ -340,7 +340,7 @@ def gen_param_decl(self, etype=None) -> ast.ParameterDeclaration: Args: etype: Parameter type. """ - name = gu.gen_identifier('lower') + name = ut.random.identifier('lower') if etype and etype.is_wildcard(): bound = etype.get_bound_rec() param_type = bound or self.select_type(exclude_covariants=True) @@ -373,7 +373,7 @@ def gen_class_decl(self, Returns: A class declaration node. """ - class_name = class_name or gu.gen_identifier('capitalize') + class_name = class_name or ut.random.identifier('capitalize') initial_namespace = self.namespace self.namespace += (class_name,) initial_depth = self.depth @@ -775,7 +775,7 @@ def gen_field_decl(self, etype=None, etype: Field type. class_is_final: Is the class final. """ - name = gu.gen_identifier('lower') + name = ut.random.identifier('lower') can_override = not class_is_final and ut.random.bool() is_final = ut.random.bool() field_type = etype or self.select_type(exclude_contravariants=True, @@ -815,7 +815,7 @@ def gen_variable_decl(self, vtype = var_type.get_bound_rec() if var_type.is_wildcard() else \ var_type var_decl = ast.VariableDeclaration( - gu.gen_identifier('lower'), + ut.random.identifier('lower'), expr=expr, is_final=is_final, var_type=vtype, @@ -2798,7 +2798,7 @@ def _gen_matching_class(self, declaration (field or function). """ initial_namespace = self.namespace - class_name = gu.gen_identifier('capitalize') + class_name = ut.random.identifier('capitalize') type_params = None # Get return type, type_var_map, and flag for wildcards diff --git a/src/generators/generators.py b/src/generators/generators.py index 32661cd0..5090f7db 100644 --- a/src/generators/generators.py +++ b/src/generators/generators.py @@ -15,7 +15,7 @@ def gen_string_constant(expr_type=None) -> ast.StringConstant: """Generate a string constant. """ - return ast.StringConstant(gu.gen_identifier()) + return ast.StringConstant(utils.random.identifier()) # pylint: disable=unused-argument diff --git a/src/generators/utils.py b/src/generators/utils.py index 08208131..3fe3a537 100644 --- a/src/generators/utils.py +++ b/src/generators/utils.py @@ -98,23 +98,3 @@ def init_variance_choices(type_var_map: tu.TypeVarMap) -> tu.VarianceChoices: type_var = type_var.bound variance_choices[type_var] = (False, False) return variance_choices - - -def gen_identifier(ident_type:str=None) -> str: - """Generate an identifier name. - - Args: - ident_type: None or 'capitalize' or 'lower' - - Raises: - AssertionError: Raises an AssertionError if the ident_type is neither - 'capitalize' nor 'lower'. - """ - word = ut.random.word() - if ident_type is None: - return word - if ident_type == 'lower': - return word.lower() - if ident_type == 'capitalize': - return word.capitalize() - raise AssertionError("ident_type should be 'capitalize' or 'lower'") diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 4a6bace1..534113f0 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -2,7 +2,6 @@ import src.ir.typescript_ast as ts_ast import src.ir.builtins as bt import src.ir.types as tp -import src.generators.utils as gu import src.utils as ut class TypeScriptBuiltinFactory(bt.BuiltinFactory): @@ -443,7 +442,7 @@ def gen_type_alias_decl(gen, gen.depth += 1 gen.depth = initial_depth type_alias_decl = ts_ast.TypeAliasDeclaration( - name=gu.gen_identifier('capitalize'), + name=ut.random.identifier('capitalize'), alias=alias_type ) gen._add_node_to_parent(gen.namespace, type_alias_decl) diff --git a/src/utils.py b/src/utils.py index bb5f4573..40cfe306 100644 --- a/src/utils.py +++ b/src/utils.py @@ -167,6 +167,25 @@ def caps(self, length=1, blacklist=None): def range(self, from_value, to_value): return range(0, self.integer(from_value, to_value)) + def identifier(self, ident_type:str=None) -> str: + """Generate an identifier name. + + Args: + ident_type: None or 'capitalize' or 'lower' + + Raises: + AssertionError: Raises an AssertionError if the ident_type is neither + 'capitalize' nor 'lower'. + """ + word = self.word() + if ident_type is None: + return word + if ident_type == 'lower': + return word.lower() + if ident_type == 'capitalize': + return word.capitalize() + raise AssertionError("ident_type should be 'capitalize' or 'lower'") + random = RandomUtils() diff --git a/tests/resources/program1.py b/tests/resources/program1.py index 475a1ac7..b9a2b99e 100644 --- a/tests/resources/program1.py +++ b/tests/resources/program1.py @@ -118,4 +118,4 @@ ctx.add_func(GLOBAL_NAMESPACE + ('A',), foo_func.name, foo_func) ctx.add_func(GLOBAL_NAMESPACE + ('A',), buz_func.name, buz_func) ctx.add_func(GLOBAL_NAMESPACE + ('A',), spam_func.name, spam_func) -program = Program(ctx, language="kotlin") +program = Program(ctx, "kotlin", KotlinBuiltinFactory()) diff --git a/tests/resources/program10.py b/tests/resources/program10.py index 591eea19..789541b9 100644 --- a/tests/resources/program10.py +++ b/tests/resources/program10.py @@ -126,4 +126,4 @@ ctx.add_var(ast.GLOBAL_NAMESPACE + ("Third",), third_z.name, third_z) ctx.add_func(ast.GLOBAL_NAMESPACE + ("Third",), third_foo.name, third_foo) ctx.add_var(ast.GLOBAL_NAMESPACE + ("Third", "foo"), third_foo_k.name, third_foo_k) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program11.py b/tests/resources/program11.py index c5c97478..2a39a327 100644 --- a/tests/resources/program11.py +++ b/tests/resources/program11.py @@ -87,4 +87,4 @@ ctx.add_var(ast.GLOBAL_NAMESPACE + ("foo",), foo_x.name, foo_x) ctx.add_func(ast.GLOBAL_NAMESPACE, "bar", fun_bar) ctx.add_var(ast.GLOBAL_NAMESPACE + ("bar",), bar_y.name, bar_y) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program12.py b/tests/resources/program12.py index 18a3faba..7f6a98b0 100644 --- a/tests/resources/program12.py +++ b/tests/resources/program12.py @@ -87,4 +87,4 @@ ctx.add_func(ast.GLOBAL_NAMESPACE, "foo", fun_foo) ctx.add_var(ast.GLOBAL_NAMESPACE + ("foo",), foo_x.name, foo_x) ctx.add_func(ast.GLOBAL_NAMESPACE, "bar", fun_bar) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program2.py b/tests/resources/program2.py index fb01532e..fb19e916 100644 --- a/tests/resources/program2.py +++ b/tests/resources/program2.py @@ -44,4 +44,4 @@ ctx.add_var(GLOBAL_NAMESPACE + ('Bam',), 'x', xB_field) ctx.add_var(GLOBAL_NAMESPACE + ('Bam', 'getX'), 'z', z_get_param) ctx.add_func(GLOBAL_NAMESPACE + ('Bam',), getX_func.name, getX_func) -program = Program(ctx, language="kotlin") +program = Program(ctx, "kotlin", KotlinBuiltinFactory()) diff --git a/tests/resources/program3.py b/tests/resources/program3.py index 3f6728c4..25b26fb8 100644 --- a/tests/resources/program3.py +++ b/tests/resources/program3.py @@ -73,4 +73,4 @@ ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "bar"), bar_x.name, bar_x) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "bar"), bar_z.name, bar_z) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "bar"), bar_y.name, bar_y) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program4.py b/tests/resources/program4.py index 672bc09d..a4e97f4e 100644 --- a/tests/resources/program4.py +++ b/tests/resources/program4.py @@ -38,4 +38,4 @@ ctx.add_func(ast.GLOBAL_NAMESPACE + ("A",), fun_foo.name, fun_foo) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A",), field_x.name, field_x) ctx.add_func(ast.GLOBAL_NAMESPACE + ("A", "foo"), fun_bar.name, fun_bar) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program5.py b/tests/resources/program5.py index 0237127b..b461fa76 100644 --- a/tests/resources/program5.py +++ b/tests/resources/program5.py @@ -82,4 +82,4 @@ ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "bar"), bar_z.name, bar_z) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "quz"), quz_y.name, quz_y) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program6.py b/tests/resources/program6.py index 2a171fb5..1bfba86a 100644 --- a/tests/resources/program6.py +++ b/tests/resources/program6.py @@ -59,4 +59,4 @@ ctx.add_func(ast.GLOBAL_NAMESPACE + ("A",), fun_baz.name, fun_baz) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A",), field_x.name, field_x) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "bar"), bar_y.name, bar_y) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program7.py b/tests/resources/program7.py index e300fb6d..28c61b2d 100644 --- a/tests/resources/program7.py +++ b/tests/resources/program7.py @@ -42,4 +42,4 @@ ctx.add_func(ast.GLOBAL_NAMESPACE + ("A",), fun_bar.name, fun_bar) ctx.add_func(ast.GLOBAL_NAMESPACE + ("A",), fun_foo.name, fun_foo) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "foo",), foo_x.name, foo_x) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program8.py b/tests/resources/program8.py index 45dea83d..83de0a3b 100644 --- a/tests/resources/program8.py +++ b/tests/resources/program8.py @@ -33,4 +33,4 @@ ctx.add_class(ast.GLOBAL_NAMESPACE, "A", cls) ctx.add_func(ast.GLOBAL_NAMESPACE + ("A",), fun_foo.name, fun_foo) ctx.add_var(ast.GLOBAL_NAMESPACE + ("A", "foo",), foo_x.name, foo_x) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/program9.py b/tests/resources/program9.py index 4ed2eb8c..547f39b9 100644 --- a/tests/resources/program9.py +++ b/tests/resources/program9.py @@ -63,4 +63,4 @@ ctx.add_var(ast.GLOBAL_NAMESPACE + ("foo",), foo_lank.name, foo_lank) ctx.add_func(ast.GLOBAL_NAMESPACE, "bar", fun_bar) ctx.add_var(ast.GLOBAL_NAMESPACE + ("bar",), bar_cinches.name, bar_cinches) -program = ast.Program(ctx, language="kotlin") +program = ast.Program(ctx, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/resources/type_analysis_programs.py b/tests/resources/type_analysis_programs.py index 65df429b..31673be1 100644 --- a/tests/resources/type_analysis_programs.py +++ b/tests/resources/type_analysis_programs.py @@ -12,7 +12,7 @@ context = ctx.Context() context.add_var(ast.GLOBAL_NAMESPACE, var_decl.name, var_decl) context.add_class(ast.GLOBAL_NAMESPACE, cls.name, cls) -program1 = ast.Program(context, "kotlin") +program1 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program2 @@ -28,7 +28,7 @@ context.add_var(ast.GLOBAL_NAMESPACE, var_x.name, var_x) context.add_var(ast.GLOBAL_NAMESPACE, var_y.name, var_y) context.add_class(ast.GLOBAL_NAMESPACE, cls.name, cls) -program2 = ast.Program(context, "kotlin") +program2 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program3 @@ -41,7 +41,7 @@ context.add_var(ast.GLOBAL_NAMESPACE, var_y.name, var_y2) context.add_class(ast.GLOBAL_NAMESPACE, cls.name, cls) context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) -program3 = ast.Program(context, "kotlin") +program3 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program4 @@ -62,7 +62,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_class(ast.GLOBAL_NAMESPACE, cls3.name, cls3) -program4 = ast.Program(context, "kotlin") +program4 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program5 @@ -85,7 +85,7 @@ context.add_var(ast.GLOBAL_NAMESPACE, var2.name, var2) context.add_var(FUNC_NAMESPACE, param1.name, param1) context.add_var(FUNC_NAMESPACE, var1.name, var1) -program5 = ast.Program(context, "kotlin") +program5 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program6 @@ -105,7 +105,7 @@ context.add_var(ast.GLOBAL_NAMESPACE, var.name, var) context.add_func(ast.GLOBAL_NAMESPACE + (cls1.name,), func.name, func) context.add_var(FUNC_NAMESPACE, param1.name, param1) -program6 = ast.Program(context, "kotlin") +program6 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program7 @@ -119,7 +119,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) context.add_var(ast.GLOBAL_NAMESPACE, var2.name, var2) -program7 = ast.Program(context, "kotlin") +program7 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 8 @@ -135,7 +135,7 @@ context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_var(FUNC_NAMESPACE, var1.name, var1) -program8 = ast.Program(context, "kotlin") +program8 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program9 @@ -158,7 +158,7 @@ context.add_var(ast.GLOBAL_NAMESPACE + (cls2.name,), f.name, f) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(FUNC_NAMESPACE, var1.name, var1) -program9 = ast.Program(context, "kotlin") +program9 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program10 @@ -176,7 +176,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_var(ast.GLOBAL_NAMESPACE + (cls1.name,), f.name, f) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) -program10 = ast.Program(context, "kotlin") +program10 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 11 @@ -191,7 +191,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_var(ast.GLOBAL_NAMESPACE + (cls1.name,), f.name, f) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) -program11 = ast.Program(context, "kotlin") +program11 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 12 @@ -217,7 +217,7 @@ context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE + (cls1.name,), f.name, f) context.add_var(FUNC_NAMESPACE, var1.name, var1) -program12 = ast.Program(context, "kotlin") +program12 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 13 @@ -251,7 +251,7 @@ context.add_var(FUNC_NAMESPACE, var1.name, var1) context.add_var(FUNC_NAMESPACE, var2.name, var2) context.add_var(FUNC_NAMESPACE, var3.name, var3) -program13 = ast.Program(context, "kotlin") +program13 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 14 @@ -265,7 +265,7 @@ context = ctx.Context() context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(FUNC_NAMESPACE, var1.name, var1) -program14 = ast.Program(context, "kotlin") +program14 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program15 @@ -280,7 +280,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_var(ast.GLOBAL_NAMESPACE + (cls1.name,), f.name, f) context.add_var(ast.GLOBAL_NAMESPACE, var.name, var) -program15 = ast.Program(context, "kotlin") +program15 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program16 @@ -298,7 +298,7 @@ context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_func(ast.GLOBAL_NAMESPACE, func2.name, func2) context.add_var(ast.GLOBAL_NAMESPACE + (func.name,), param1.name, param1) -program16 = ast.Program(context, "kotlin") +program16 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 17 @@ -315,7 +315,7 @@ context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_func(ast.GLOBAL_NAMESPACE, func2.name, func2) context.add_var(ast.GLOBAL_NAMESPACE + (func.name,), param1.name, param1) -program17 = ast.Program(context, "kotlin") +program17 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 18 @@ -336,7 +336,7 @@ context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_func(ast.GLOBAL_NAMESPACE, func2.name, func2) context.add_var(ast.GLOBAL_NAMESPACE + (func.name,), param1.name, param1) -program18 = ast.Program(context, "kotlin") +program18 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 19 @@ -359,7 +359,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls3.name, cls3) context.add_var(ast.GLOBAL_NAMESPACE + (cls3.name,), f.name, f) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program19 = ast.Program(context, "kotlin") +program19 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 20 @@ -377,7 +377,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_var(ast.GLOBAL_NAMESPACE + (cls2.name,), cls2.name, cls2) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program20 = ast.Program(context, "kotlin") +program20 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 21 @@ -396,7 +396,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls3.name, cls3) context.add_var(ast.GLOBAL_NAMESPACE + (cls3.name,), cls3.name, cls3) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program21 = ast.Program(context, "kotlin") +program21 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 22 @@ -415,7 +415,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_var(ast.GLOBAL_NAMESPACE + (cls2.name,), f.name, f) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program22 = ast.Program(context, "kotlin") +program22 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 23 @@ -429,7 +429,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program23 = ast.Program(context, "kotlin") +program23 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 24 @@ -444,7 +444,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program24 = ast.Program(context, "kotlin") +program24 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 25 @@ -459,7 +459,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls1.name, cls1) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program25 = ast.Program(context, "kotlin") +program25 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 26 @@ -481,7 +481,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program26 = ast.Program(context, "kotlin") +program26 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 27 @@ -505,7 +505,7 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_func(ast.GLOBAL_NAMESPACE, func.name, func) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program27 = ast.Program(context, "kotlin") +program27 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 28 @@ -531,7 +531,7 @@ context.add_var(ast.GLOBAL_NAMESPACE + (cls2.name,), f.name, f) context.add_func(ast.GLOBAL_NAMESPACE + (cls3.name,), func.name, func) context.add_var(ast.GLOBAL_NAMESPACE + (cls3.name, func.name), var1.name, var1) -program28 = ast.Program(context, "kotlin") +program28 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) # program 29 @@ -549,4 +549,4 @@ context.add_class(ast.GLOBAL_NAMESPACE, cls2.name, cls2) context.add_var(ast.GLOBAL_NAMESPACE + (cls2.name,), f.name, f) context.add_var(ast.GLOBAL_NAMESPACE, var1.name, var1) -program29 = ast.Program(context, "kotlin") +program29 = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) diff --git a/tests/test_type_utils.py b/tests/test_type_utils.py index 3e27577e..649b4c18 100644 --- a/tests/test_type_utils.py +++ b/tests/test_type_utils.py @@ -842,7 +842,7 @@ def test_type_hint_field_access_inheritance(): expr = ast.FieldAccess(cond3, "f") - program = ast.Program(context, language="kotlin") + program = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) types = program.get_types() assert tutils.get_type_hint(expr, context, ast.GLOBAL_NAMESPACE, KT_FACTORY, types) == kt.Integer @@ -875,7 +875,7 @@ def test_type_hint_smart_cast(): cond2 = ast.Conditional(ast.Is(expr2, cls.get_type()), expr2, expr3, cls.get_type()) smart_casts = [(expr, cls.get_type())] - program = ast.Program(context, language="kotlin") + program = ast.Program(context, "kotlin", kt.KotlinBuiltinFactory()) types = program.get_types() assert tutils.get_type_hint(expr, context, ast.GLOBAL_NAMESPACE, From a8f85b88d236aaf256eeb6f07e7f1b184e1f3fbd Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 19 Sep 2022 14:25:50 +0300 Subject: [PATCH 39/78] Add unit test and subtyping relations for type alias with equal literals --- src/ir/typescript_types.py | 76 +++++++++++++++++++++----------------- tests/test_typescript.py | 12 ++++++ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 534113f0..0913da55 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -237,6 +237,35 @@ def get_name(self): return 'undefined' +class AliasType(ObjectType): + def __init__(self, alias, name="AliasType", primitive=False): + super().__init__() + self.alias = alias + self.name = name + self.primitive = primitive + + def get_type(self): + return self.alias + + def is_subtype(self, other): + if isinstance(other, AliasType): + return self.alias.is_subtype(other.alias) + return self.alias.is_subtype(other) + + def box_type(self): + return AliasType(self.alias, self.name) + + def get_name(self): + return self.name + + def __eq__(self, other): + return (isinstance(other, AliasType) and + self.alias == other.alias) + + def __hash__(self): + return hash(str(self.name) + str(self.alias)) + + class NumberLiteralType(TypeScriptBuiltin): def __init__(self, literal, name="NumberLiteralType", primitive=False): super().__init__(name, primitive) @@ -263,9 +292,14 @@ def is_subtype(self, other): is the same, 23. """ + if (isinstance(other, AliasType) and isinstance(other.alias, NumberLiteralType)): + other = other.alias + elif isinstance(other, AliasType): + return isinstance(other.alias, NumberType) + return ((isinstance(other, NumberLiteralType) and - other.get_literal() == self.get_literal()) or - isinstance(other, NumberType)) + other.get_literal() == self.get_literal()) or + isinstance(other, NumberType)) def get_name(self): return self.name @@ -305,9 +339,14 @@ def is_subtype(self, other): is the same, "PULL". """ + if (isinstance(other, AliasType) and isinstance(other.alias, StringLiteralType)): + other = other.alias + elif isinstance(other, AliasType): + return isinstance(other.alias, StringType) + return ((isinstance(other, StringLiteralType) and - other.get_literal() == self.get_literal()) or - isinstance(other, StringType)) + other.get_literal() == self.get_literal()) or + isinstance(other, StringType)) def get_name(self): return self.name @@ -386,35 +425,6 @@ def __init__(self, nr_type_parameters: int): self.supertypes.append(ObjectType()) -class AliasType(ObjectType): - def __init__(self, alias, name="AliasType", primitive=False): - super().__init__() - self.alias = alias - self.name = name - self.primitive = primitive - - def get_type(self): - return self.alias - - def is_subtype(self, other): - if isinstance(other, AliasType): - return self.alias.is_subtype(other.alias) - return self.alias.is_subtype(other) - - def box_type(self): - return AliasType(self.alias, self.name) - - def get_name(self): - return self.name - - def __eq__(self, other): - return (isinstance(other, AliasType) and - self.alias == other.alias) - - def __hash__(self): - return hash(str(self.name) + str(self.alias)) - - # Generator Extension """ The below functions are all passed as candidate diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 60ad51f8..a06463cb 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -12,3 +12,15 @@ def test_type_alias_with_literals(): assert not string_alias.is_subtype(string_lit) assert number_lit.is_subtype(number_alias) assert not number_alias.is_subtype(number_lit) + +def test_type_alias_with_literals2(): + string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringLiteralType("foo")).get_type() + number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberLiteralType(5)).get_type() + + string_lit = tst.StringLiteralType("foo") + number_lit = tst.NumberLiteralType(5) + + assert string_lit.is_subtype(string_alias) + assert number_lit.is_subtype(number_alias) + assert string_alias.is_subtype(string_lit) + assert number_alias.is_subtype(number_lit) From 9020ff9de83dc4d3e4f7dc01ad6d6bf92acc9d4d Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 19 Sep 2022 15:59:43 +0300 Subject: [PATCH 40/78] Rewrite generator lambda functions --- src/generators/generator.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index f96601ce..6b220c68 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -110,9 +110,9 @@ def gen_top_level_declaration(self): declarations. """ candidates = [ - lambda gen: self.gen_variable_decl(), - lambda gen: self.gen_class_decl(), - lambda gen: self.gen_func_decl(), + lambda gen: gen.gen_variable_decl(), + lambda gen: gen.gen_class_decl(), + lambda gen: gen.gen_func_decl(), ] candidates.extend(self.bt_factory.get_decl_candidates()) gen_func = ut.random.choice(candidates) @@ -538,12 +538,12 @@ def _add_node_to_class(self, cls, node): def _add_node_to_parent(self, parent_namespace, node): node_type = { - ast.FunctionDeclaration: lambda gen, p, n, nd: self.context.add_func(p, n, nd), - ast.ClassDeclaration: lambda gen, p, n, nd: self.context.add_class(p, n, nd), - ast.VariableDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), - ast.FieldDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), - ast.ParameterDeclaration: lambda gen, p, n, nd: self.context.add_var(p, n, nd), - ast.Lambda: lambda gen, p, n, nd: self.context.add_lambda(p, n, nd), + ast.FunctionDeclaration: lambda gen, p, n, nd: gen.context.add_func(p, n, nd), + ast.ClassDeclaration: lambda gen, p, n, nd: gen.context.add_class(p, n, nd), + ast.VariableDeclaration: lambda gen, p, n, nd: gen.context.add_var(p, n, nd), + ast.FieldDeclaration: lambda gen, p, n, nd: gen.context.add_var(p, n, nd), + ast.ParameterDeclaration: lambda gen, p, n, nd: gen.context.add_var(p, n, nd), + ast.Lambda: lambda gen, p, n, nd: gen.context.add_lambda(p, n, nd), } node_type.update(self.bt_factory.update_add_node_to_parent()) if parent_namespace == ast.GLOBAL_NAMESPACE: From 5122ed3da155b43cb44a6001514931eb5849b330 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 10 Oct 2022 20:54:21 +0300 Subject: [PATCH 41/78] Add GSOC.md --- GSOC.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 GSOC.md diff --git a/GSOC.md b/GSOC.md new file mode 100644 index 00000000..d2339c7c --- /dev/null +++ b/GSOC.md @@ -0,0 +1,125 @@ + +

+ +# Hephaestus: Testing the TypeScript Compiler + +## A project outline by [Alex Papadopoulos](https://www.linkedin.com/in/alexios-papadopoulos-siountris-855935240/) + +Organisation: [GFOSS - Open Technology Alliance](https://github.com/eellak/) + +Central Repository: [Hephaestus](https://github.com/hephaestus-compiler-project/hephaestus) + +Mentor: [Thodoris Sotiropoulos](https://github.com/theosotr) + +## 📚 Contents + +- Project Description +- Communicating with the Hephaestus Team +- Development Outline + - Git/GitHub: Branch and Repo Management + - Important Features + - Automated Testing +- Pull Request Overview +- Results +- What's next? + +## 💭 Project Description + +Hephaestus is a compiler testing tool, designed to test the compilers of statically-typed languages. Namely, upon the start of the GSoC '22 cycle, Hephaestus was actively testing and had found bugs in the languages of Java, Kotlin, and Groovy. + +My task was to expand Hephaestus to test and find bugs in the compiler of TypeScript, a language by Microsoft that has risen in popularity in the previous decade. + +Below we will go over important moments that shaped my GSoC project, with the aim of describing the development process and providing useful tips for future GSoC contributors. + +## 📧 Communicating with the Hephaestus Team + +Communication with your mentor during GSoC is crucial. Mentors can make or break a GSoC project and, in my case, I was paired with a great one. Thodoris motivated me and guided me through blockers, head scratchers, and problems that all contributors will eventually face in one way or another. + +Hephaestus is housed at a research lab, so I was lucky to have the ability to meet up and work with Thodoris on-site at the lab. At the same time, we also held virtual meetings whenever needed. + +Both virtually and on-site we had pair programming sessions when needed. Whenever I stumbled upon a problem that I could not solve by myself, after going through all other avenues (properly searching the internet, writing unit tests, using the python debugger etc.) we sat down together and tried to fix the problem. Those sessions were extremely helpful and I learnt a lot both from being the one coding, but also from being the one discussing the problem while Thodoris typed our solution out. + +Pair programming is a very important skill for any developer team out there. A make-or-break tip for these sessions (especially for Junior Developers) is to go into them prepared. First exhaust all other possible means (in a reasonable time-frame) and be able to tell the other person what you have already tried. This way, no time is wasted and you can more effectively crack the problem. + +Thodoris and I used a [Slack](https://slack.com/) group to communicate throughout the program both for coordinating our efforts but also for updating other Hephaestus team members with my progress. + +## 👨‍💻 Development Outline + +### Git/GitHub: Branch and Repo Management + +By participating in the Google Summer of Code program, I learnt how to seamlessly integrate Git and GitHub in my workflow when working with large projects. From rebasing branches to using handy commands like [git-bisect](https://git-scm.com/docs/git-bisect), I can now easily navigate and contribute to larger codebases, while utilizing tools that are vital to all developers regardless of their field. + +This was the first time I used Git outside of personal or small-team projects, which equipped me with necessary skills for any future project. + +### Important Features + +Lots was added, discarded, and rewritten during GSoC '22 for the Hephaestus project. Which features were the most challenging and important? + +- **Translating an Abstract Syntax Tree (AST) to TypeScript** + +The first feature I completed was perhaps the one with the biggest learning curve. While future additions were in fact more complex, the first one was the most all-around challenging. + +It is not hard to think of why. I had to deeply understand the functionality of an AST as well as how the mechanisms of Hephaestus cooperated to produce the tree itself. Aside from the learning material directly related to this feature, I also had to get a good understanding of properly contributing in Open Source as a whole before I could even start coding this out. + +- **Making Hephaestus extensible** + +It turns out that trying to add a language with exclusive features to a language agnostic compiler tester comes with its challenges. + +The applications and handling of certain cases in the core generator were not designed with TypeScript features in mind (eg. Union Types). + +As a result, I had to find a proper and effective way for one to extend the core generator of Hephaestus to features that are language-specific. After designing this with my mentor, we proceeded with its application. + +Now, everyone can extend Hephaestus to any language they want, and there already is a way to add language-specific features without changing the core and language-agnostic nature of Hephaestus. + +### Automated Testing + +Automated Testing is important for all healthy codebases. After a certain point, the code changes are too many to keep manually testing features. One must know at any point if an addition broke some part of the code, in order to quickly and effectively act upon that information to fix potential bugs. + +Throughout GSoC, I wrote a number of unit tests to ensure stability, increase code coverage, and fix bugs. + +## 🌿 Pull Requests + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
StatusNameDiff
TypeScript Translator+1020 −530
Null and Undefined types. Null Constant+88 -7
Literal Types+155 −11
Type Aliases+307 -111
🚧Union Types+471 −77
+ +## Results & What's Next + +After the GSoC program, I am now a member of the research lab that houses the Hephaestus project: the [AUEB BALAB](https://www.balab.aueb.gr/)! + +I will stay on the Hephaestus team and contribute to the project. I am very excited for the future to come. + +Thanks to my mentor Thodoris, the BALAB team, and all the GSoC community members that made this summer one to remember! From 5a4be54eb207d571111d2420cf426b576618444d Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 9 Nov 2022 18:49:07 +0200 Subject: [PATCH 42/78] Add deployment script for running and installing typescript, npm --- deployment/run.sh | 9 +++++++++ deployment/setup.sh | 16 ++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/deployment/run.sh b/deployment/run.sh index c87fe06d..9d9f9401 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -47,6 +47,13 @@ run_groovy_from_source() { --language groovy --cast-numbers } +run_typescript() { + npm install -g typescript@next + python3 hephaestus.py -s $TIME_TO_RUN -t $TRANSFORMATIONS -w $CORES --batch 30 -P \ + --language typescript --disable-use-site-variance \ + --error-filter-patterns patterns.txt +} + run_multiple_versions() { cd $CHECK_TYPE_SYSTEMS git pull @@ -94,12 +101,14 @@ while getopts "hksagS" OPTION; do h) echo "Usage:" echo "init.sh -k " + echo "init.sh -t " echo "init.sh -s " echo "init.sh -a " echo "init.sh -g " echo "init.sh -S " echo "" echo " -k Simple run" + echo " -t Install latest typescript nightly version" echo " -s Run from source" echo " -a Run multiple versions" echo " -g Simple run groovy" diff --git a/deployment/setup.sh b/deployment/setup.sh index 1f1ca3b6..4ede2dc3 100755 --- a/deployment/setup.sh +++ b/deployment/setup.sh @@ -77,6 +77,15 @@ install_groovy_from_source() { source $HOME/.bash_profile } +install_npm() { + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash + nvm install node +} + +install_typescript() { + npm install -g typescript@next +} + install_kotlin() { install_java sdk install kotlin @@ -180,6 +189,11 @@ while getopts "hskagS" OPTION; do add_run_script_to_path ;; + t) + install_npm + install_typescript + ;; + s) install_deps install_kotlin_from_source @@ -211,12 +225,14 @@ while getopts "hskagS" OPTION; do h) echo "Usage:" echo "init.sh -k " + echo "init.sh -t " echo "init.sh -s " echo "init.sh -a " echo "init.sh -g " echo "init.sh -S " echo "" echo " -k Install latest kotlin version" + echo " -t Install latest typescript nightly version" echo " -s Install kotlin from source" echo " -a Install all kotlin versions" echo " -g Install latest groovy version" From 3d30d15b825dc49c417c77a7cb363fdbee49c073 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 10 Nov 2022 12:51:11 +0200 Subject: [PATCH 43/78] Update run.sh and setup.sh --- deployment/run.sh | 4 ++++ deployment/setup.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/deployment/run.sh b/deployment/run.sh index 9d9f9401..bb48e39b 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -82,6 +82,10 @@ while getopts "hksagS" OPTION; do simple_run ;; + t) + run_typescript + ;; + s) run_from_source ;; diff --git a/deployment/setup.sh b/deployment/setup.sh index 4ede2dc3..152c9d22 100755 --- a/deployment/setup.sh +++ b/deployment/setup.sh @@ -179,7 +179,7 @@ then exit 0 fi -while getopts "hskagS" OPTION; do +while getopts "hsktagS" OPTION; do case $OPTION in k) From 362ec915b403ced7ae4b552f22074d4f6ce2d4cd Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 10 Nov 2022 13:54:38 +0200 Subject: [PATCH 44/78] Add typescript option to run.sh --- deployment/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/run.sh b/deployment/run.sh index bb48e39b..da176d84 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -75,7 +75,7 @@ then exit 0 fi -while getopts "hksagS" OPTION; do +while getopts "hkstagS" OPTION; do case $OPTION in k) From bd06ca24f9a76349acc2070f9fb9d9c753b72ee5 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 10 Nov 2022 14:13:46 +0200 Subject: [PATCH 45/78] Modify python command to use newer python version --- deployment/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/run.sh b/deployment/run.sh index da176d84..e92eb9d0 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -49,7 +49,7 @@ run_groovy_from_source() { run_typescript() { npm install -g typescript@next - python3 hephaestus.py -s $TIME_TO_RUN -t $TRANSFORMATIONS -w $CORES --batch 30 -P \ + python3.9 hephaestus.py -s $TIME_TO_RUN -t $TRANSFORMATIONS -w $CORES --batch 30 -P \ --language typescript --disable-use-site-variance \ --error-filter-patterns patterns.txt } From 4c44200a2df7fa8df8ddad8b87d9fe9eff59d7dd Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 10 Nov 2022 14:20:46 +0200 Subject: [PATCH 46/78] Remove transformations when running hephaestus for typescript at remote server --- deployment/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/run.sh b/deployment/run.sh index e92eb9d0..57efa732 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -49,7 +49,7 @@ run_groovy_from_source() { run_typescript() { npm install -g typescript@next - python3.9 hephaestus.py -s $TIME_TO_RUN -t $TRANSFORMATIONS -w $CORES --batch 30 -P \ + python3.9 hephaestus.py -s $TIME_TO_RUN -t 0 -w $CORES --batch 30 -P \ --language typescript --disable-use-site-variance \ --error-filter-patterns patterns.txt } From 9d4a0028eab3c15121df5cedd64d8376bcc5b744 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 16 Aug 2022 19:21:21 +0300 Subject: [PATCH 47/78] Correct the primitive status of typescript built-in types --- src/ir/typescript_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 0913da55..d97f8a27 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -18,19 +18,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_boolean_type(self): - return BooleanType(primitive=False) + return BooleanType(primitive=True) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=False) + return StringType(primitive=True) def get_big_integer_type(self): - return BigIntegerType(primitive=False) + return BigIntegerType(primitive=True) def get_array_type(self): return ArrayType() @@ -53,25 +53,25 @@ def get_primitive_types(self): ] def get_integer_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_byte_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_short_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_long_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_float_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_double_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_big_decimal_type(self): - return NumberType(primitive=False) + return NumberType(primitive=True) def get_null_type(self): return NullType(primitive=False) From 8a42ca2d270f3d2ca878ee89354915472eaf9693 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 23 Aug 2022 21:57:56 +0300 Subject: [PATCH 48/78] Merge literal types from main to ts-type-alias --- src/ir/typescript_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index d97f8a27..0913da55 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -18,19 +18,19 @@ def get_any_type(self): return ObjectType() def get_number_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_boolean_type(self): - return BooleanType(primitive=True) + return BooleanType(primitive=False) def get_char_type(self): return StringType(primitive=False) def get_string_type(self): - return StringType(primitive=True) + return StringType(primitive=False) def get_big_integer_type(self): - return BigIntegerType(primitive=True) + return BigIntegerType(primitive=False) def get_array_type(self): return ArrayType() @@ -53,25 +53,25 @@ def get_primitive_types(self): ] def get_integer_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_byte_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_short_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_long_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_float_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_double_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_big_decimal_type(self): - return NumberType(primitive=True) + return NumberType(primitive=False) def get_null_type(self): return NullType(primitive=False) From 7f76da3180433d784042b00d546b31dc9198eaed Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 29 Aug 2022 13:14:14 +0300 Subject: [PATCH 49/78] Add full support for typescript number and string literal types --- src/ir/typescript_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 0913da55..9fba5d50 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -2,6 +2,7 @@ import src.ir.typescript_ast as ts_ast import src.ir.builtins as bt import src.ir.types as tp +import src.ir.ast as ast import src.utils as ut class TypeScriptBuiltinFactory(bt.BuiltinFactory): From 8dc152aa576ca51fc686cdda20749b78815d22bd Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 30 Aug 2022 14:12:11 +0300 Subject: [PATCH 50/78] Add UnionType class and UnionTypeFactory --- src/ir/typescript_types.py | 73 ++++++++++++++++++++++++++++++++++++++ src/utils.py | 5 +++ 2 files changed, 78 insertions(+) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 9fba5d50..1aeda263 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -99,6 +99,7 @@ def get_constant_candidates(self): "StringLiteralType": lambda etype: ast.StringConstant(etype.literal), } + class TypeScriptBuiltin(tp.Builtin): def __init__(self, name, primitive): super().__init__(name) @@ -403,6 +404,73 @@ def gen_number_literal(self): return lit +class UnionType(TypeScriptBuiltin): + def __init__(self, types, name="UnionType", primitive=False): + super().__init__(name, primitive) + self.types = types + + def get_types(self): + return self.types + + def get_name(self): + return self.name + + +class UnionTypeFactory: + def __init__(self, max_ut): + self.max_ut = max_ut + self.unions = [] + self.candidates = [ + ObjectType(), + NumberType(), + BooleanType(), + StringType(), + ArrayType(), + NullType(), + UndefinedType(primitive=False), + literal_types.get_literal_types(), + ] + + def get_number_of_types(self): + # TODO Perhaps make this user configurable + return ut.random.integer(2, 4) + + def gen_union_type(self): + """ Generates a union type that consists of + N types (where N is num_of_types). + + Args: + num_of_types - Number of types to be unionized + """ + # TODO | generate union types with previously + # TODO | generated types (ie. classes, type aliases) + num_of_types = self.get_number_of_types() + assert num_of_types < len(self.candidates) + types = self.candidates.copy() + ut.random.shuffle(types) + types = types[0:num_of_types] + gen_union = UnionType(types) + self.unions.append(gen_union) + return gen_union + + def get_union_type(self): + """ Returns a previously created union type + or a newly generated at random. + + If there are previously generated union types + and they have not exceeded the limit, we make a + probabilistic choice on whether to pick one of + the already generated types or create a new one. + + """ + generated = len(self.unions) + if generated == 0: + return self.gen_union_type() + if generated >= self.max_ut or ut.random.bool(): + return ut.random.choice(self.unions) + return self.gen_union_type() + + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): # In TypeScript, arrays are covariant. @@ -470,3 +538,8 @@ def add_type_alias(gen, namespace, type_name, ta_decl): MAX_STRING_LITERAL_TYPES = 10 MAX_NUM_LITERAL_TYPES = 10 literal_types = LiteralTypeFactory(MAX_STRING_LITERAL_TYPES, MAX_NUM_LITERAL_TYPES) + +# Union Types + +MAX_UNION_TYPES = 10 +union_types = UnionTypeFactory(MAX_UNION_TYPES) diff --git a/src/utils.py b/src/utils.py index 40cfe306..01db3365 100644 --- a/src/utils.py +++ b/src/utils.py @@ -167,6 +167,7 @@ def caps(self, length=1, blacklist=None): def range(self, from_value, to_value): return range(0, self.integer(from_value, to_value)) +<<<<<<< HEAD def identifier(self, ident_type:str=None) -> str: """Generate an identifier name. @@ -185,6 +186,10 @@ def identifier(self, ident_type:str=None) -> str: if ident_type == 'capitalize': return word.capitalize() raise AssertionError("ident_type should be 'capitalize' or 'lower'") +======= + def shuffle(self, ll): + return self.r.shuffle(ll) +>>>>>>> d566664 (Add UnionType class and UnionTypeFactory) random = RandomUtils() From 93d9ae522bb25607d5dc27ac4eac81d1f6043389 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 31 Aug 2022 15:06:18 +0300 Subject: [PATCH 51/78] Add limit for types in a union --- src/ir/typescript_types.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 1aeda263..b3fa10b7 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -417,7 +417,7 @@ def get_name(self): class UnionTypeFactory: - def __init__(self, max_ut): + def __init__(self, max_ut, max_in_union): self.max_ut = max_ut self.unions = [] self.candidates = [ @@ -430,10 +430,12 @@ def __init__(self, max_ut): UndefinedType(primitive=False), literal_types.get_literal_types(), ] + self.max_in_union = (max_in_union if max_in_union <= len(self.candidates) + else len(self.candidates)) def get_number_of_types(self): # TODO Perhaps make this user configurable - return ut.random.integer(2, 4) + return ut.random.integer(2, self.max_in_union) def gen_union_type(self): """ Generates a union type that consists of @@ -541,5 +543,7 @@ def add_type_alias(gen, namespace, type_name, ta_decl): # Union Types +# TODO make these limits user-configurable MAX_UNION_TYPES = 10 -union_types = UnionTypeFactory(MAX_UNION_TYPES) +MAX_TYPES_IN_UNION = 4 +union_types = UnionTypeFactory(MAX_UNION_TYPES, MAX_TYPES_IN_UNION) From f075f3aa2848b64c81c9b5660c4a243362bda4f4 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 31 Aug 2022 15:40:26 +0300 Subject: [PATCH 52/78] Pass union types to generator and translate them to typescript --- src/generators/generator.py | 2 +- src/ir/builtins.py | 2 +- src/ir/typescript_types.py | 9 +++++---- src/translators/typescript.py | 9 ++++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 6b220c68..4aae7887 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -61,7 +61,7 @@ def __init__(self, self.function_types = self.bt_factory.get_function_types( cfg.limits.max_functional_params) - self.ret_builtin_types = self.bt_factory.get_non_nothing_types() + self.ret_builtin_types = self.bt_factory.get_non_nothing_types(self) self.builtin_types = self.ret_builtin_types + \ [self.bt_factory.get_void_type()] diff --git a/src/ir/builtins.py b/src/ir/builtins.py index 12fa9c77..21fa3105 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -81,7 +81,7 @@ def get_function_type(self, nr_parameters=0): def get_null_type(self): pass - def get_non_nothing_types(self): + def get_non_nothing_types(self, gen_object): return [ self.get_any_type(), self.get_number_type(), diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index b3fa10b7..f75c94c4 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -77,11 +77,12 @@ def get_big_decimal_type(self): def get_null_type(self): return NullType(primitive=False) - def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types - types = super().get_non_nothing_types() + def get_non_nothing_types(self, gen_object): # Overwriting Parent method to add TS-specific types + types = super().get_non_nothing_types(gen_object) types.extend([ self.get_null_type(), - UndefinedType(primitive=False) + UndefinedType(primitive=False), + union_types.get_union_type(gen_object), ] + literal_types.get_literal_types()) return types @@ -455,7 +456,7 @@ def gen_union_type(self): self.unions.append(gen_union) return gen_union - def get_union_type(self): + def get_union_type(self, gen_object): """ Returns a previously created union type or a newly generated at random. diff --git a/src/translators/typescript.py b/src/translators/typescript.py index b47ee2ea..5db65fed 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -77,11 +77,12 @@ def get_filename(): def get_incorrect_filename(): return TypeScriptTranslator.incorrect_filename + def get_union(self, utype): + union = " | ".join([self.type_arg2str(t) for t in utype.types]) + return union + def type_arg2str(self, t_arg): # TypeScript does not have a Wildcard type - if (isinstance(t_arg, tst.StringLiteralType) or - isinstance(t_arg, tst.NumberLiteralType)): - return str(t_arg.get_literal()) if not t_arg.is_wildcard(): return self.get_type_name(t_arg) return "unknown" @@ -91,6 +92,8 @@ def get_type_name(self, t): if (isinstance(t, tst.NumberLiteralType) or isinstance(t, tst.StringLiteralType)): return str(t.get_literal()) + if t.name == 'UnionType': + return self.get_union(t) if not t_constructor: return t.get_name() From 43322bd5cdf8db14e3e5dc98066e8bac345bf6d7 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 31 Aug 2022 16:25:08 +0300 Subject: [PATCH 53/78] Fix union type handling of literal types --- src/ir/typescript_types.py | 4 +--- src/translators/typescript.py | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index f75c94c4..815972f3 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -426,11 +426,9 @@ def __init__(self, max_ut, max_in_union): NumberType(), BooleanType(), StringType(), - ArrayType(), NullType(), UndefinedType(primitive=False), - literal_types.get_literal_types(), - ] + ] + literal_types.get_literal_types() self.max_in_union = (max_in_union if max_in_union <= len(self.candidates) else len(self.candidates)) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 5db65fed..5849881a 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -78,8 +78,7 @@ def get_incorrect_filename(): return TypeScriptTranslator.incorrect_filename def get_union(self, utype): - union = " | ".join([self.type_arg2str(t) for t in utype.types]) - return union + return " | ".join([self.type_arg2str(t) for t in utype.types]) def type_arg2str(self, t_arg): # TypeScript does not have a Wildcard type From 8aa2dd1778e1bb65284cde7d4edbf13db52d6d7b Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 31 Aug 2022 19:57:08 +0300 Subject: [PATCH 54/78] Expand generator to generate union type constants --- src/generators/generator.py | 2 +- src/ir/typescript_types.py | 36 +++++++++++++++++++++++++++++++++++- src/translators/utils.py | 2 +- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 4aae7887..3540eccf 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -1895,7 +1895,7 @@ def gen_fun_call(etype): ), self.bt_factory.get_null_type().name: lambda x: ast.Null } - constant_candidates.update(self.bt_factory.get_constant_candidates()) + constant_candidates.update(self.bt_factory.get_constant_candidates(self, constant_candidates)) binary_ops = { self.bt_factory.get_boolean_type(): [ lambda x: self.gen_logical_expr(x, only_leaves), diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 815972f3..5da488ea 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -94,10 +94,29 @@ def update_add_node_to_parent(self): ts_ast.TypeAliasDeclaration: add_type_alias, } - def get_constant_candidates(self): + def get_constant_candidates(self, gen_object, constants): + """ Updates the constant candidates of the generator + with the type-constant pairs for language-specific features. + + Args: + gen_object: The generator instance + constants: The dictionary of constant candidates + at the time of the method call + Returns: + A dictionary where the keys are strings of type names and + values are functions that return the appropriate constant + node for the type. + + The constants dictionary is updated at the generator-side + with the method's returned key-value pairs. + + This method is called at src.ir.generator.get_generators() + + """ return { "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.literal, NumberLiteralType), "StringLiteralType": lambda etype: ast.StringConstant(etype.literal), + "UnionType": lambda etype: union_types.get_union_constant(etype, constants), } @@ -471,6 +490,21 @@ def get_union_type(self, gen_object): return ut.random.choice(self.unions) return self.gen_union_type() + def get_union_constant(self, utype, constants): + type_candidates = [t for t in utype.types if t.name in constants] + """ A union type can have types like 'Object' or 'undefined' + as part of its union, which however do not have a respective + constant equivalent. + + Hence, we only consider types that we can generate a constant + from. If there is none, we revert to a bottom constant. + + """ + if len(type_candidates) == 0: + return ast.BottomConstant(utype.types[0]) + t = ut.random.choice(type_candidates) + return constants[t.name](t) + class ArrayType(tp.TypeConstructor, ObjectType): def __init__(self, name="Array"): diff --git a/src/translators/utils.py b/src/translators/utils.py index 783f37a9..f7c9838c 100644 --- a/src/translators/utils.py +++ b/src/translators/utils.py @@ -3,4 +3,4 @@ def inner(self, node): self._nodes_stack.append(node) visit(self, node) self._nodes_stack.pop() - return inner \ No newline at end of file + return inner From 49cb8afb9cd88fd3119c593e6374c667fc63f73f Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 1 Sep 2022 18:48:05 +0300 Subject: [PATCH 55/78] Pass and generate union types with previously generated types --- src/generators/generator.py | 11 +++++----- src/ir/builtins.py | 14 +++++++++++-- src/ir/typescript_types.py | 42 ++++++++++++++++++++++++++----------- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 3540eccf..6c85dae7 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -61,7 +61,7 @@ def __init__(self, self.function_types = self.bt_factory.get_function_types( cfg.limits.max_functional_params) - self.ret_builtin_types = self.bt_factory.get_non_nothing_types(self) + self.ret_builtin_types = self.bt_factory.get_non_nothing_types() self.builtin_types = self.ret_builtin_types + \ [self.bt_factory.get_void_type()] @@ -1895,7 +1895,7 @@ def gen_fun_call(etype): ), self.bt_factory.get_null_type().name: lambda x: ast.Null } - constant_candidates.update(self.bt_factory.get_constant_candidates(self, constant_candidates)) + constant_candidates.update(self.bt_factory.get_constant_candidates(constant_candidates)) binary_ops = { self.bt_factory.get_boolean_type(): [ lambda x: self.gen_logical_expr(x, only_leaves), @@ -1981,7 +1981,6 @@ def get_types(self, if exclude_contravariants and variance == tp.Contravariant: continue type_params.append(t_param) - if type_params and ut.random.bool(): return type_params @@ -1993,9 +1992,11 @@ def get_types(self, t for t in builtins if t.name != self.bt_factory.get_array_type().name ] + + dynamic = self.bt_factory.get_dynamic_types(self) if exclude_function_types: - return usr_types + builtins - return usr_types + builtins + self.function_types + return usr_types + builtins + dynamic + return usr_types + builtins + dynamic + self.function_types def select_type(self, ret_types=True, diff --git a/src/ir/builtins.py b/src/ir/builtins.py index 21fa3105..778a17f1 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -81,7 +81,7 @@ def get_function_type(self, nr_parameters=0): def get_null_type(self): pass - def get_non_nothing_types(self, gen_object): + def get_non_nothing_types(self): return [ self.get_any_type(), self.get_number_type(), @@ -138,9 +138,19 @@ def get_function_types(self, max_parameters): def get_nothing(self): raise NotImplementedError - def get_constant_candidates(self): + def get_dynamic_types(self, gen_object): + """ A type is considered dynamic if it can utilize + both a builtin type and a user-defined type. + + Eg. A TypeScript Union Type: string | myClass + + """ + return [] + + def get_constant_candidates(self, constants): """ Overwrite this function to update the generator constants with language-specific. + """ return {} diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 5da488ea..174a9c2b 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -77,12 +77,11 @@ def get_big_decimal_type(self): def get_null_type(self): return NullType(primitive=False) - def get_non_nothing_types(self, gen_object): # Overwriting Parent method to add TS-specific types - types = super().get_non_nothing_types(gen_object) + def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types + types = super().get_non_nothing_types() types.extend([ self.get_null_type(), UndefinedType(primitive=False), - union_types.get_union_type(gen_object), ] + literal_types.get_literal_types()) return types @@ -94,7 +93,12 @@ def update_add_node_to_parent(self): ts_ast.TypeAliasDeclaration: add_type_alias, } - def get_constant_candidates(self, gen_object, constants): + def get_dynamic_types(self, gen_object): + return [ + union_types.get_union_type(gen_object), + ] + + def get_constant_candidates(self, constants): """ Updates the constant candidates of the generator with the type-constant pairs for language-specific features. @@ -432,6 +436,11 @@ def __init__(self, types, name="UnionType", primitive=False): def get_types(self): return self.types + def is_assignable(self, other): + # TODO revisit this after implementing structural types + return (isinstance(other, UnionType) and + set(other.types) == set(self.types)) + def get_name(self): return self.name @@ -452,21 +461,24 @@ def __init__(self, max_ut, max_in_union): else len(self.candidates)) def get_number_of_types(self): - # TODO Perhaps make this user configurable return ut.random.integer(2, self.max_in_union) - def gen_union_type(self): + def gen_union_type(self, gen): """ Generates a union type that consists of N types (where N is num_of_types). Args: num_of_types - Number of types to be unionized + gen - Instance of Hephaestus' generator """ - # TODO | generate union types with previously - # TODO | generated types (ie. classes, type aliases) num_of_types = self.get_number_of_types() assert num_of_types < len(self.candidates) types = self.candidates.copy() + usr_types = [ + c.get_type() + for c in gen.context.get_classes(gen.namespace).values() + ] + types.extend(usr_types) ut.random.shuffle(types) types = types[0:num_of_types] gen_union = UnionType(types) @@ -485,21 +497,27 @@ def get_union_type(self, gen_object): """ generated = len(self.unions) if generated == 0: - return self.gen_union_type() + return self.gen_union_type(gen_object) if generated >= self.max_ut or ut.random.bool(): return ut.random.choice(self.unions) - return self.gen_union_type() + return self.gen_union_type(gen_object) def get_union_constant(self, utype, constants): - type_candidates = [t for t in utype.types if t.name in constants] - """ A union type can have types like 'Object' or 'undefined' + """ This method randomly chooses one of the types in a type's + union and then assigns the union a constant value that matches + the randomly selected type. + + A union type can have types like 'Object' or 'undefined' as part of its union, which however do not have a respective constant equivalent. Hence, we only consider types that we can generate a constant from. If there is none, we revert to a bottom constant. + TODO revisit this after implementing structural types. + """ + type_candidates = [t for t in utype.types if t.name in constants] if len(type_candidates) == 0: return ast.BottomConstant(utype.types[0]) t = ut.random.choice(type_candidates) From f3ee75510e55a8bd41fbeceda3f0f7333da4e0b8 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Mon, 5 Sep 2022 18:48:44 +0300 Subject: [PATCH 56/78] Add union type assignment test, update tests to pass builtin factory when creating ast.Program node. --- tests/test_typescript.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index a06463cb..b5f6ec41 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -1,5 +1,6 @@ import src.ir.typescript_types as tst import src.ir.typescript_ast as ts_ast +import src.ir.types as tp def test_type_alias_with_literals(): string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringType()).get_type() @@ -24,3 +25,17 @@ def test_type_alias_with_literals2(): assert number_lit.is_subtype(number_alias) assert string_alias.is_subtype(string_lit) assert number_alias.is_subtype(number_lit) + +def test_union_types_simple(): + print('click click click... I\'m in.') + union_1 = tst.UnionType([tst.NumberType(), tst.BooleanType()]) + + bar_lit = tst.StringLiteralType("bar") + union_2 = tst.UnionType([tst.BooleanType(), bar_lit]) + + union_3 = tst.UnionType([tst.BooleanType(), tst.NumberType()]) + + assert not union_1.is_assignable(union_2) + assert not union_2.is_assignable(union_1) + assert union_3.is_assignable(union_1) + assert union_1.is_assignable(union_3) From 338420b5d32b5d8f6d9603167c4617a238bff3c1 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 14 Sep 2022 15:19:52 +0300 Subject: [PATCH 57/78] Change default is_subtype function for builtin types. Add unit tests for typescript --- tests/test_typescript.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index b5f6ec41..3577f30e 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -27,7 +27,6 @@ def test_type_alias_with_literals2(): assert number_alias.is_subtype(number_lit) def test_union_types_simple(): - print('click click click... I\'m in.') union_1 = tst.UnionType([tst.NumberType(), tst.BooleanType()]) bar_lit = tst.StringLiteralType("bar") @@ -39,3 +38,22 @@ def test_union_types_simple(): assert not union_2.is_assignable(union_1) assert union_3.is_assignable(union_1) assert union_1.is_assignable(union_3) + + +def test_union_type_assign(): + union = tst.UnionType([tst.StringType(), tst.NumberType(), tst.BooleanType(), tst.ObjectType()]) + foo = tst.StringType() + + assert len(union.types) == 4 + assert not union.is_assignable(foo) + assert foo.is_assignable(union) + + +def test_union_type_param(): + union1 = tst.UnionType([tst.NumberType(), tst.NullType()]) + union2 = tst.UnionType([tst.StringLiteralType("foo"), tst.NumberType()]) + t_param = tp.TypeParameter("T", bound=union2) + + assert not union2.is_subtype(union1) + assert not union1.is_subtype(t_param) + assert not t_param.is_subtype(union1) From 09ccae6ad65cf59c1e52191a555cf4306ebd36cc Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 14 Sep 2022 16:15:53 +0300 Subject: [PATCH 58/78] Fix subtyping relations for union types --- src/ir/typescript_types.py | 32 +++++++++++++------------------- src/utils.py | 4 +--- tests/test_typescript.py | 12 ++++++------ 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 174a9c2b..9234e68b 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -160,11 +160,6 @@ def __init__(self, name="Number", primitive=False): super().__init__(name, primitive) self.supertypes.append(ObjectType()) - def is_assignable(self, other): - if isinstance(other, NumberLiteralType): - return False - return isinstance(other, NumberType) - def box_type(self): return NumberType(self.name, primitive=False) @@ -214,11 +209,6 @@ def __init__(self, name="String", primitive=False): def box_type(self): return StringType(self.name, primitive=False) - def is_assignable(self, other): - if isinstance(other, StringLiteralType): - return False - return isinstance(other, StringType) - def get_name(self): if self.is_primitive: return "string" @@ -436,14 +426,22 @@ def __init__(self, types, name="UnionType", primitive=False): def get_types(self): return self.types - def is_assignable(self, other): - # TODO revisit this after implementing structural types - return (isinstance(other, UnionType) and - set(other.types) == set(self.types)) + def is_subtype(self, other): + if isinstance(other, UnionType): + return set(self.types).issubset(other.types) + return other.name == 'Object' def get_name(self): return self.name + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.name == other.name and + set(self.types) == set(other.types)) + + def __hash__(self): + return hash(str(self.name) + str(self.types)) + class UnionTypeFactory: def __init__(self, max_ut, max_in_union): @@ -470,15 +468,11 @@ def gen_union_type(self, gen): Args: num_of_types - Number of types to be unionized gen - Instance of Hephaestus' generator + """ num_of_types = self.get_number_of_types() assert num_of_types < len(self.candidates) types = self.candidates.copy() - usr_types = [ - c.get_type() - for c in gen.context.get_classes(gen.namespace).values() - ] - types.extend(usr_types) ut.random.shuffle(types) types = types[0:num_of_types] gen_union = UnionType(types) diff --git a/src/utils.py b/src/utils.py index 01db3365..3b7a4ddb 100644 --- a/src/utils.py +++ b/src/utils.py @@ -167,7 +167,6 @@ def caps(self, length=1, blacklist=None): def range(self, from_value, to_value): return range(0, self.integer(from_value, to_value)) -<<<<<<< HEAD def identifier(self, ident_type:str=None) -> str: """Generate an identifier name. @@ -186,10 +185,9 @@ def identifier(self, ident_type:str=None) -> str: if ident_type == 'capitalize': return word.capitalize() raise AssertionError("ident_type should be 'capitalize' or 'lower'") -======= + def shuffle(self, ll): return self.r.shuffle(ll) ->>>>>>> d566664 (Add UnionType class and UnionTypeFactory) random = RandomUtils() diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 3577f30e..93d67010 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -34,10 +34,10 @@ def test_union_types_simple(): union_3 = tst.UnionType([tst.BooleanType(), tst.NumberType()]) - assert not union_1.is_assignable(union_2) - assert not union_2.is_assignable(union_1) - assert union_3.is_assignable(union_1) - assert union_1.is_assignable(union_3) + assert not union_1.is_subtype(union_2) + assert not union_2.is_subtype(union_1) + assert union_3.is_subtype(union_1) + assert union_1.is_subtype(union_3) def test_union_type_assign(): @@ -45,8 +45,8 @@ def test_union_type_assign(): foo = tst.StringType() assert len(union.types) == 4 - assert not union.is_assignable(foo) - assert foo.is_assignable(union) + assert not union.is_subtype(foo) + assert foo.is_subtype(union) def test_union_type_param(): From ab9c0d7d99c494e9659199b7006140348f863748 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Tue, 20 Sep 2022 15:49:46 +0300 Subject: [PATCH 59/78] Add an extra subtyping check for when additional subtyping relations exist --- src/generators/generator.py | 19 +++++++++++++------ src/ir/decorators.py | 4 ++++ src/ir/types.py | 20 ++++++++++++++++++++ src/ir/typescript_types.py | 14 ++++++++++---- 4 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 src/ir/decorators.py diff --git a/src/generators/generator.py b/src/generators/generator.py index 6c85dae7..68170270 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -1950,7 +1950,8 @@ def get_types(self, exclude_covariants=False, exclude_contravariants=False, exclude_type_vars=False, - exclude_function_types=False) -> List[tp.Type]: + exclude_function_types=False, + exclude_dynamic_types=False) -> List[tp.Type]: """Get all available types. Including user-defined types, built-ins, and function types. @@ -1964,6 +1965,7 @@ def get_types(self, exclude_contravariants: exclude contravariant type parameters. exclude_type_vars: exclude type variables. exclude_function_types: exclude function types. + exclude_dynamic_types: exclude dynamic types. Returns: A list of available types. @@ -1993,7 +1995,9 @@ def get_types(self, if t.name != self.bt_factory.get_array_type().name ] - dynamic = self.bt_factory.get_dynamic_types(self) + dynamic = (self.bt_factory.get_dynamic_types(self) + if not exclude_dynamic_types + else []) if exclude_function_types: return usr_types + builtins + dynamic return usr_types + builtins + dynamic + self.function_types @@ -2003,7 +2007,8 @@ def select_type(self, exclude_arrays=False, exclude_covariants=False, exclude_contravariants=False, - exclude_function_types=False) -> tp.Type: + exclude_function_types=False, + exclude_dynamic_types=False) -> tp.Type: """Select a type from the all available types. It will always instantiating type constructors to parameterized types. @@ -2014,8 +2019,8 @@ def select_type(self, exclude_arrays: exclude array types. exclude_covariants: exclude covariant type parameters. exclude_contravariants: exclude contravariant type parameters. - exclude_type_vars: exclude type variables. exclude_function_types: exclude function types. + eclude_dynamic_types: exclude dynamic types. Returns: Returns a type. @@ -2024,7 +2029,8 @@ def select_type(self, exclude_arrays=exclude_arrays, exclude_covariants=exclude_covariants, exclude_contravariants=exclude_contravariants, - exclude_function_types=exclude_function_types) + exclude_function_types=exclude_function_types, + exclude_dynamic_types=exclude_dynamic_types) stype = ut.random.choice(types) if stype.is_type_constructor(): exclude_type_vars = stype.name == self.bt_factory.get_array_type().name @@ -2033,7 +2039,8 @@ def select_type(self, exclude_covariants=True, exclude_contravariants=True, exclude_type_vars=exclude_type_vars, - exclude_function_types=exclude_function_types), + exclude_function_types=exclude_function_types, + exclude_dynamic_types=exclude_dynamic_types), enable_pecs=self.enable_pecs, disable_variance_functions=self.disable_variance_functions, variance_choices={} diff --git a/src/ir/decorators.py b/src/ir/decorators.py new file mode 100644 index 00000000..f5064cf1 --- /dev/null +++ b/src/ir/decorators.py @@ -0,0 +1,4 @@ +def two_way_subtyping(is_subtype): + def inner(self, other): + return is_subtype(self, other) or other.dynamic_subtyping(self) + return inner diff --git a/src/ir/types.py b/src/ir/types.py index 4a48f87f..4e059b5c 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -5,6 +5,7 @@ from typing import List, Dict, Set from src.ir.node import Node +from src.ir.decorators import two_way_subtyping class Variance(object): @@ -66,6 +67,19 @@ def has_type_variables(self): def is_subtype(self, other: Type): raise NotImplementedError("You have to implement 'is_subtype()'") + def dynamic_subtyping(self, other: Type): + """ + Overwritten when a certain type needs + two-way subtyping checks. + + Eg. when checking if a string type is a subtype + of union type 'Foo | string' we call this method + as `union-type.dynamic_subtyping(string_type)` + to check from the union's side. + + """ + return False + def is_assignable(self, other: Type): """ Checks of a value of the current type is assignable to 'other' type. @@ -153,6 +167,7 @@ def __hash__(self): """Hash based on the Type""" return hash(str(self.__class__)) + @two_way_subtyping def is_subtype(self, other: Type) -> bool: return other == self or other in self.get_supertypes() @@ -219,6 +234,7 @@ def _check_supertypes(self): str(t_class[0].t_constructor) + " " + \ "do not have the same types" + @two_way_subtyping def is_subtype(self, other: Type) -> bool: supertypes = self.get_supertypes() # Since the subtyping relation is transitive, we must also check @@ -273,6 +289,7 @@ def get_bound_rec(self, factory): # are out of scope in the context where we use this bound. return t.to_type_variable_free(factory) + @two_way_subtyping def is_subtype(self, other): if not self.bound: return False @@ -302,6 +319,7 @@ def __init__(self, bound=None, variance=Invariant): self.bound = bound self.variance = variance + @two_way_subtyping def is_subtype(self, other): if isinstance(other, WildCardType): if other.bound is not None: @@ -471,6 +489,7 @@ def __hash__(self): def is_type_constructor(self): return True + @two_way_subtyping def is_subtype(self, other: Type): supertypes = self.get_supertypes() matched_supertype = None @@ -695,6 +714,7 @@ def get_name(self): return "{}<{}>".format(self.name, ", ".join([t.get_name() for t in self.type_args])) + @two_way_subtyping def is_subtype(self, other: Type) -> bool: if super().is_subtype(other): return True diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 9234e68b..cf2628bd 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -4,6 +4,7 @@ import src.ir.types as tp import src.ir.ast as ast import src.utils as ut +from src.ir.decorators import two_way_subtyping class TypeScriptBuiltinFactory(bt.BuiltinFactory): def get_language(self): @@ -263,6 +264,7 @@ def __init__(self, alias, name="AliasType", primitive=False): def get_type(self): return self.alias + @two_way_subtyping def is_subtype(self, other): if isinstance(other, AliasType): return self.alias.is_subtype(other.alias) @@ -291,6 +293,7 @@ def __init__(self, literal, name="NumberLiteralType", primitive=False): def get_literal(self): return self.literal + @two_way_subtyping def is_subtype(self, other): """ A number literal type is assignable to any supertype of type 'number'. @@ -338,6 +341,7 @@ def __init__(self, literal, name="StringLiteralType", primitive=False): def get_literal(self): return '"' + self.literal + '"' + @two_way_subtyping def is_subtype(self, other): """ A string literal type is assignable to any supertype of type 'string'. @@ -426,11 +430,15 @@ def __init__(self, types, name="UnionType", primitive=False): def get_types(self): return self.types + @two_way_subtyping def is_subtype(self, other): if isinstance(other, UnionType): return set(self.types).issubset(other.types) return other.name == 'Object' + def dynamic_subtyping(self, other): + return other in set(self.types) + def get_name(self): return self.name @@ -448,7 +456,6 @@ def __init__(self, max_ut, max_in_union): self.max_ut = max_ut self.unions = [] self.candidates = [ - ObjectType(), NumberType(), BooleanType(), StringType(), @@ -462,11 +469,10 @@ def get_number_of_types(self): return ut.random.integer(2, self.max_in_union) def gen_union_type(self, gen): - """ Generates a union type that consists of - N types (where N is num_of_types). + """ Generates a union type that consists of N types + where N is a number in [2, self.max_in_union]. Args: - num_of_types - Number of types to be unionized gen - Instance of Hephaestus' generator """ From 2182d540c81ff7acf2dfbfd03206930017e623ba Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 23 Sep 2022 18:34:03 +0300 Subject: [PATCH 60/78] Refactoring of Type Substitution Mechanism --- src/ir/types.py | 75 ++++++++++++++++++----------------- src/ir/typescript_types.py | 32 +++++++-------- src/translators/typescript.py | 10 +++-- 3 files changed, 61 insertions(+), 56 deletions(-) diff --git a/src/ir/types.py b/src/ir/types.py index 4e059b5c..0606fd52 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -121,6 +121,19 @@ def get_supertypes(self): stack.append(supertype) return visited + def substitute_type_args(self, type_map, + cond=lambda t: t.has_type_variables()): + t = type_map.get(self) + if t is None or cond(t): + # Perform type substitution on the bound of the current type variable. + if self.is_type_var() and self.bound is not None: + new_bound = self.bound.substitute_type_args(type_map, cond) + return TypeParameter(self.name, self.variance, new_bound) + # The type parameter does not correspond to an abstract type + # so, there is nothing to substitute. + return self + return t + def not_related(self, other: Type): return not(self.is_subtype(other) or other.is_subtype(self)) @@ -343,6 +356,18 @@ def get_type_variables(self, factory): else: return {} + def substitute_type_args(self, type_map, + cond=lambda t: t.has_type_variables()): + if self.bound is not None: + new_bound = self.bound.substitute_type_args(type_map, cond) + return WildCardType(new_bound, variance=self.variance) + t = type_map.get(self) + if t is None or cond(t): + # The bound does not correspond to abstract type + # so there is nothing to substitute + return self + return t + def get_bound_rec(self): if not self.bound: return None @@ -385,42 +410,8 @@ def is_primitive(self): return False -def _get_type_substitution(etype, type_map, - cond=lambda t: t.has_type_variables()): - if etype.is_parameterized(): - return substitute_type_args(etype, type_map, cond) - if etype.is_wildcard() and etype.bound is not None: - new_bound = _get_type_substitution(etype.bound, type_map, cond) - return WildCardType(new_bound, variance=etype.variance) - t = type_map.get(etype) - if t is None or cond(t): - # Perform type substitution on the bound of the current type variable. - if etype.is_type_var() and etype.bound is not None: - new_bound = _get_type_substitution(etype.bound, type_map, cond) - return TypeParameter(etype.name, etype.variance, new_bound) - # The type parameter does not correspond to an abstract type - # so, there is nothing to substitute. - return etype - return t - - -def substitute_type_args(etype, type_map, - cond=lambda t: t.has_type_variables()): - assert etype.is_parameterized() - type_args = [] - for t_arg in etype.type_args: - type_args.append(_get_type_substitution(t_arg, type_map, cond)) - new_type_map = { - tp: type_args[i] - for i, tp in enumerate(etype.t_constructor.type_parameters) - } - type_con = perform_type_substitution( - etype.t_constructor, new_type_map, cond) - return ParameterizedType(type_con, type_args) - - def substitute_type(t, type_map): - return _get_type_substitution(t, type_map, lambda t: False) + return t.substitute_type_args(type_map, lambda t: False) def perform_type_substitution(etype, type_map, @@ -443,7 +434,7 @@ class X : Y>() supertypes = [] for t in etype.supertypes: if t.is_parameterized(): - supertypes.append(substitute_type_args(t, type_map)) + supertypes.append(t.substitute_type_args(type_map)) else: supertypes.append(t) type_params = [] @@ -682,6 +673,18 @@ def get_type_variables(self, factory) -> Dict[TypeParameter, Set[Type]]: continue return type_vars + def substitute_type_args(self, type_map, cond=lambda t: t.has_type_variables()): + type_args = [] + for t_arg in self.type_args: + type_args.append(t_arg.substitute_type_args(type_map, cond)) + new_type_map = { + tp: type_args[i] + for i, tp in enumerate(self.t_constructor.type_parameters) + } + type_con = perform_type_substitution( + self.t_constructor, new_type_map, cond) + return ParameterizedType(type_con, type_args) + @property def can_infer_type_args(self): return self._can_infer_type_args diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index cf2628bd..269edc82 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -96,7 +96,7 @@ def update_add_node_to_parent(self): def get_dynamic_types(self, gen_object): return [ - union_types.get_union_type(gen_object), + #union_types.get_union_type(gen_object), ] def get_constant_candidates(self, constants): @@ -439,6 +439,9 @@ def is_subtype(self, other): def dynamic_subtyping(self, other): return other in set(self.types) + def has_type_variables(self): + return any(t.has_type_variables() for t in self.types) + def get_name(self): return self.name @@ -455,19 +458,19 @@ class UnionTypeFactory: def __init__(self, max_ut, max_in_union): self.max_ut = max_ut self.unions = [] - self.candidates = [ - NumberType(), - BooleanType(), - StringType(), - NullType(), - UndefinedType(primitive=False), - ] + literal_types.get_literal_types() - self.max_in_union = (max_in_union if max_in_union <= len(self.candidates) - else len(self.candidates)) + self.max_in_union = max_in_union def get_number_of_types(self): return ut.random.integer(2, self.max_in_union) + def get_types_for_union(self, gen): + num_of_types = self.get_number_of_types() + types = set() + while len(types) < num_of_types: + t = gen.select_type(exclude_dynamic_types=True) + types.add(t) + return list(types) + def gen_union_type(self, gen): """ Generates a union type that consists of N types where N is a number in [2, self.max_in_union]. @@ -476,11 +479,7 @@ def gen_union_type(self, gen): gen - Instance of Hephaestus' generator """ - num_of_types = self.get_number_of_types() - assert num_of_types < len(self.candidates) - types = self.candidates.copy() - ut.random.shuffle(types) - types = types[0:num_of_types] + types = self.get_types_for_union(gen) gen_union = UnionType(types) self.unions.append(gen_union) return gen_union @@ -495,6 +494,7 @@ def get_union_type(self, gen_object): the already generated types or create a new one. """ + return self.gen_union_type(gen_object) generated = len(self.unions) if generated == 0: return self.gen_union_type(gen_object) @@ -574,7 +574,7 @@ def gen_type_alias_decl(gen, gen.depth += 1 gen.depth = initial_depth type_alias_decl = ts_ast.TypeAliasDeclaration( - name=ut.random.identifier('capitalize'), + name=ut.random.identifier('lower'), alias=alias_type ) gen._add_node_to_parent(gen.namespace, type_alias_decl) diff --git a/src/translators/typescript.py b/src/translators/typescript.py index 5849881a..b3b385d9 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -78,15 +78,15 @@ def get_incorrect_filename(): return TypeScriptTranslator.incorrect_filename def get_union(self, utype): - return " | ".join([self.type_arg2str(t) for t in utype.types]) + return " | ".join([self.type_arg2str(t, True) for t in utype.types]) - def type_arg2str(self, t_arg): + def type_arg2str(self, t_arg, from_union=False): # TypeScript does not have a Wildcard type if not t_arg.is_wildcard(): - return self.get_type_name(t_arg) + return self.get_type_name(t_arg, from_union) return "unknown" - def get_type_name(self, t): + def get_type_name(self, t, from_union=False): t_constructor = getattr(t, 't_constructor', None) if (isinstance(t, tst.NumberLiteralType) or isinstance(t, tst.StringLiteralType)): @@ -107,6 +107,8 @@ def get_type_name(self, t): ]), self.type_arg2str(ret_type) ) + if from_union: + return "("+res+")" return res return "{}<{}>".format(t.name, ", ".join([self.type_arg2str(ta) From ff26ffe6a6898f49c8c27be9e247b946ace273a1 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 29 Sep 2022 13:59:37 +0300 Subject: [PATCH 61/78] Add functionality for union types type substitution --- src/ir/type_utils.py | 6 ++--- src/ir/types.py | 15 +++++++---- src/ir/typescript_types.py | 51 ++++++++++++++++++++++++++++++++++- src/translators/typescript.py | 4 +-- tests/test_typescript.py | 16 +++++++++++ 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/src/ir/type_utils.py b/src/ir/type_utils.py index 51cb2934..477098b8 100644 --- a/src/ir/type_utils.py +++ b/src/ir/type_utils.py @@ -1101,10 +1101,8 @@ class B : A() if not _update_type_var_map(type_var_map, t_var, t_arg1): return {} continue - is_parameterized = isinstance(t_var.bound, - tp.ParameterizedType) - is_parameterized2 = isinstance(t_arg1, - tp.ParameterizedType) + is_parameterized = t_var.bound.is_combound() + is_parameterized2 = t_arg1.is_combound() if is_parameterized and is_parameterized2: res = unify_types(t_arg1, t_var.bound, factory) if not res or any( diff --git a/src/ir/types.py b/src/ir/types.py index 0606fd52..190b52c9 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -100,6 +100,9 @@ def is_type_var(self): def is_wildcard(self): return False + def is_combound(self): + return False + def is_parameterized(self): return False @@ -351,7 +354,7 @@ def get_type_variables(self, factory): return self.bound.get_type_variables(factory) elif self.bound.is_type_var(): return {self.bound: {self.bound.get_bound_rec(factory)}} - elif self.bound.is_parameterized(): + elif self.bound.is_combound(): return self.bound.get_type_variables(factory) else: return {} @@ -536,7 +539,7 @@ def _to_type_variable_free(t: Type, t_param, factory) -> Type: ) ) return WildCardType(bound, variance) - elif t.is_parameterized(): + elif t.is_combound(): return t.to_type_variable_free(factory) else: return t @@ -592,6 +595,9 @@ def __init__(self, t_constructor: TypeConstructor, # XXX revisit self.supertypes = copy(self.t_constructor.supertypes) + def is_combound(self): + return True + def is_parameterized(self): return True @@ -661,12 +667,11 @@ def get_type_variables(self, factory) -> Dict[TypeParameter, Set[Type]]: # This function actually returns a dict of the enclosing type variables # along with the set of their bounds. type_vars = defaultdict(set) - for i, t_arg in enumerate(self.type_args): - t_arg = t_arg + for t_arg in self.type_args: if t_arg.is_type_var(): type_vars[t_arg].add( t_arg.get_bound_rec(factory)) - elif t_arg.is_parameterized() or t_arg.is_wildcard(): + elif t_arg.is_combound() or t_arg.is_wildcard(): for k, v in t_arg.get_type_variables(factory).items(): type_vars[k].update(v) else: diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 269edc82..272f5432 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -1,3 +1,5 @@ +from collections import defaultdict + import src.ir.ast as ast import src.ir.typescript_ast as ts_ast import src.ir.builtins as bt @@ -96,7 +98,7 @@ def update_add_node_to_parent(self): def get_dynamic_types(self, gen_object): return [ - #union_types.get_union_type(gen_object), + union_types.get_union_type(gen_object), ] def get_constant_candidates(self, constants): @@ -430,6 +432,9 @@ def __init__(self, types, name="UnionType", primitive=False): def get_types(self): return self.types + def is_combound(self): + return True + @two_way_subtyping def is_subtype(self, other): if isinstance(other, UnionType): @@ -439,9 +444,53 @@ def is_subtype(self, other): def dynamic_subtyping(self, other): return other in set(self.types) + def substitute_type_args(self, type_map, + cond=lambda t: t.has_type_variables()): + new_types = [] + for t in self.types: + new_t = (t.substitute_type_args(type_map, cond) + if t.has_type_variables() + else t) + new_types.append(new_t) + return UnionType(new_types) + def has_type_variables(self): return any(t.has_type_variables() for t in self.types) + def get_type_variables(self, factory): + # This function actually returns a dict of the enclosing type variables + # along with the set of their bounds. + type_vars = defaultdict(set) + for t in self.types: + if t.is_type_var(): + type_vars[t].add( + t.get_bound_rec(factory)) + elif t.is_combound() or t.is_wildcard(): + for k, v in t.get_type_variables(factory).items(): + type_vars[k].update(v) + else: + continue + return type_vars + + def to_variance_free(self, type_var_map=None): + new_types = [] + for t in self.types: + new_types.append(t.to_variance_free(type_var_map) + if t.is_combound() + else t) + return UnionType(new_types) + + def to_type_variable_free(self, factory): + # We translate a union type that contains + # type variables into a parameterized type that is + # type variable free. + new_types = [] + for t in self.types: + new_types.append(t.to_type_variable_free(factory) + if t.is_combound() + else t) + return UnionType(new_types) + def get_name(self): return self.name diff --git a/src/translators/typescript.py b/src/translators/typescript.py index b3b385d9..0fa5d93b 100644 --- a/src/translators/typescript.py +++ b/src/translators/typescript.py @@ -78,7 +78,7 @@ def get_incorrect_filename(): return TypeScriptTranslator.incorrect_filename def get_union(self, utype): - return " | ".join([self.type_arg2str(t, True) for t in utype.types]) + return " | ".join([self.get_type_name(t, True) for t in utype.types]) def type_arg2str(self, t_arg, from_union=False): # TypeScript does not have a Wildcard type @@ -108,7 +108,7 @@ def get_type_name(self, t, from_union=False): self.type_arg2str(ret_type) ) if from_union: - return "("+res+")" + return "(" + res + ")" return res return "{}<{}>".format(t.name, ", ".join([self.type_arg2str(ta) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 93d67010..92a9c94a 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -57,3 +57,19 @@ def test_union_type_param(): assert not union2.is_subtype(union1) assert not union1.is_subtype(t_param) assert not t_param.is_subtype(union1) + + +def test_union_type_substitution(): + type_param1 = tp.TypeParameter("T1") + type_param2 = tp.TypeParameter("T2") + type_param3 = tp.TypeParameter("T3") + type_param4 = tp.TypeParameter("T4") + + foo = tp.TypeConstructor("Foo", [type_param1, type_param2]) + foo_p = foo.new([tst.NumberType(), type_param3]) + + union = tst.UnionType([tst.StringLiteralType("bar"), foo_p]) + ptype = tp.substitute_type(union, {type_param3: type_param4}) + + assert ptype.types[1].type_args[0] == tst.NumberType() + assert ptype.types[1].type_args[1] == type_param4 From 3e06d6f71d116ef3506ee9d2905ae9c982a11187 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Thu, 29 Sep 2022 14:23:31 +0300 Subject: [PATCH 62/78] Add more unit tests for union type substitution --- tests/test_typescript.py | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 92a9c94a..acb23c32 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -73,3 +73,48 @@ def test_union_type_substitution(): assert ptype.types[1].type_args[0] == tst.NumberType() assert ptype.types[1].type_args[1] == type_param4 + + +def test_union_type_substitution_type_var_bound(): + type_param1 = tp.TypeParameter("T1") + type_param2 = tp.TypeParameter("T2", bound=type_param1) + type_map = {type_param1: tst.StringType()} + + union = tst.UnionType([tst.NumberType(), type_param2]) + ptype_union = tp.substitute_type(union, type_map) + ptype = ptype_union.types[1] + + + assert ptype.name == type_param2.name + assert ptype.variance == type_param2.variance + assert ptype.bound == tst.StringType() + + +def test_union_to_type_variable_free(): + type_param1 = tp.TypeParameter("T1") + type_param2 = tp.TypeParameter("T2") + foo = tp.TypeConstructor("Foo", [type_param1]) + foo_t = foo.new([type_param2]) + union = tst.UnionType([foo_t, tst.StringLiteralType("bar")]) + + union_n = union.to_type_variable_free(tst.TypeScriptBuiltinFactory()) + foo_n = union_n.types[0] + assert foo_n.type_args[0] == tp.WildCardType(tst.ObjectType(), variance=tp.Covariant) + + type_param2.bound = tst.NumberType() + foo_t = foo.new([type_param2]) + union = tst.UnionType([foo_t, tst.NumberLiteralType(43)]) + + union_n = union.to_type_variable_free(tst.TypeScriptBuiltinFactory()) + foo_n = union_n.types[0] + assert foo_n.type_args[0] == tp.WildCardType(tst.NumberType(), variance=tp.Covariant) + + bar = tp.TypeConstructor("Bar", [tp.TypeParameter("T")]) + bar_p = bar.new([type_param2]) + foo_t = foo.new([bar_p]) + union = tst.UnionType([foo_t, tst.NumberType(), tst.StringType(), tst.AliasType(tst.StringLiteralType("foobar"))]) + + union_n = union.to_type_variable_free(tst.TypeScriptBuiltinFactory()) + foo_n = union_n.types[0] + assert foo_n.type_args[0] == bar.new( + [tp.WildCardType(tst.NumberType(), variance=tp.Covariant)]) From 291397b3285370c0aac0c6241c1152bcf93f48a6 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 30 Sep 2022 16:09:28 +0300 Subject: [PATCH 63/78] Add type unification support for union types --- src/ir/type_utils.py | 3 +++ src/ir/typescript_types.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/ir/type_utils.py b/src/ir/type_utils.py index 477098b8..2d299587 100644 --- a/src/ir/type_utils.py +++ b/src/ir/type_utils.py @@ -1040,6 +1040,9 @@ def unify_types(t1: tp.Type, t2: tp.Type, factory, class A class B : A() """ + if t1.is_combound() and not t1.is_parameterized(): + return t1.unify_types(t2, factory, same_type) + if same_type and type(t1) != type(t2): return {} diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 272f5432..a1126695 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -491,6 +491,42 @@ def to_type_variable_free(self, factory): else t) return UnionType(new_types) + def unify_types(self, other, factory, same_type=True): + """ + This is used in src.ir.type_utils in the function + unify_types. + + We delegate work here when the first of the two + types that are passed to that function is a union type. + + For more information on the function see the detailed + explanation at the unify_types function definition. + + The only way a union type type-unification can be + achieved is when the first of the two types + (here: self) is a union type and the second + is either a type variable or another union type + with similar union structure and has at least one + type variable in its union. + + """ + type_var_map = {} + if (isinstance(other, UnionType) + and len(self.types) == len(other.types) + and other.has_type_variables()): + for i, t in enumerate(self.types): + t1 = t + t2 = other.types[i] + is_type_var = t2.is_type_var() + if not is_type_var: + if not t2.is_subtype(t1): + return {} + else: + type_var_map[t2] = t1 + elif other.is_type_var(): + type_var_map[other] = self + return type_var_map + def get_name(self): return self.name From 123d20a037e2ac37309fa13d75c85668b59f60bc Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 30 Sep 2022 16:20:52 +0300 Subject: [PATCH 64/78] Add unit tests for union type unification --- tests/test_typescript.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index acb23c32..ed68a3aa 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -1,6 +1,7 @@ import src.ir.typescript_types as tst import src.ir.typescript_ast as ts_ast import src.ir.types as tp +import src.ir.type_utils as tu def test_type_alias_with_literals(): string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringType()).get_type() @@ -118,3 +119,30 @@ def test_union_to_type_variable_free(): foo_n = union_n.types[0] assert foo_n.type_args[0] == bar.new( [tp.WildCardType(tst.NumberType(), variance=tp.Covariant)]) + + +def test_union_type_unification_type_var(): + union = tst.UnionType([tst.StringType(), tst.StringLiteralType("foo")]) + type_param = tp.TypeParameter("T") + + type_var_map = tu.unify_types(union, type_param, tst.TypeScriptBuiltinFactory()) + assert len(type_var_map) == 1 + assert type_var_map == {type_param: union} + + +def test_unify_two_union_types(): + type_param = tp.TypeParameter("T") + union1 = tst.UnionType([tst.NumberLiteralType(1410), tst.NumberType(), tst.StringType()]) + union2 = tst.UnionType([type_param, tst.NumberType(), tst.StringType()]) + + type_var_map = tu.unify_types(union1, union2, tst.TypeScriptBuiltinFactory()) + assert len(type_var_map) == 1 + assert type_var_map == {type_param: union1.types[0]} + + type_param2 = tp.TypeParameter("G") + union3 = tst.UnionType([type_param, type_param2, tst.StringLiteralType("foo")]) + + type_var_map = tu.unify_types(union1, union3, tst.TypeScriptBuiltinFactory()) + assert len(type_var_map) == 2 + assert type_var_map == {type_param: union1.types[0], + type_param2: union1.types[1]} From 4bf17c6a6ba715a9699b57ccf26a3f910f27d6f9 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Fri, 30 Sep 2022 16:40:26 +0300 Subject: [PATCH 65/78] Change assertion statement to include union type cases --- src/generators/generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 68170270..bc9509e9 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -2899,8 +2899,8 @@ def _create_type_params_from_etype(self, etype: tp.Type): type_params[0].variance = tp.Invariant return type_params, {etype: type_params[0]}, True - # the given type is parameterized - assert isinstance(etype, (tp.ParameterizedType, tp.WildCardType)) + # the given type is combound + assert etype.is_combound() or etype.is_wildcard() type_vars = etype.get_type_variables(self.bt_factory) type_params = self.gen_type_params( len(type_vars), with_variance=self.language == 'kotlin') From 832c73a758aec81d1820b7b707ce73210e0e2ee6 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 19 Oct 2022 18:38:00 +0300 Subject: [PATCH 66/78] Refactor and fully outline the procedure for type unification for union types. --- src/ir/type_utils.py | 6 +- src/ir/typescript_types.py | 214 +++++++++++++++++++++++++++++++++---- tests/test_typescript.py | 5 +- 3 files changed, 198 insertions(+), 27 deletions(-) diff --git a/src/ir/type_utils.py b/src/ir/type_utils.py index 2d299587..fd140560 100644 --- a/src/ir/type_utils.py +++ b/src/ir/type_utils.py @@ -1040,8 +1040,10 @@ def unify_types(t1: tp.Type, t2: tp.Type, factory, class A class B : A() """ - if t1.is_combound() and not t1.is_parameterized(): - return t1.unify_types(t2, factory, same_type) + if t2.is_combound() and not t2.is_parameterized(): + return t2.unify_types(t1, factory, same_type) + elif t1.is_combound() and not t1.is_parameterized() and t2.is_type_var(): + return {t2: t1} if same_type and type(t1) != type(t2): return {} diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index a1126695..80523166 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -438,7 +438,10 @@ def is_combound(self): @two_way_subtyping def is_subtype(self, other): if isinstance(other, UnionType): - return set(self.types).issubset(other.types) + for t in self.types: + if not any(t.is_subtype(other_t) for other_t in other.types): + return False + return True return other.name == 'Object' def dynamic_subtyping(self, other): @@ -491,7 +494,7 @@ def to_type_variable_free(self, factory): else t) return UnionType(new_types) - def unify_types(self, other, factory, same_type=True): + def unify_types(self, t1, factory, same_type=True): """ This is used in src.ir.type_utils in the function unify_types. @@ -501,31 +504,196 @@ def unify_types(self, other, factory, same_type=True): For more information on the function see the detailed explanation at the unify_types function definition. + """ + t2 = self + type_var_map = {} + + if not t2.has_type_variables(): + return {} + + # If T1 is a union type, then get all its types. + t1_types = (t1.types if t1.is_combound() and + not t1.is_parameterized() + else [t1]) + + if not t1.is_subtype(t2): + # Get the Type Variables of T2 + t_vars = dict(t2.get_type_variables(factory)) + + # Find which types of t1 are not already in t2 + add_to_t2 = set(t1_types) - set(t2.types) + + # If T1 is a union type like 100 | number | string, + # we do not need to substitute both 100 and number + # in the type variables of T2. + # Since number is a supertype of 100, if we only + # substitute number, then we have also covered + # the subtypes of 100 too! + # Hence, we only substitute necessary types in T2 + # by ensuring that the T1 type we will subtitute + # is NOT a subtype of any other types in the T1 union type. + for t1_t in t1_types: + if any(t1_t.is_subtype(other_t1_t) + and t1_t is not other_t1_t + for other_t1_t in t1_types): + add_to_t2.remove(t1_t) + + # If T1 is a union type, and its types that we need + # to substitute in T2 are more than the type variables of T2, + # then there is no substitution that ensures T1 <: T2. + if len(add_to_t2) > len(t_vars): + return {} + + # Get bounds of type variables of T2 + bounds = [b for b in t_vars.values() if b != {None}] + + # If the type variables have no bounds, then we can just assign + # the types from T1 to any type variable. + if not bounds: + temp = list(t_vars.keys()) + for t in add_to_t2: + tv = temp.pop(0) + type_var_map[tv] = t + return type_var_map + + # Get all the possible substitutions between T1 types (add_to_t2) + # and type variables of T2. A type variable can be substituted + # with a type in T1 if the type variable has no bound or + # if the type is a subtype of a bound. + possible_substitutions = {} + for t in add_to_t2: + subs = set() + # Below remember that: + # - k is the type variable + # - v is a set containing its bounds + for k,v in t_vars.items(): + if v == {None} or any(t.is_subtype(b) for b in v): + subs.add(k) + + # If there are no possible substitutions with type variables + # for any given type in T1 (add_to_t2) then there is no + # substitution that ensures T1 <: T2. + if not subs: + return {} + possible_substitutions[t] = subs + + # Decide the order of assignments (if possible) + assignments, flag = self.assign_types_to_type_vars(possible_substitutions) + if not flag: + return {} + type_var_map.update(assignments) + + + # Instantiate any not-utilized T2 type variables with their bound (if they have one). + # If they don't have a bound instantiate them with a type from T1. + leftover_type_vars = [t for t in t2.types if t.is_type_var() and t not in type_var_map] + for type_var in leftover_type_vars: + type_var_map[type_var] = type_var.bound if type_var.bound else t1_types[0] + + return type_var_map + + def assign_types_to_type_vars(self, possible_subs): + """ + This method is a helper for the method unify_types of union types (see above) + + Args: + - possible_subs: A dict containing (types.Type: set) pairs, which represents possible + T2 type variable (value) susbstitutions with a T1 type (key). + The set values contain compatible type vars of T2. + + - t2_t_vars: The type variables of T2 + + - t1_types: The types of T1 that we want to substitute in T2 + + Returns: + - A dict of (TypeVariable: types.Type) pairs representing the substitutions in T2 + + This method is needed because we need to find the correct substitutions + of the type variables in the T2 union type, in order for all T1 types (T1 can be a union type itself) + to be substituted in T2 (if possible). - The only way a union type type-unification can be - achieved is when the first of the two types - (here: self) is a union type and the second - is either a type variable or another union type - with similar union structure and has at least one - type variable in its union. + Consider the following case: + T1: number | string + T2: boolean | X | Y extends number + + In this case, if we first substituted X with the type number from T1, + we would have been left with the type variable Y, which is not compatible + with the type string. + + As a result we would falsely conclude that we can not unify the types T1 and T2, + when in reality, had we just substituted Y with number and X with string, + we would have been ably to correctly unify the two types. + + A naive solution would be to find all the possible substitution permutations + between T1 types and T2 type variables. + + Using our approach, after first creating the possible_subs dict, + which contains all compatible T2 type variables for each T1 type, + we first substitute the T1 type that is compatible with the FEWEST + T2 type variables at any given moment. + + Going back two the above case, here is how we tackle it with our new approach: + + T1: number | string + T2: boolean | X | Y extends number + + (1) We find the possible substitutions for each T1 type (done outside this method) + possible_subs = {number: {X, Y}, + string: {X} + } + + (2) We sort the dict based on the length of the type variable sets corresponding to each type + sorted_type_subs = [(string, {X}), (number, {X, Y})] + + (3) Now we work on the first element, the pair of string and {X}. We assign the substitution + X: string. We remove X from all possible substitutions for other T1 types and then + delete the pair with key string from our possible_subs dict. + + (4) We sort the dict again, it now looks like this: + sorted_type_subs = [(number, {Y})] + + (5) Assign the substitution Y: number and repeat the rest of step (3) + + (6) The possible_subs dict is now empty, so we return our substitution dictionary + return {X: string, Y: number} """ type_var_map = {} - if (isinstance(other, UnionType) - and len(self.types) == len(other.types) - and other.has_type_variables()): - for i, t in enumerate(self.types): - t1 = t - t2 = other.types[i] - is_type_var = t2.is_type_var() - if not is_type_var: - if not t2.is_subtype(t1): - return {} - else: - type_var_map[t2] = t1 - elif other.is_type_var(): - type_var_map[other] = self - return type_var_map + + # Continue trying to find type variable susbstitutions until + # all T1 types are substituted in T2. + while possible_subs: + # Sort the possible_subs dict, in order to first find a substitution for the T1 + # type with the fewest compatible T2 type variables. + sorted_type_subs = sorted(possible_subs.items(), key=lambda x: len(x), reverse=True) + + # Get the first (T1 type, T2 type variable) pair (sorted_type_subs is a tuples list) + type_to_substitute, compatible_tvars = sorted_type_subs[0] + + # If there aren't any compatible_tvars, then that means that there is no possible + # order of substitutions that ensures all types in T1 are substituted in T2. + # Hence, we return a False flag to indicate that the type unification is not possible. + # Note: at this point this happens if at previous iterations of the while loop + # we substituted all the type variables that are compatible with this specific type_to_substitute. + if not compatible_tvars: + return ({}, False) + + # Get any of the compatible type variables and substitute it with the T1 type + chosen_tvar = compatible_tvars.pop() + type_var_map[chosen_tvar] = type_to_substitute + + # Remove the substituted type variable from the possible substitutions + # of all other T1 types. + for k in list(possible_subs.keys()): + if chosen_tvar in possible_subs[k]: + possible_subs.remove(chosen_tvar) + + # Delete the possible substitutions of the T1 type we just substituted in T2 + del possible_subs[type_to_substitute] + + # Return the substitutions we found and a flag confirming that there is a possible + # order of substitutions that gurantees type unification. + return (type_var_map, True) def get_name(self): return self.name diff --git a/tests/test_typescript.py b/tests/test_typescript.py index ed68a3aa..85cfa127 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -134,6 +134,7 @@ def test_unify_two_union_types(): type_param = tp.TypeParameter("T") union1 = tst.UnionType([tst.NumberLiteralType(1410), tst.NumberType(), tst.StringType()]) union2 = tst.UnionType([type_param, tst.NumberType(), tst.StringType()]) + assert union1.is_subtype(union2) type_var_map = tu.unify_types(union1, union2, tst.TypeScriptBuiltinFactory()) assert len(type_var_map) == 1 @@ -144,5 +145,5 @@ def test_unify_two_union_types(): type_var_map = tu.unify_types(union1, union3, tst.TypeScriptBuiltinFactory()) assert len(type_var_map) == 2 - assert type_var_map == {type_param: union1.types[0], - type_param2: union1.types[1]} + assert type_param, type_param2 in type_var_map + assert union1.types[1], union1.types[2] in type_var_map.values() From e385331d9e26ab2e36541ebb0230cf7999a700ac Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 2 Nov 2022 16:50:03 +0200 Subject: [PATCH 67/78] Add more unit tests for type unification, solve bugs in the type unification algorithm --- src/ir/typescript_types.py | 9 +++++--- src/resources/typescript_keywords | 3 ++- tests/test_typescript.py | 35 ++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 80523166..e7a2c5a4 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -532,7 +532,7 @@ def unify_types(self, t1, factory, same_type=True): # Hence, we only substitute necessary types in T2 # by ensuring that the T1 type we will subtitute # is NOT a subtype of any other types in the T1 union type. - for t1_t in t1_types: + for t1_t in list(add_to_t2): if any(t1_t.is_subtype(other_t1_t) and t1_t is not other_t1_t for other_t1_t in t1_types): @@ -665,7 +665,7 @@ def assign_types_to_type_vars(self, possible_subs): while possible_subs: # Sort the possible_subs dict, in order to first find a substitution for the T1 # type with the fewest compatible T2 type variables. - sorted_type_subs = sorted(possible_subs.items(), key=lambda x: len(x), reverse=True) + sorted_type_subs = sorted(list(possible_subs.items()), key=lambda x: len(x[1]), reverse=False) # Get the first (T1 type, T2 type variable) pair (sorted_type_subs is a tuples list) type_to_substitute, compatible_tvars = sorted_type_subs[0] @@ -686,7 +686,7 @@ def assign_types_to_type_vars(self, possible_subs): # of all other T1 types. for k in list(possible_subs.keys()): if chosen_tvar in possible_subs[k]: - possible_subs.remove(chosen_tvar) + possible_subs[k].remove(chosen_tvar) # Delete the possible substitutions of the T1 type we just substituted in T2 del possible_subs[type_to_substitute] @@ -698,6 +698,9 @@ def assign_types_to_type_vars(self, possible_subs): def get_name(self): return self.name + def __str__(self): + return f'UnionType(TS)({" | ".join([str(t) for t in self.types])})' + def __eq__(self, other): return (self.__class__ == other.__class__ and self.name == other.name and diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords index 566f5ffc..bd936a9a 100644 --- a/src/resources/typescript_keywords +++ b/src/resources/typescript_keywords @@ -6,4 +6,5 @@ Delete debugger arguments let -export \ No newline at end of file +export +symbol diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 85cfa127..5e77adea 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -1,3 +1,4 @@ +from src.ir.builtins import NumberType import src.ir.typescript_types as tst import src.ir.typescript_ast as ts_ast import src.ir.types as tp @@ -130,7 +131,7 @@ def test_union_type_unification_type_var(): assert type_var_map == {type_param: union} -def test_unify_two_union_types(): +def test_union_type_unification(): type_param = tp.TypeParameter("T") union1 = tst.UnionType([tst.NumberLiteralType(1410), tst.NumberType(), tst.StringType()]) union2 = tst.UnionType([type_param, tst.NumberType(), tst.StringType()]) @@ -147,3 +148,35 @@ def test_unify_two_union_types(): assert len(type_var_map) == 2 assert type_param, type_param2 in type_var_map assert union1.types[1], union1.types[2] in type_var_map.values() + +def test_union_type_unification2(): + union = tst.UnionType([tst.NumberType(), tst.StringType()]) + assert tu.unify_types(tst.BooleanType(), union, tst.TypeScriptBuiltinFactory()) == {} + + t1 = tst.NumberType() + t2 = tst.UnionType([tst.NumberType(), tp.TypeParameter("T")]) + res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) + assert len(res) == 1 and res[t2.types[1]] == t1 + + t1 = tst.UnionType([tst.NumberType(), tst.StringType()]) + res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) + assert len(res) == 1 and res[t2.types[1]] == t1.types[1] + + t1 = tst.UnionType([tst.NumberType(), tst.StringLiteralType("foo"), tst.StringType()]) + res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) + assert len(res) == 1 and res[t2.types[1]] == t1.types[2] + + t1 = tst.UnionType([tst.NumberType(), tst.NumberLiteralType(100), tst.BooleanType(), tst.StringLiteralType("foo"), tst.StringType()]) + + t_param1 = tp.TypeParameter("T", bound=tst.StringType()) + helper_union = tst.UnionType([tst.BooleanType(), tst.StringType()]) + t_param2 = tp.TypeParameter("G", bound=helper_union) + t2 = tst.UnionType([tst.NumberType(), t_param1, t_param2]) + + # Unify t1: number | 100 | boolean | "foo" | string + # with t2: number | T extends string | G extends (boolean | string) + # Result should be {T: StringType, G: BooleanType} + res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) + assert (len(res) == 2 and + res[t2.types[1]] == t1.types[4] and + res[t2.types[2]] == t1.types[2]) From 137b4742692e23c67f223f201b36be002b68539f Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Fri, 11 Nov 2022 14:51:01 +0200 Subject: [PATCH 68/78] Remove dead import --- src/compilers/typescript.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/compilers/typescript.py b/src/compilers/typescript.py index 11a4f184..3354bde6 100644 --- a/src/compilers/typescript.py +++ b/src/compilers/typescript.py @@ -1,4 +1,3 @@ -from collections import defaultdict import re import os from src.compilers.base import BaseCompiler From 26e58f9df2fe690c40cc75f9cc2ee52bab25d2f0 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Mon, 14 Nov 2022 12:18:41 +0200 Subject: [PATCH 69/78] Fix type unification for type params and combound types --- src/ir/type_utils.py | 8 ++++++-- tests/test_typescript.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ir/type_utils.py b/src/ir/type_utils.py index fd140560..e5258682 100644 --- a/src/ir/type_utils.py +++ b/src/ir/type_utils.py @@ -1042,8 +1042,12 @@ class B : A() """ if t2.is_combound() and not t2.is_parameterized(): return t2.unify_types(t1, factory, same_type) - elif t1.is_combound() and not t1.is_parameterized() and t2.is_type_var(): - return {t2: t1} + elif t1.is_combound() and t2.is_type_var(): + bound = t2.get_bound_rec(factory) + if bound is None or t1.is_subtype(bound): + return {t2: t1} + else: + return {} if same_type and type(t1) != type(t2): return {} diff --git a/tests/test_typescript.py b/tests/test_typescript.py index 5e77adea..c467aff0 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -4,6 +4,7 @@ import src.ir.types as tp import src.ir.type_utils as tu + def test_type_alias_with_literals(): string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringType()).get_type() number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberType()).get_type() @@ -16,6 +17,7 @@ def test_type_alias_with_literals(): assert number_lit.is_subtype(number_alias) assert not number_alias.is_subtype(number_lit) + def test_type_alias_with_literals2(): string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringLiteralType("foo")).get_type() number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberLiteralType(5)).get_type() @@ -28,6 +30,7 @@ def test_type_alias_with_literals2(): assert string_alias.is_subtype(string_lit) assert number_alias.is_subtype(number_lit) + def test_union_types_simple(): union_1 = tst.UnionType([tst.NumberType(), tst.BooleanType()]) @@ -42,6 +45,11 @@ def test_union_types_simple(): assert union_1.is_subtype(union_3) +def test_union_types_other_types(): + union = tst.UnionType([tst.NumberType(), tst.BooleanType()]) + assert tst.NumberType().is_subtype(union) + + def test_union_type_assign(): union = tst.UnionType([tst.StringType(), tst.NumberType(), tst.BooleanType(), tst.ObjectType()]) foo = tst.StringType() @@ -130,6 +138,22 @@ def test_union_type_unification_type_var(): assert len(type_var_map) == 1 assert type_var_map == {type_param: union} + # Case 2: unify a union with a bounded type param, which has an + # incompatible bound with the given union. + union = tst.UnionType([tst.NumberType(), tst.StringType()]) + type_param = tp.TypeParameter("T", bound=tst.NumberType()) + + type_var_map = tu.unify_types(union, type_param, + tst.TypeScriptBuiltinFactory()) + assert type_var_map == {} + + + # Case 3: unify a union with a bounded type param, which has a compatible + # bound with the given union. + type_param = tp.TypeParameter("T", bound=union) + type_var_map = tu.unify_types(union, type_param, + tst.TypeScriptBuiltinFactory()) + assert type_var_map == {type_param: union} def test_union_type_unification(): type_param = tp.TypeParameter("T") @@ -149,6 +173,7 @@ def test_union_type_unification(): assert type_param, type_param2 in type_var_map assert union1.types[1], union1.types[2] in type_var_map.values() + def test_union_type_unification2(): union = tst.UnionType([tst.NumberType(), tst.StringType()]) assert tu.unify_types(tst.BooleanType(), union, tst.TypeScriptBuiltinFactory()) == {} From 2ee782cb1d5448f951b5b084cc78a14df1c50572 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Mon, 14 Nov 2022 15:30:10 +0200 Subject: [PATCH 70/78] Correctly remove type variables from a union type --- src/ir/typescript_types.py | 15 +++++++++++---- tests/test_typescript.py | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index e7a2c5a4..4d0e2a89 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -489,9 +489,14 @@ def to_type_variable_free(self, factory): # type variable free. new_types = [] for t in self.types: - new_types.append(t.to_type_variable_free(factory) - if t.is_combound() - else t) + if t.is_combound(): + new_type = t.to_type_variable_free(factory) + elif t.is_type_var(): + bound = t.get_bound_rec(factory) + new_type = factory.get_any_type() if bound is None else bound + else: + new_type = t + new_types.append(new_type) return UnionType(new_types) def unify_types(self, t1, factory, same_type=True): @@ -787,7 +792,7 @@ def __init__(self, name="Array"): "T", variance=tp.Covariant)]) -class FunctionType(tp.TypeConstructor, ObjectType): +class FunctionType(tp.TypeConstructor): def __init__(self, nr_type_parameters: int): name = "Function" + str(nr_type_parameters) @@ -811,6 +816,7 @@ def __init__(self, nr_type_parameters: int): features of typescript. """ + def gen_type_alias_decl(gen, etype=None) -> ts_ast.TypeAliasDeclaration: """ Generate a Type Declaration (Type Alias) @@ -836,6 +842,7 @@ def gen_type_alias_decl(gen, gen._add_node_to_parent(gen.namespace, type_alias_decl) return type_alias_decl + def add_type_alias(gen, namespace, type_name, ta_decl): gen.context._add_entity(namespace, 'types', type_name, ta_decl.get_type()) gen.context._add_entity(namespace, 'decls', type_name, ta_decl) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index c467aff0..db0f99f3 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -205,3 +205,17 @@ def test_union_type_unification2(): assert (len(res) == 2 and res[t2.types[1]] == t1.types[4] and res[t2.types[2]] == t1.types[2]) + + +def test_union_to_type_variable_free(): + type_param = tp.TypeParameter("S") + union = tst.UnionType([tst.NumberType(), type_param]) + + new_union = union.to_type_variable_free(tst.TypeScriptBuiltinFactory()) + assert new_union == tst.UnionType([tst.NumberType(), + tst.TypeScriptBuiltinFactory().get_any_type()]) + + type_param = tp.TypeParameter("S", bound=tst.StringType()) + union = tst.UnionType([tst.NumberType(), type_param]) + new_union = union.to_type_variable_free(tst.TypeScriptBuiltinFactory()) + assert new_union == tst.UnionType([tst.NumberType(), tst.StringType()]) From cbd3399da9d9a1701467c4a72949e1218bbfdcd9 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Tue, 15 Nov 2022 14:39:46 +0200 Subject: [PATCH 71/78] Add one more typescript keyword --- src/resources/typescript_keywords | 1 + 1 file changed, 1 insertion(+) diff --git a/src/resources/typescript_keywords b/src/resources/typescript_keywords index bd936a9a..614838dc 100644 --- a/src/resources/typescript_keywords +++ b/src/resources/typescript_keywords @@ -8,3 +8,4 @@ arguments let export symbol +infer From d13bc031fc51d53705f7ce985e3956cbd98be997 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 23 Nov 2022 15:54:07 +0200 Subject: [PATCH 72/78] Name changes and resolving PR comments --- src/generators/generator.py | 24 ++++++++++++------------ src/ir/builtins.py | 7 ++++--- src/ir/type_utils.py | 8 ++++---- src/ir/types.py | 10 +++++----- src/ir/typescript_types.py | 16 ++++++++-------- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index bc9509e9..412135d9 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -1951,7 +1951,7 @@ def get_types(self, exclude_contravariants=False, exclude_type_vars=False, exclude_function_types=False, - exclude_dynamic_types=False) -> List[tp.Type]: + exclude_native_compound_types=False) -> List[tp.Type]: """Get all available types. Including user-defined types, built-ins, and function types. @@ -1965,7 +1965,7 @@ def get_types(self, exclude_contravariants: exclude contravariant type parameters. exclude_type_vars: exclude type variables. exclude_function_types: exclude function types. - exclude_dynamic_types: exclude dynamic types. + exclude_native_compound_types: exclude native compound types. Returns: A list of available types. @@ -1995,12 +1995,12 @@ def get_types(self, if t.name != self.bt_factory.get_array_type().name ] - dynamic = (self.bt_factory.get_dynamic_types(self) - if not exclude_dynamic_types + compound_types = (self.bt_factory.get_compound_types(self) + if not exclude_native_compound_types else []) if exclude_function_types: - return usr_types + builtins + dynamic - return usr_types + builtins + dynamic + self.function_types + return usr_types + builtins + compound_types + return usr_types + builtins + compound_types + self.function_types def select_type(self, ret_types=True, @@ -2008,7 +2008,7 @@ def select_type(self, exclude_covariants=False, exclude_contravariants=False, exclude_function_types=False, - exclude_dynamic_types=False) -> tp.Type: + exclude_native_compound_types=False) -> tp.Type: """Select a type from the all available types. It will always instantiating type constructors to parameterized types. @@ -2020,7 +2020,7 @@ def select_type(self, exclude_covariants: exclude covariant type parameters. exclude_contravariants: exclude contravariant type parameters. exclude_function_types: exclude function types. - eclude_dynamic_types: exclude dynamic types. + exclude_native_compound_types: exclude native compound types. Returns: Returns a type. @@ -2030,7 +2030,7 @@ def select_type(self, exclude_covariants=exclude_covariants, exclude_contravariants=exclude_contravariants, exclude_function_types=exclude_function_types, - exclude_dynamic_types=exclude_dynamic_types) + exclude_native_compound_types=exclude_native_compound_types) stype = ut.random.choice(types) if stype.is_type_constructor(): exclude_type_vars = stype.name == self.bt_factory.get_array_type().name @@ -2040,7 +2040,7 @@ def select_type(self, exclude_contravariants=True, exclude_type_vars=exclude_type_vars, exclude_function_types=exclude_function_types, - exclude_dynamic_types=exclude_dynamic_types), + exclude_native_compound_types=exclude_native_compound_types), enable_pecs=self.enable_pecs, disable_variance_functions=self.disable_variance_functions, variance_choices={} @@ -2899,8 +2899,8 @@ def _create_type_params_from_etype(self, etype: tp.Type): type_params[0].variance = tp.Invariant return type_params, {etype: type_params[0]}, True - # the given type is combound - assert etype.is_combound() or etype.is_wildcard() + # the given type is compound + assert etype.is_compound() or etype.is_wildcard() type_vars = etype.get_type_variables(self.bt_factory) type_params = self.gen_type_params( len(type_vars), with_variance=self.language == 'kotlin') diff --git a/src/ir/builtins.py b/src/ir/builtins.py index 778a17f1..88777488 100644 --- a/src/ir/builtins.py +++ b/src/ir/builtins.py @@ -138,9 +138,10 @@ def get_function_types(self, max_parameters): def get_nothing(self): raise NotImplementedError - def get_dynamic_types(self, gen_object): - """ A type is considered dynamic if it can utilize - both a builtin type and a user-defined type. + def get_compound_types(self, gen_object): + """ A type is considered compound if it can consist + of other types. This function is used to add a lanuage's + native compound types to the generator. Eg. A TypeScript Union Type: string | myClass diff --git a/src/ir/type_utils.py b/src/ir/type_utils.py index e5258682..7378e659 100644 --- a/src/ir/type_utils.py +++ b/src/ir/type_utils.py @@ -1040,9 +1040,9 @@ def unify_types(t1: tp.Type, t2: tp.Type, factory, class A class B : A() """ - if t2.is_combound() and not t2.is_parameterized(): + if t2.is_compound() and not t2.is_parameterized(): return t2.unify_types(t1, factory, same_type) - elif t1.is_combound() and t2.is_type_var(): + elif t1.is_compound() and t2.is_type_var(): bound = t2.get_bound_rec(factory) if bound is None or t1.is_subtype(bound): return {t2: t1} @@ -1110,8 +1110,8 @@ class B : A() if not _update_type_var_map(type_var_map, t_var, t_arg1): return {} continue - is_parameterized = t_var.bound.is_combound() - is_parameterized2 = t_arg1.is_combound() + is_parameterized = t_var.bound.is_compound() + is_parameterized2 = t_arg1.is_compound() if is_parameterized and is_parameterized2: res = unify_types(t_arg1, t_var.bound, factory) if not res or any( diff --git a/src/ir/types.py b/src/ir/types.py index 190b52c9..fd3b3809 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -100,7 +100,7 @@ def is_type_var(self): def is_wildcard(self): return False - def is_combound(self): + def is_compound(self): return False def is_parameterized(self): @@ -354,7 +354,7 @@ def get_type_variables(self, factory): return self.bound.get_type_variables(factory) elif self.bound.is_type_var(): return {self.bound: {self.bound.get_bound_rec(factory)}} - elif self.bound.is_combound(): + elif self.bound.is_compound(): return self.bound.get_type_variables(factory) else: return {} @@ -539,7 +539,7 @@ def _to_type_variable_free(t: Type, t_param, factory) -> Type: ) ) return WildCardType(bound, variance) - elif t.is_combound(): + elif t.is_compound(): return t.to_type_variable_free(factory) else: return t @@ -595,7 +595,7 @@ def __init__(self, t_constructor: TypeConstructor, # XXX revisit self.supertypes = copy(self.t_constructor.supertypes) - def is_combound(self): + def is_compound(self): return True def is_parameterized(self): @@ -671,7 +671,7 @@ def get_type_variables(self, factory) -> Dict[TypeParameter, Set[Type]]: if t_arg.is_type_var(): type_vars[t_arg].add( t_arg.get_bound_rec(factory)) - elif t_arg.is_combound() or t_arg.is_wildcard(): + elif t_arg.is_compound() or t_arg.is_wildcard(): for k, v in t_arg.get_type_variables(factory).items(): type_vars[k].update(v) else: diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 4d0e2a89..f29e08b4 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -96,7 +96,7 @@ def update_add_node_to_parent(self): ts_ast.TypeAliasDeclaration: add_type_alias, } - def get_dynamic_types(self, gen_object): + def get_compound_types(self, gen_object): return [ union_types.get_union_type(gen_object), ] @@ -432,7 +432,7 @@ def __init__(self, types, name="UnionType", primitive=False): def get_types(self): return self.types - def is_combound(self): + def is_compound(self): return True @two_way_subtyping @@ -468,7 +468,7 @@ def get_type_variables(self, factory): if t.is_type_var(): type_vars[t].add( t.get_bound_rec(factory)) - elif t.is_combound() or t.is_wildcard(): + elif t.is_compound() or t.is_wildcard(): for k, v in t.get_type_variables(factory).items(): type_vars[k].update(v) else: @@ -479,7 +479,7 @@ def to_variance_free(self, type_var_map=None): new_types = [] for t in self.types: new_types.append(t.to_variance_free(type_var_map) - if t.is_combound() + if t.is_compound() else t) return UnionType(new_types) @@ -489,7 +489,7 @@ def to_type_variable_free(self, factory): # type variable free. new_types = [] for t in self.types: - if t.is_combound(): + if t.is_compound(): new_type = t.to_type_variable_free(factory) elif t.is_type_var(): bound = t.get_bound_rec(factory) @@ -517,7 +517,7 @@ def unify_types(self, t1, factory, same_type=True): return {} # If T1 is a union type, then get all its types. - t1_types = (t1.types if t1.is_combound() and + t1_types = (t1.types if t1.is_compound() and not t1.is_parameterized() else [t1]) @@ -715,7 +715,7 @@ def __hash__(self): return hash(str(self.name) + str(self.types)) -class UnionTypeFactory: +class UnionTypeFactory(object): def __init__(self, max_ut, max_in_union): self.max_ut = max_ut self.unions = [] @@ -728,7 +728,7 @@ def get_types_for_union(self, gen): num_of_types = self.get_number_of_types() types = set() while len(types) < num_of_types: - t = gen.select_type(exclude_dynamic_types=True) + t = gen.select_type(exclude_native_compound_types=True) types.add(t) return list(types) From 93ce9614ce08f374482e1d99a1c0695ba3b3477f Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Wed, 23 Nov 2022 16:04:44 +0200 Subject: [PATCH 73/78] Make builtin factory set up factories for individual types --- src/generators/generator.py | 2 +- src/ir/__init__.py | 8 +++--- src/ir/typescript_types.py | 55 +++++++++++++++++++------------------ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/generators/generator.py b/src/generators/generator.py index 412135d9..b07626a6 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -45,7 +45,7 @@ def __init__(self, self.language = language self.logger: Logger = logger self.context: Context = None - self.bt_factory: BuiltinFactory = BUILTIN_FACTORIES[language] + self.bt_factory: BuiltinFactory = BUILTIN_FACTORIES[language]() self.depth = 1 self._vars_in_context = defaultdict(lambda: 0) self._new_from_class = None diff --git a/src/ir/__init__.py b/src/ir/__init__.py index a8273c8d..2e8bf1bf 100644 --- a/src/ir/__init__.py +++ b/src/ir/__init__.py @@ -4,8 +4,8 @@ from src.ir.typescript_types import TypeScriptBuiltinFactory BUILTIN_FACTORIES = { - "kotlin": KotlinBuiltinFactory(), - "groovy": GroovyBuiltinFactory(), - "java": JavaBuiltinFactory(), - "typescript": TypeScriptBuiltinFactory() + "kotlin": KotlinBuiltinFactory, + "groovy": GroovyBuiltinFactory, + "java": JavaBuiltinFactory, + "typescript": TypeScriptBuiltinFactory } diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index f29e08b4..7305d8bd 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -8,7 +8,15 @@ import src.utils as ut from src.ir.decorators import two_way_subtyping + class TypeScriptBuiltinFactory(bt.BuiltinFactory): + def __init__(self, max_union_types=10, max_types_in_union=4, + max_string_literal_types=10, max_num_literal_types=10): + self._literal_type_factory = LiteralTypeFactory( + max_string_literal_types, max_num_literal_types) + self._union_type_factory = UnionTypeFactory(max_union_types, + max_types_in_union) + def get_language(self): return "typescript" @@ -80,16 +88,17 @@ def get_big_decimal_type(self): def get_null_type(self): return NullType(primitive=False) - def get_non_nothing_types(self): # Overwriting Parent method to add TS-specific types + def get_non_nothing_types(self): + # Overwriting Parent method to add TS-specific types types = super().get_non_nothing_types() types.extend([ self.get_null_type(), UndefinedType(primitive=False), - ] + literal_types.get_literal_types()) + ] + self._literal_type_factory.get_literal_types()) return types def get_decl_candidates(self): - return [gen_type_alias_decl,] + return [gen_type_alias_decl, ] def update_add_node_to_parent(self): return { @@ -98,7 +107,7 @@ def update_add_node_to_parent(self): def get_compound_types(self, gen_object): return [ - union_types.get_union_type(gen_object), + self._union_type_factory.get_union_type(gen_object), ] def get_constant_candidates(self, constants): @@ -121,9 +130,12 @@ def get_constant_candidates(self, constants): """ return { - "NumberLiteralType": lambda etype: ast.IntegerConstant(etype.literal, NumberLiteralType), - "StringLiteralType": lambda etype: ast.StringConstant(etype.literal), - "UnionType": lambda etype: union_types.get_union_constant(etype, constants), + "NumberLiteralType": lambda etype: ast.IntegerConstant( + etype.literal, NumberLiteralType), + "StringLiteralType": lambda etype: ast.StringConstant( + etype.literal), + "UnionType": lambda etype: self._union_type_factory.get_union_constant( + etype, constants), } @@ -755,12 +767,19 @@ def get_union_type(self, gen_object): the already generated types or create a new one. """ - return self.gen_union_type(gen_object) generated = len(self.unions) if generated == 0: return self.gen_union_type(gen_object) if generated >= self.max_ut or ut.random.bool(): - return ut.random.choice(self.unions) + union_t = ut.random.choice(self.unions) + if union_t.has_type_variables(): + # We might have selected a union type that holds a type + # variable. However, we must be careful because it might be + # no possible to use the selected union type since it uses + # a type variable that is out of context. + return self.gen_union_type(gen_object) + else: + return union_t return self.gen_union_type(gen_object) def get_union_constant(self, utype, constants): @@ -830,8 +849,7 @@ def gen_type_alias_decl(gen, """ alias_type = (etype if etype else - gen.select_type() - ) + gen.select_type()) initial_depth = gen.depth gen.depth += 1 gen.depth = initial_depth @@ -846,18 +864,3 @@ def gen_type_alias_decl(gen, def add_type_alias(gen, namespace, type_name, ta_decl): gen.context._add_entity(namespace, 'types', type_name, ta_decl.get_type()) gen.context._add_entity(namespace, 'decls', type_name, ta_decl) - - -# Literal Types - -# TODO make these limits user-configurable -MAX_STRING_LITERAL_TYPES = 10 -MAX_NUM_LITERAL_TYPES = 10 -literal_types = LiteralTypeFactory(MAX_STRING_LITERAL_TYPES, MAX_NUM_LITERAL_TYPES) - -# Union Types - -# TODO make these limits user-configurable -MAX_UNION_TYPES = 10 -MAX_TYPES_IN_UNION = 4 -union_types = UnionTypeFactory(MAX_UNION_TYPES, MAX_TYPES_IN_UNION) From 790fff8fd6ee1d0749d2cb723e1038220b8dbc74 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 23 Nov 2022 16:29:34 +0200 Subject: [PATCH 74/78] Add description comments to unit tests --- tests/test_typescript.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_typescript.py b/tests/test_typescript.py index db0f99f3..a1889d84 100644 --- a/tests/test_typescript.py +++ b/tests/test_typescript.py @@ -6,6 +6,10 @@ def test_type_alias_with_literals(): + # Tests subtyping relations between a string alias and string literal + # and between a number alias and a number literal. + # - (type Foo = string) with literal "foo" + # - (type Bar = number) with literal 5 string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringType()).get_type() number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberType()).get_type() @@ -19,6 +23,10 @@ def test_type_alias_with_literals(): def test_type_alias_with_literals2(): + # Tests subtyping relation between a literal alias + # and their corresponding literal type. + # - (type Foo = "foo") with literal "foo" + # - (type Bar = "bar") with literal "bar" string_alias = ts_ast.TypeAliasDeclaration("Foo", tst.StringLiteralType("foo")).get_type() number_alias = ts_ast.TypeAliasDeclaration("Bar", tst.NumberLiteralType(5)).get_type() @@ -32,6 +40,11 @@ def test_type_alias_with_literals2(): def test_union_types_simple(): + # Tests subtyping relation between union types + # and the types in their union. + # - number | boolean + # - boolean | "bar" + # - boolean | number union_1 = tst.UnionType([tst.NumberType(), tst.BooleanType()]) bar_lit = tst.StringLiteralType("bar") @@ -46,11 +59,14 @@ def test_union_types_simple(): def test_union_types_other_types(): + # Tests that types A, B are subtypes of A | B union = tst.UnionType([tst.NumberType(), tst.BooleanType()]) assert tst.NumberType().is_subtype(union) + assert tst.BooleanType().is_subtype(union) def test_union_type_assign(): + # Tests correct creation and assignment of union type union = tst.UnionType([tst.StringType(), tst.NumberType(), tst.BooleanType(), tst.ObjectType()]) foo = tst.StringType() @@ -60,6 +76,8 @@ def test_union_type_assign(): def test_union_type_param(): + # Tests that union type bounds of type parameters do not + # conflict with the sybtyping relations between the two. union1 = tst.UnionType([tst.NumberType(), tst.NullType()]) union2 = tst.UnionType([tst.StringLiteralType("foo"), tst.NumberType()]) t_param = tp.TypeParameter("T", bound=union2) @@ -70,6 +88,7 @@ def test_union_type_param(): def test_union_type_substitution(): + # Tests substitution of type parametes in union types type_param1 = tp.TypeParameter("T1") type_param2 = tp.TypeParameter("T2") type_param3 = tp.TypeParameter("T3") @@ -86,6 +105,7 @@ def test_union_type_substitution(): def test_union_type_substitution_type_var_bound(): + # Tests substitution of bounded type parameters in union types type_param1 = tp.TypeParameter("T1") type_param2 = tp.TypeParameter("T2", bound=type_param1) type_map = {type_param1: tst.StringType()} @@ -101,6 +121,7 @@ def test_union_type_substitution_type_var_bound(): def test_union_to_type_variable_free(): + # Tests the builtin method to-type-variable-free of union types type_param1 = tp.TypeParameter("T1") type_param2 = tp.TypeParameter("T2") foo = tp.TypeConstructor("Foo", [type_param1]) @@ -134,6 +155,7 @@ def test_union_type_unification_type_var(): union = tst.UnionType([tst.StringType(), tst.StringLiteralType("foo")]) type_param = tp.TypeParameter("T") + # Case 1: Unify a union with an unbounded type param type_var_map = tu.unify_types(union, type_param, tst.TypeScriptBuiltinFactory()) assert len(type_var_map) == 1 assert type_var_map == {type_param: union} @@ -161,6 +183,9 @@ def test_union_type_unification(): union2 = tst.UnionType([type_param, tst.NumberType(), tst.StringType()]) assert union1.is_subtype(union2) + # Unify t1: 1410 | number | string + # with t2: T | number | string + # Result should be: {T: 1410} type_var_map = tu.unify_types(union1, union2, tst.TypeScriptBuiltinFactory()) assert len(type_var_map) == 1 assert type_var_map == {type_param: union1.types[0]} @@ -168,6 +193,9 @@ def test_union_type_unification(): type_param2 = tp.TypeParameter("G") union3 = tst.UnionType([type_param, type_param2, tst.StringLiteralType("foo")]) + # Unify t1: 1410 | number | string + # with t3: T | G | "foo". + # Result should be: {T: number, G: string} or reversed. type_var_map = tu.unify_types(union1, union3, tst.TypeScriptBuiltinFactory()) assert len(type_var_map) == 2 assert type_param, type_param2 in type_var_map @@ -178,15 +206,24 @@ def test_union_type_unification2(): union = tst.UnionType([tst.NumberType(), tst.StringType()]) assert tu.unify_types(tst.BooleanType(), union, tst.TypeScriptBuiltinFactory()) == {} + # Unify t1: number + # with t2: number | T + # Result should be: {T: number} t1 = tst.NumberType() t2 = tst.UnionType([tst.NumberType(), tp.TypeParameter("T")]) res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) assert len(res) == 1 and res[t2.types[1]] == t1 + # Unify t1: number | string + # with t2: number | T + # Result should be: {T: string} t1 = tst.UnionType([tst.NumberType(), tst.StringType()]) res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) assert len(res) == 1 and res[t2.types[1]] == t1.types[1] + # Unify t1: number | "foo" | string + # with t2: number | T + # Result should be: {T: string} t1 = tst.UnionType([tst.NumberType(), tst.StringLiteralType("foo"), tst.StringType()]) res = tu.unify_types(t1, t2, tst.TypeScriptBuiltinFactory()) assert len(res) == 1 and res[t2.types[1]] == t1.types[2] From adea32dc6bf2ff5774b8b811c7520761a6a7c4d0 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 23 Nov 2022 16:40:30 +0200 Subject: [PATCH 75/78] Change name of dynamic_subtyping --- src/ir/decorators.py | 2 +- src/ir/types.py | 4 ++-- src/ir/typescript_types.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ir/decorators.py b/src/ir/decorators.py index f5064cf1..75bae264 100644 --- a/src/ir/decorators.py +++ b/src/ir/decorators.py @@ -1,4 +1,4 @@ def two_way_subtyping(is_subtype): def inner(self, other): - return is_subtype(self, other) or other.dynamic_subtyping(self) + return is_subtype(self, other) or other.two_way_subtyping(self) return inner diff --git a/src/ir/types.py b/src/ir/types.py index fd3b3809..3da6b0d9 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -67,14 +67,14 @@ def has_type_variables(self): def is_subtype(self, other: Type): raise NotImplementedError("You have to implement 'is_subtype()'") - def dynamic_subtyping(self, other: Type): + def two_way_subtyping(self, other: Type): """ Overwritten when a certain type needs two-way subtyping checks. Eg. when checking if a string type is a subtype of union type 'Foo | string' we call this method - as `union-type.dynamic_subtyping(string_type)` + as `union-type.two_way_subtyping(string_type)` to check from the union's side. """ diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index 7305d8bd..a1da32bc 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -456,7 +456,7 @@ def is_subtype(self, other): return True return other.name == 'Object' - def dynamic_subtyping(self, other): + def two_way_subtyping(self, other): return other in set(self.types) def substitute_type_args(self, type_map, From 8574c788b35d1916a6f1d95d2cf323e417804839 Mon Sep 17 00:00:00 2001 From: Thodoris Sotiropoulos Date: Wed, 23 Nov 2022 16:53:26 +0200 Subject: [PATCH 76/78] substitute_type_args => substitute_type --- src/ir/types.py | 43 +++++++++++++++++++++----------------- src/ir/typescript_types.py | 6 +++--- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/ir/types.py b/src/ir/types.py index 3da6b0d9..20ec7d9a 100644 --- a/src/ir/types.py +++ b/src/ir/types.py @@ -124,18 +124,9 @@ def get_supertypes(self): stack.append(supertype) return visited - def substitute_type_args(self, type_map, - cond=lambda t: t.has_type_variables()): - t = type_map.get(self) - if t is None or cond(t): - # Perform type substitution on the bound of the current type variable. - if self.is_type_var() and self.bound is not None: - new_bound = self.bound.substitute_type_args(type_map, cond) - return TypeParameter(self.name, self.variance, new_bound) - # The type parameter does not correspond to an abstract type - # so, there is nothing to substitute. - return self - return t + def substitute_type(self, type_map, + cond=lambda t: t.has_type_variables()): + return self def not_related(self, other: Type): return not(self.is_subtype(other) or other.is_subtype(self)) @@ -311,6 +302,20 @@ def is_subtype(self, other): return False return self.bound.is_subtype(other) + def substitute_type(self, type_map, + cond=lambda t: t.has_type_variables()): + t = type_map.get(self) + if t is None or cond(t): + # Perform type substitution on the bound of the current type + # variable. + if self.bound is not None: + new_bound = self.bound.substitute_type(type_map, cond) + return TypeParameter(self.name, self.variance, new_bound) + # The type parameter does not correspond to an abstract type + # so, there is nothing to substitute. + return self + return t + def __eq__(self, other): return (self.__class__ == other.__class__ and self.name == other.name and @@ -359,10 +364,10 @@ def get_type_variables(self, factory): else: return {} - def substitute_type_args(self, type_map, - cond=lambda t: t.has_type_variables()): + def substitute_type(self, type_map, + cond=lambda t: t.has_type_variables()): if self.bound is not None: - new_bound = self.bound.substitute_type_args(type_map, cond) + new_bound = self.bound.substitute_type(type_map, cond) return WildCardType(new_bound, variance=self.variance) t = type_map.get(self) if t is None or cond(t): @@ -414,7 +419,7 @@ def is_primitive(self): def substitute_type(t, type_map): - return t.substitute_type_args(type_map, lambda t: False) + return t.substitute_type(type_map, lambda t: False) def perform_type_substitution(etype, type_map, @@ -437,7 +442,7 @@ class X : Y>() supertypes = [] for t in etype.supertypes: if t.is_parameterized(): - supertypes.append(t.substitute_type_args(type_map)) + supertypes.append(t.substitute_type(type_map)) else: supertypes.append(t) type_params = [] @@ -678,10 +683,10 @@ def get_type_variables(self, factory) -> Dict[TypeParameter, Set[Type]]: continue return type_vars - def substitute_type_args(self, type_map, cond=lambda t: t.has_type_variables()): + def substitute_type(self, type_map, cond=lambda t: t.has_type_variables()): type_args = [] for t_arg in self.type_args: - type_args.append(t_arg.substitute_type_args(type_map, cond)) + type_args.append(t_arg.substitute_type(type_map, cond)) new_type_map = { tp: type_args[i] for i, tp in enumerate(self.t_constructor.type_parameters) diff --git a/src/ir/typescript_types.py b/src/ir/typescript_types.py index a1da32bc..941efbc0 100644 --- a/src/ir/typescript_types.py +++ b/src/ir/typescript_types.py @@ -459,11 +459,11 @@ def is_subtype(self, other): def two_way_subtyping(self, other): return other in set(self.types) - def substitute_type_args(self, type_map, - cond=lambda t: t.has_type_variables()): + def substitute_type(self, type_map, + cond=lambda t: t.has_type_variables()): new_types = [] for t in self.types: - new_t = (t.substitute_type_args(type_map, cond) + new_t = (t.substitute_type(type_map, cond) if t.has_type_variables() else t) new_types.append(new_t) From 66541442b2b6fd981e371a904795a3315f2c4ac9 Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 7 Dec 2022 15:40:52 +0200 Subject: [PATCH 77/78] Install typescript nightly build from seperate function --- deployment/run.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deployment/run.sh b/deployment/run.sh index 57efa732..c90fb93c 100755 --- a/deployment/run.sh +++ b/deployment/run.sh @@ -47,8 +47,11 @@ run_groovy_from_source() { --language groovy --cast-numbers } -run_typescript() { +install_ts_nightly() { npm install -g typescript@next +} + +run_typescript() { python3.9 hephaestus.py -s $TIME_TO_RUN -t 0 -w $CORES --batch 30 -P \ --language typescript --disable-use-site-variance \ --error-filter-patterns patterns.txt @@ -83,6 +86,7 @@ while getopts "hkstagS" OPTION; do ;; t) + install_ts_nightly run_typescript ;; From 101d78772b37ca72f27e4a5fc1580188ce911dbf Mon Sep 17 00:00:00 2001 From: Alex Papadopoulos Date: Wed, 7 Dec 2022 15:26:33 +0200 Subject: [PATCH 78/78] remove GSOC.md from central repo pull request --- GSOC.md | 125 -------------------------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 GSOC.md diff --git a/GSOC.md b/GSOC.md deleted file mode 100644 index d2339c7c..00000000 --- a/GSOC.md +++ /dev/null @@ -1,125 +0,0 @@ - -

- -# Hephaestus: Testing the TypeScript Compiler - -## A project outline by [Alex Papadopoulos](https://www.linkedin.com/in/alexios-papadopoulos-siountris-855935240/) - -Organisation: [GFOSS - Open Technology Alliance](https://github.com/eellak/) - -Central Repository: [Hephaestus](https://github.com/hephaestus-compiler-project/hephaestus) - -Mentor: [Thodoris Sotiropoulos](https://github.com/theosotr) - -## 📚 Contents - -- Project Description -- Communicating with the Hephaestus Team -- Development Outline - - Git/GitHub: Branch and Repo Management - - Important Features - - Automated Testing -- Pull Request Overview -- Results -- What's next? - -## 💭 Project Description - -Hephaestus is a compiler testing tool, designed to test the compilers of statically-typed languages. Namely, upon the start of the GSoC '22 cycle, Hephaestus was actively testing and had found bugs in the languages of Java, Kotlin, and Groovy. - -My task was to expand Hephaestus to test and find bugs in the compiler of TypeScript, a language by Microsoft that has risen in popularity in the previous decade. - -Below we will go over important moments that shaped my GSoC project, with the aim of describing the development process and providing useful tips for future GSoC contributors. - -## 📧 Communicating with the Hephaestus Team - -Communication with your mentor during GSoC is crucial. Mentors can make or break a GSoC project and, in my case, I was paired with a great one. Thodoris motivated me and guided me through blockers, head scratchers, and problems that all contributors will eventually face in one way or another. - -Hephaestus is housed at a research lab, so I was lucky to have the ability to meet up and work with Thodoris on-site at the lab. At the same time, we also held virtual meetings whenever needed. - -Both virtually and on-site we had pair programming sessions when needed. Whenever I stumbled upon a problem that I could not solve by myself, after going through all other avenues (properly searching the internet, writing unit tests, using the python debugger etc.) we sat down together and tried to fix the problem. Those sessions were extremely helpful and I learnt a lot both from being the one coding, but also from being the one discussing the problem while Thodoris typed our solution out. - -Pair programming is a very important skill for any developer team out there. A make-or-break tip for these sessions (especially for Junior Developers) is to go into them prepared. First exhaust all other possible means (in a reasonable time-frame) and be able to tell the other person what you have already tried. This way, no time is wasted and you can more effectively crack the problem. - -Thodoris and I used a [Slack](https://slack.com/) group to communicate throughout the program both for coordinating our efforts but also for updating other Hephaestus team members with my progress. - -## 👨‍💻 Development Outline - -### Git/GitHub: Branch and Repo Management - -By participating in the Google Summer of Code program, I learnt how to seamlessly integrate Git and GitHub in my workflow when working with large projects. From rebasing branches to using handy commands like [git-bisect](https://git-scm.com/docs/git-bisect), I can now easily navigate and contribute to larger codebases, while utilizing tools that are vital to all developers regardless of their field. - -This was the first time I used Git outside of personal or small-team projects, which equipped me with necessary skills for any future project. - -### Important Features - -Lots was added, discarded, and rewritten during GSoC '22 for the Hephaestus project. Which features were the most challenging and important? - -- **Translating an Abstract Syntax Tree (AST) to TypeScript** - -The first feature I completed was perhaps the one with the biggest learning curve. While future additions were in fact more complex, the first one was the most all-around challenging. - -It is not hard to think of why. I had to deeply understand the functionality of an AST as well as how the mechanisms of Hephaestus cooperated to produce the tree itself. Aside from the learning material directly related to this feature, I also had to get a good understanding of properly contributing in Open Source as a whole before I could even start coding this out. - -- **Making Hephaestus extensible** - -It turns out that trying to add a language with exclusive features to a language agnostic compiler tester comes with its challenges. - -The applications and handling of certain cases in the core generator were not designed with TypeScript features in mind (eg. Union Types). - -As a result, I had to find a proper and effective way for one to extend the core generator of Hephaestus to features that are language-specific. After designing this with my mentor, we proceeded with its application. - -Now, everyone can extend Hephaestus to any language they want, and there already is a way to add language-specific features without changing the core and language-agnostic nature of Hephaestus. - -### Automated Testing - -Automated Testing is important for all healthy codebases. After a certain point, the code changes are too many to keep manually testing features. One must know at any point if an addition broke some part of the code, in order to quickly and effectively act upon that information to fix potential bugs. - -Throughout GSoC, I wrote a number of unit tests to ensure stability, increase code coverage, and fix bugs. - -## 🌿 Pull Requests - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
StatusNameDiff
TypeScript Translator+1020 −530
Null and Undefined types. Null Constant+88 -7
Literal Types+155 −11
Type Aliases+307 -111
🚧Union Types+471 −77
- -## Results & What's Next - -After the GSoC program, I am now a member of the research lab that houses the Hephaestus project: the [AUEB BALAB](https://www.balab.aueb.gr/)! - -I will stay on the Hephaestus team and contribute to the project. I am very excited for the future to come. - -Thanks to my mentor Thodoris, the BALAB team, and all the GSoC community members that made this summer one to remember!