Ejemplo n.º 1
0
def test_FuncSpec_typing_List():
    from typing import List

    from bifrostrpc.typing import Advanced, FuncSpec

    def accept_list(things: List[int]) -> None: return None
    def return_list() -> List[int]: return []
    def list_of_lists(things: List[List[List[int]]]) -> List[List[List[str]]]: return []

    # "Any" return type is not allowed
    FuncSpec(accept_list, Advanced())
    FuncSpec(return_list, Advanced())
    FuncSpec(list_of_lists, Advanced())
Ejemplo n.º 2
0
def test_FuncSpec_typing_Any():
    from typing import Any

    from bifrostrpc import TypeNotSupportedError
    from bifrostrpc.typing import Advanced, FuncSpec

    def return_any() -> Any: return None
    def accept_any(a: Any) -> None: return None

    # "Any" return type is not allowed
    with raises(TypeNotSupportedError):
        FuncSpec(return_any, Advanced())

    # "Any" argument type is not allowed
    with raises(TypeNotSupportedError):
        FuncSpec(accept_any, Advanced())
Ejemplo n.º 3
0
def generateClient(
    dest: FilePython,
    *,
    classname: str,
    funcspecs: List[Tuple[str, FuncSpec]],
    adv: Advanced,
    flavour: Flavour,
) -> None:
    dest.filecomment(HEADER)

    # we're always going to need typing module
    dest.contents.alsoImportPy("typing")

    appendFailureModeClasses(dest)

    # make copies of all our dataclasses
    for dc in adv.getAllDataclasses():
        dcspec = _getDataclassSpec(dc, adv)
        dest.contents.also(dcspec)

    # generate function wrappers
    dest.contents.also(_generateWrappers(classname, funcspecs, adv, flavour))

    dest.writefile()
    dest.makepretty()
Ejemplo n.º 4
0
def test_FuncSpec_scalar():
    from bifrostrpc.typing import Advanced, FuncSpec, ScalarTypeSpec

    def scalar_fn(i: int, s: str, b: bool) -> None:
        return None

    # "Any" return type is not allowed
    spec = FuncSpec(scalar_fn, Advanced())
    assert isinstance(spec.argSpecs['i'], ScalarTypeSpec)
    assert isinstance(spec.argSpecs['s'], ScalarTypeSpec)
    assert isinstance(spec.argSpecs['b'], ScalarTypeSpec)
Ejemplo n.º 5
0
def test_get_int_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec

    adv = Advanced()

    ts = getTypeSpec(int, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is int
    assert ts.typeName == 'int'

    MyInt = NewType('MyInt', int)
    adv.addNewType(MyInt)

    ts = getTypeSpec(MyInt, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is MyInt
    assert ts.typeName == 'MyInt'

    MyInt2 = NewType('MyInt2', MyInt)
    adv.addNewType(MyInt2)

    ts = getTypeSpec(MyInt2, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is int
    assert ts.originalType is MyInt2
    assert ts.typeName == 'MyInt2'
Ejemplo n.º 6
0
def test_get_Literal_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import LiteralTypeSpec

    # need type: ignore here because mypy can't work out what Literal is due to
    # import fallback mechanism above
    Five = NewType('Five', Literal[5])  # type: ignore
    Hello = NewType('Hello', Literal["hello"])  # type: ignore
    adv = Advanced()
    adv.addNewType(Five)
    adv.addNewType(Hello)

    ts = getTypeSpec(Literal[5], adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == 5
    assert ts.expectedType is int

    ts = getTypeSpec(Five, adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == 5
    assert ts.expectedType is int

    ts = getTypeSpec(Hello, adv)
    assert isinstance(ts, LiteralTypeSpec)
    assert ts.expected == "hello"
    assert ts.expectedType is str
Ejemplo n.º 7
0
def test_get_str_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec

    adv = Advanced()

    ts = getTypeSpec(str, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is str
    assert ts.typeName == 'str'

    MyStr = NewType('MyStr', str)
    adv.addNewType(MyStr)

    ts = getTypeSpec(MyStr, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is MyStr
    assert ts.typeName == 'MyStr'

    MyStr2 = NewType('MyStr2', MyStr)
    adv.addNewType(MyStr2)

    ts = getTypeSpec(MyStr2, adv)
    assert isinstance(ts, ScalarTypeSpec)
    assert ts.scalarType is str
    assert ts.originalType is MyStr2
    assert ts.typeName == 'MyStr2'
Ejemplo n.º 8
0
def _generateAdvancedTypes(dest: FileTS, adv: Advanced) -> None:
    for name, baseType, children in adv.getNewTypeDetails():
        try:
            tsprimitive = PRIMITIVES[baseType.__name__]
        except KeyError:
            raise Exception(
                f'Cannot generate a typescript alias matching {name}'
                f'; no known primitive type for {baseType.__name__}')

        typeExpr = tsprimitive + ' & {readonly brand?: unique symbol}'
        if children:
            typeExpr = ' | '.join(children) + ' | (' + typeExpr + ')'

        dest.contents.blank()
        dest.contents.also(tsexpr(f'export type {name} = {typeExpr}'))

    for dc in adv.getAllDataclasses():
        dest.contents.blank()
        iface = InterfaceSpec(dc.__name__,
                              tsexport=True,
                              appendto=dest.contents)
        for field in dataclasses.fields(dc):
            generated = _generateCrossType(getTypeSpec(field.type, adv), adv)
            iface.addProperty(field.name, generated)
Ejemplo n.º 9
0
def _generateType(spec: TypeSpec, adv: Advanced, addimport: Callable[[str], None]) -> str:
    if isinstance(spec, NullTypeSpec):
        return 'None'

    if isinstance(spec, ScalarTypeSpec):
        return spec.typeName

    if isinstance(spec, ListTypeSpec):
        itemtype = _generateType(spec.itemSpec, adv, addimport)
        return f'typing.List[{itemtype}]'

    if isinstance(spec, DictTypeSpec):
        keytype = _generateType(spec.keySpec, adv, addimport)
        valuetype = _generateType(spec.valueSpec, adv, addimport)
        return f'typing.Dict[{keytype}, {valuetype}]'

    if isinstance(spec, DataclassTypeSpec):
        if not adv.hasDataclass(spec.class_):
            raise Exception(
                f'Cannot generate a python type for unknown dataclass {spec.class_.__name__}')

        return spec.class_.__name__

    if isinstance(spec, UnionTypeSpec):
        joined = ", ".join([
            _generateType(variantspec, adv, addimport)
            for variantspec in spec.variants
        ])
        return f'typing.Union[{joined}]'

    if isinstance(spec, LiteralTypeSpec):
        if spec.expectedType is bool:
            raise Exception("TODO: test this code path")  # noqa
            addimport('typing_extensions')  # pylint: disable=unreachable
            return f'typing_extensions.Literal[{spec.expected!r}]'

        if spec.expectedType is int:
            raise Exception("TODO: test this code path")  # noqa
            addimport('typing_extensions')  # pylint: disable=unreachable
            return f'typing_extensions.Literal[{spec.expected}]'

        if spec.expectedType is not str:
            raise Exception(f"Unexpected literal type {spec.expectedType.__name__}")

        addimport('typing_extensions')
        return f'typing_extensions.Literal[{spec.expected!r}]'

    raise Exception(f"TODO: generate a type for {spec!r}")
Ejemplo n.º 10
0
def test_Union_type_spec_does_not_collapse_int_bool() -> None:
    """Some versions of python express this bug.

    'Union[int, bool]' becomes just 'int'

    See https://stackoverflow.com/questions/60154326/unable-to-create-unionbool-int-type
    """
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Union[int, bool]
    _assert_union_variants(
        getTypeSpec(Union[int, bool], Advanced()),
        ScalarTester(int, int, 'int'),
        ScalarTester(bool, bool, 'bool'),
    )
Ejemplo n.º 11
0
def test_get_Dict_type_spec() -> None:
    from bifrostrpc import TypeNotSupportedError
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import DictTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Dict[str, int]
    ts = getTypeSpec(Dict[str, int], Advanced())
    assert isinstance(ts, DictTypeSpec)

    assert isinstance(ts.keySpec, ScalarTypeSpec)
    assert ts.keySpec.scalarType is str
    assert ts.keySpec.originalType is str
    assert ts.keySpec.typeName == 'str'

    assert isinstance(ts.valueSpec, ScalarTypeSpec)
    assert ts.valueSpec.scalarType is int
    assert ts.valueSpec.originalType is int
    assert ts.valueSpec.typeName == 'int'

    # test handling of a more complex Dict[str, Dict[str, CustomType]]
    MyStr = NewType('MyStr', str)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyStr)
    adv.addNewType(MyStr2)
    ts = getTypeSpec(Dict[str, Dict[str, MyStr2]], adv)
    assert isinstance(ts, DictTypeSpec)
    assert isinstance(ts.keySpec, ScalarTypeSpec)
    assert isinstance(ts.valueSpec, DictTypeSpec)
    assert isinstance(ts.valueSpec.keySpec, ScalarTypeSpec)
    assert isinstance(ts.valueSpec.valueSpec, ScalarTypeSpec)
    assert ts.keySpec.scalarType is str
    assert ts.keySpec.originalType is str
    assert ts.keySpec.typeName == 'str'
    assert ts.valueSpec.keySpec.scalarType is str
    assert ts.valueSpec.keySpec.originalType is str
    assert ts.valueSpec.keySpec.typeName == 'str'
    assert ts.valueSpec.valueSpec.scalarType is str
    assert ts.valueSpec.valueSpec.originalType is MyStr2
    assert ts.valueSpec.valueSpec.typeName == 'MyStr2'

    with raises(TypeNotSupportedError):
        getTypeSpec(Dict[MyStr2, str], adv)
Ejemplo n.º 12
0
def _getTypeNoMatchExpr(var_or_prop: str, spec: TypeSpec) -> Optional[str]:
    if isinstance(spec, NullTypeSpec):
        return f"{var_or_prop} !== null"

    if isinstance(spec, ScalarTypeSpec):
        tsscalar = PRIMITIVES[spec.scalarType.__name__]
        return f'typeof {var_or_prop} !== "{tsscalar}"'

    if isinstance(spec, LiteralTypeSpec):
        # a literal type's type definition is identical to how you would write it as an expression
        valueexpr = _generateType(spec, Advanced())
        return f'{var_or_prop} !== {valueexpr}'

    if isinstance(spec, DataclassTypeSpec):
        # not possible
        return None

    if isinstance(spec, ListTypeSpec):
        # not possible
        return None

    raise Exception(f'TODO: no code to get a type-match expr for {spec!r}')
Ejemplo n.º 13
0
 def __init__(self, targets: List[Callable[..., Any]]):
     self._targets = {fn.__name__: fn for fn in targets}
     self._adv: Advanced = Advanced()
     self._spec: Dict[str, FuncSpec] = {}
     self._factory: Dict[Type[Any], Callable[[], Any]] = {}
Ejemplo n.º 14
0
def _getConverterBlock(
    var_or_prop: str,
    outname: str,
    label: str,
    spec: TypeSpec,
    names: Names,
    adv: Advanced,
) -> Statement:
    """
    Return a paradox Statement that will convert var_or_prop to the right type.

    The converted value is assigned to `outname`. A TypeError is raised by the generated code block
    if `var_or_prop` can't be converted.
    """
    assert outname != var_or_prop

    if not names.isAssignable(outname):
        raise Exception(
            f"Can't generate a converter to build {spec}"
            f" when {outname} is not assignable"
        )

    if isinstance(spec, ListTypeSpec):
        ret = Statements()

        # make sure the thing came back as a list
        with ret.withCond(pyexpr(f'not isinstance({var_or_prop}, list)')) as cond:
            cond.alsoRaise("TypeError", msg=f"{label} should be of type list")

        ret.alsoAssign(PanVar(outname, None), pyexpr('[]'))

        # add converts for the items - we know filter blocks aren't possible because if they were,
        # we wouldn't be using _getConverterBlock on a ListTypeSpec
        itemspec = spec.itemSpec
        itemvar = names.getNewName(var_or_prop, 'item', False)
        with ret.withFor(PanVar(itemvar, None), pyexpr(var_or_prop)) as loop:
            # TODO: also if we want to provide meaningful error messages, we really want to know
            # the idx of the item that was broken and include it in the error message
            itemvar2 = names.getNewName(var_or_prop, 'item_converted', True)
            loop.also(_getConverterBlock(
                itemvar,
                itemvar2,
                f"{label}[$n]",
                itemspec,
                names,
                adv,
            ))
            loop.also(pyexpr(f"{outname}.append({itemvar2})"))

        return ret

    if isinstance(spec, DataclassTypeSpec):
        if not adv.hasDataclass(spec.class_):
            raise Exception(
                f'Cannot generate a converter for unknown dataclass {spec.class_.__name__}')

        ret = Statements()
        ret.remark(f'try and build a {spec.class_.__name__} from {label}')
        ret.alsoAssign(PanVar(outname, None), PanCall(
            f'{spec.class_.__name__}.fromDict',
            PanVar(var_or_prop, None),
            pan(label),
        ))
        return ret

    if isinstance(spec, UnionTypeSpec):
        ret = Statements()

        # make a list of simple expressions that can be used to verify simple types quickly, and a
        # list of TypeSpecs for which simple expressions aren't possible
        notsimple: List[TypeSpec] = []
        simpleexprs: List[str] = []

        for vspec in spec.variants:
            nomatchexpr = _getTypeNoMatchExpr(var_or_prop, vspec)
            if nomatchexpr is None:
                notsimple.append(vspec)
            else:
                simpleexprs.append(nomatchexpr)

        # if they were all simple, we can use a single negative-if to rule out some invalid types
        if simpleexprs:
            innerstmt: Statements = ConditionalBlock(pyexpr(' and '.join(simpleexprs)))
            ret.also(innerstmt)
        else:
            innerstmt = ret

        if notsimple:
            # make sure we can assign a new value to var_or_prop
            if var_or_prop != 'result' and not names.isAssignable(var_or_prop):
                raise Exception(f"Overwriting {var_or_prop} won't work")

            # use a nested function for flow-control ... mostly so we can use 'return' statements
            # to break out of the function early if we find a matching type
            checkervar = names.getNewName('', 'value', True)
            checkername = names.getNewName('', 'checker', False)
            innercheck = FunctionSpec(checkername, CrossAny())
            innercheck.addPositionalArg(checkervar, CrossAny())
            innerstmt.also(innercheck)
            innerstmt.alsoAssign(PanVar(var_or_prop, None),
                                 PanCall(checkername, PanVar(var_or_prop, None)))
            innerstmt = innercheck

        for vspec in notsimple:
            innerstmt.remark('add a try/except for each vspec')
            with innerstmt.withTryBlock() as tryblock:
                filterblock = convertblock = None
                try:
                    filterblock = _getFilterBlock(checkervar, label, vspec, names)
                except _FilterNotPossible:
                    convertedvar = names.getNewName(checkervar, 'converted', True)
                    convertblock = _getConverterBlock(
                        checkervar,
                        convertedvar,
                        label,
                        vspec,
                        names,
                        adv,
                    )

                if filterblock:
                    tryblock.also(filterblock)
                    tryblock.alsoReturn(PanVar(checkervar, None))
                else:
                    assert convertblock is not None
                    tryblock.also(convertblock)
                    tryblock.alsoReturn(PanVar(convertedvar, None))
                with tryblock.withCatchBlock('TypeError') as catchblock:
                    catchblock.remark('ignore TypeError -contine on to next variant')
                    catchblock.also(pyexpr('pass'))
        innerstmt.alsoRaise("TypeError", msg=f"{var_or_prop} did not match any variant")
        return ret

    raise Exception(
        f"No code to generate a converter block for {var_or_prop} using {spec!r}"
    )
Ejemplo n.º 15
0
def _generateType(spec: TypeSpec, adv: Advanced) -> str:
    if isinstance(spec, NullTypeSpec):
        return 'null'

    if isinstance(spec, LiteralTypeSpec):
        if spec.expectedType is bool:
            raise Exception("TODO: test this code path")  # noqa
            return 'true' if spec.expected else 'false'  # pylint: disable=unreachable

        if spec.expectedType is int:
            raise Exception("TODO: test this code path")  # noqa
            return str(spec.expected)  # pylint: disable=unreachable

        if spec.expectedType is not str:
            raise Exception(
                f"Unexpected literal type {spec.expectedType.__name__}")

        if not re.match(r'^[a-zA-Z0-9_.,\-]+$', cast(str, spec.expected)):
            raise Exception(
                f"Literal {spec.expected!r} is too complex to rebuild in typescript"
            )

        return '"' + cast(str, spec.expected) + '"'

    if isinstance(spec, ScalarTypeSpec):
        typeName = spec.typeName
        if adv.hasNewType(spec.originalType) or adv.hasExternalType(
                spec.originalType):
            return typeName

        try:
            return PRIMITIVES[typeName]
        except KeyError:
            raise Exception(
                f'Cannot generate a typescript alias matching {typeName}'
                f'; no known primitive type for {typeName}')

    if isinstance(spec, DataclassTypeSpec):
        if not adv.hasDataclass(spec.class_):
            raise Exception(
                f'Cannot generate a typescript type for unknown dataclass {spec.class_.__name__}'
            )

        return spec.class_.__name__

    if isinstance(spec, ListTypeSpec):
        itemstr = _generateType(spec.itemSpec, adv)
        if re.match(r'^\w+$', itemstr):
            return itemstr + '[]'
        return 'Array<' + itemstr + '>'

    if isinstance(spec, UnionTypeSpec):
        return ' | '.join(
            [_generateType(variantspec, adv) for variantspec in spec.variants])

    if isinstance(spec, DictTypeSpec):
        assert isinstance(spec.keySpec,
                          ScalarTypeSpec) and spec.keySpec.scalarType is str
        keytype = _generateType(spec.keySpec, adv)
        valuetype = _generateType(spec.valueSpec, adv)
        return f"{{[k: {keytype}]: {valuetype}}}"

    raise Exception(f"TODO: generate a type for {spec!r}")
Ejemplo n.º 16
0
def test_get_List_type_spec() -> None:
    from bifrostrpc import TypeNotSupportedError
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import ListTypeSpec
    from bifrostrpc.typing import ScalarTypeSpec
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple List[int]
    ts = getTypeSpec(List[int], Advanced())
    assert isinstance(ts, ListTypeSpec)

    assert isinstance(ts.itemSpec, ScalarTypeSpec)
    assert ts.itemSpec.scalarType is int
    assert ts.itemSpec.originalType is int
    assert ts.itemSpec.typeName == 'int'

    # test handling of a more complex List[List[CustomType]]
    MyStr = NewType('MyStr', str)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyStr)
    adv.addNewType(MyStr2)
    ts = getTypeSpec(List[List[MyStr2]], adv)
    assert isinstance(ts, ListTypeSpec)
    assert isinstance(ts.itemSpec, ListTypeSpec)
    assert isinstance(ts.itemSpec.itemSpec, ScalarTypeSpec)
    assert ts.itemSpec.itemSpec.scalarType is str
    assert ts.itemSpec.itemSpec.originalType is MyStr2
    assert ts.itemSpec.itemSpec.typeName == 'MyStr2'

    UserID = NewType('UserID', int)
    Users = NewType('Users', List[UserID])
    adv = Advanced()
    adv.addNewType(UserID)
    adv.addNewType(Users)

    # TODO: we don't yet support a NewType wrapping a List like this
    with raises(TypeNotSupportedError):
        ts = getTypeSpec(Users, adv)
        assert isinstance(ts, ListTypeSpec)
        assert isinstance(ts.itemSpec, ListTypeSpec)
        assert isinstance(ts.itemSpec.itemSpec, ScalarTypeSpec)
        assert ts.itemSpec.itemSpec.scalarType is UserID
        assert ts.itemSpec.itemSpec.originalType is int
        assert ts.itemSpec.itemSpec.typeName == 'UserID'
Ejemplo n.º 17
0
def _generateConverter(
    ts: RawTypescript,
    var_or_prop: str,
    spec: TypeSpec,
    names: Names,
    adv: Advanced,
    indent: str,
) -> None:
    if isinstance(spec, NullTypeSpec):
        # make sure the thing is null
        ts.rawline(f'{indent}if ({var_or_prop} !== null) {{')
        ts.rawline(
            f'{indent}  throw new TypeError("{var_or_prop} should be null");')
        ts.rawline(f'{indent}}}')
        return

    if isinstance(spec, ScalarTypeSpec):
        tsscalar = PRIMITIVES[spec.scalarType.__name__]

        # make sure the thing has the correct type
        ts.rawline(f'{indent}if (typeof {var_or_prop} !== "{tsscalar}") {{')
        ts.rawline(
            f'{indent}  throw new TypeError("{var_or_prop} should be of type {tsscalar}");'
        )
        ts.rawline(f'{indent}}}')
        return

    if isinstance(spec, ListTypeSpec):
        # make sure the thing is an array
        ts.rawline(f'{indent}if (!Array.isArray({var_or_prop})) {{')
        ts.rawline(
            f'{indent}  throw new TypeError("{var_or_prop} should be an Array");'
        )
        ts.rawline(f'{indent}}}')

        # make sure all items have the correct type
        itemspec = spec.itemSpec
        itemvar = names.getNewName(var_or_prop, 'item', False)
        # TODO: if we actually need to *convert* a type, then we probably need to use
        #   for (let idx in var) { itemvar = var[idx]; ... }
        # or possibly
        #   var = var.map(function(var_item) { return convert(var_item); })...
        ts.rawline(f'{indent}for (let {itemvar} of {var_or_prop}) {{')
        # TODO: also if we want to provide meaningful error messages, we really want to know the
        # idx of the item that was broken and include it in the error message
        _generateConverter(ts, itemvar, itemspec, names, adv, indent + '  ')
        ts.rawline(f'{indent}}}')
        return

    if isinstance(spec, DataclassTypeSpec):
        if not adv.hasDataclass(spec.class_):
            raise Exception(
                f'Cannot generate a typescript type for unknown dataclass {spec.class_.__name__}'
            )

        ts.rawline(
            f"{indent}// make sure {var_or_prop} is an object that has properties"
        )
        ts.rawline(f"{indent}if (!{var_or_prop}) {{")
        msg = f'{var_or_prop} must be an object that satisfies {spec.class_.__name__} interface'
        ts.rawline(f"{indent}  throw new TypeError('{msg}');")
        ts.rawline(f"{indent}}}")
        ts.rawline(f'{indent}// verify each member of {spec.class_.__name__}')
        for name, fieldspec in spec.fieldSpecs.items():
            propexpr = var_or_prop + '.' + name
            _generateConverter(ts, propexpr, fieldspec, names, adv, indent)
        return

    if isinstance(spec, UnionTypeSpec):
        # make a list of simple expressions that can be used to verify simple types quickly, and a
        # list of TypeSpecs for which simple expressions aren't possible
        notsimple: List[TypeSpec] = []
        simpleexprs: List[str] = []

        for vspec in spec.variants:
            nomatchexpr = _getTypeNoMatchExpr(var_or_prop, vspec)
            if nomatchexpr is None:
                notsimple.append(vspec)
            else:
                simpleexprs.append(nomatchexpr)

        joinedexpr = ' && '.join(simpleexprs)

        # if they were all simple, we can use a single negative-if to match invalid types
        # TODO: this won't work if we have no simpleexprs
        ts.rawline(f'{indent}if ({joinedexpr}) {{')
        # use a nested function for flow-control ... mostly so we can use 'return' statements
        # to break out of the function early if we find a matching type
        ts.rawline(f'{indent}  (function() {{')

        # add a try/except for each vspec
        # TODO: this fix will need some more serious testing
        for vspec in notsimple:
            ename = names.getNewName(var_or_prop, 'error', False)
            ts.rawline(f'{indent}    try {{')
            _generateConverter(ts, var_or_prop, vspec, names, adv,
                               indent + '      ')
            ts.rawline(
                f'{indent}      return; // {var_or_prop} matches this variant')
            ts.rawline(f'{indent}    }} catch ({ename}) {{')
            ts.rawline(f'{indent}      if ({ename} instanceof TypeError) {{')
            ts.rawline(
                f'{indent}        // ignore type-error - continue on to next variant'
            )
            ts.rawline(f'{indent}      }} else {{')
            ts.rawline(
                f'{indent}        throw {ename}; // re-throw any real errors')
            ts.rawline(f'{indent}      }}')
            ts.rawline(f'{indent}    }}')  # end try/catch
        ts.rawline(
            f'{indent}    throw new TypeError("{var_or_prop} did not match any variant");'
        )
        ts.rawline(f'{indent}  }})();')
        ts.rawline(f'{indent}}}')
        return

    if isinstance(spec, DictTypeSpec):
        assert isinstance(spec.keySpec,
                          ScalarTypeSpec) and spec.keySpec.scalarType is str

        # NOTE: we don't bother typechecking the keys since we know they will be strings coming
        # from JSON
        ts.rawline(
            f"{indent}// make sure {var_or_prop} is an object that matches the dict spec"
        )
        ts.rawline(f"{indent}if (!{var_or_prop}) {{")
        msg = f'{var_or_prop} must be an object'
        ts.rawline(f"{indent}  throw new TypeError('{msg}');")
        ts.rawline(f"{indent}}}")
        ts.rawline(f'{indent}// verify each item of the dict')
        keyvar = names.getNewName(var_or_prop, 'key', False)
        ts.rawline(f'{indent}for (let {keyvar} in {var_or_prop}) {{')
        itemvar = var_or_prop + '[' + keyvar + ']'
        _generateConverter(ts, itemvar, spec.valueSpec, names, adv,
                           indent + '  ')
        ts.rawline(f'{indent}}}')
        return

    raise Exception(
        f"TODO: generate a converter for {var_or_prop} using {spec!r}")
Ejemplo n.º 18
0
def _importExternalTypes(dest: FileTS, adv: Advanced) -> None:
    for name, (tsmodule, ) in adv.getExternalTypeDetails():
        # XXX: dirty hack get rid of this
        if name in ('TagID', 'CategoryName', 'CategoryDesc', 'CategoryID'):
            continue
        dest.contents.alsoImportTS(tsmodule, [name])
Ejemplo n.º 19
0
def test_get_Union_type_spec() -> None:
    from bifrostrpc.typing import Advanced
    from bifrostrpc.typing import getTypeSpec

    # test handling of a simple Union[str, int, None]
    _assert_union_variants(
        getTypeSpec(Union[str, int, None], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    # test handling of Optional[]
    _assert_union_variants(
        getTypeSpec(Optional[str], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
    )

    # test handling of nested Unions - note that nested unions are collapsed
    _assert_union_variants(
        getTypeSpec(Union[str, Union[int, None]], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    _assert_union_variants(
        getTypeSpec(Union[str, Optional[int]], Advanced()),
        is_null_spec,
        ScalarTester(str, str, 'str'),
        ScalarTester(int, int, 'int'),
    )

    # test Union containing more complex types: Union[List[], Dict[]]
    _assert_union_variants(
        getTypeSpec(Union[List[int], Dict[str, int]], Advanced()),
        ListTester(ScalarTester(int, int, 'int')),
        DictTester(ScalarTester(int, int, 'int')),
    )

    # test Union containing some NewTypes
    MyInt = NewType('MyInt', int)
    MyStr = NewType('MyStr', str)
    MyInt2 = NewType('MyInt2', MyInt)
    MyStr2 = NewType('MyStr2', MyStr)
    adv = Advanced()
    adv.addNewType(MyInt)
    adv.addNewType(MyStr)
    adv.addNewType(MyInt2)
    adv.addNewType(MyStr2)
    _assert_union_variants(
        getTypeSpec(Union[List[MyInt2], Dict[str, MyStr2]], adv),
        ListTester(ScalarTester(MyInt2, int, 'MyInt2')),
        DictTester(ScalarTester(MyStr2, str, 'MyStr2')),
    )