diff --git a/cpp/csp/cppnodes/baselibimpl.cpp b/cpp/csp/cppnodes/baselibimpl.cpp index 69bf5b1e8..cb26eccfa 100644 --- a/cpp/csp/cppnodes/baselibimpl.cpp +++ b/cpp/csp/cppnodes/baselibimpl.cpp @@ -628,6 +628,9 @@ DECLARE_CPPNODE( struct_field ) START() { auto * structType = static_cast( x.type() ); + //Special check for null meta ( csp.Struct type ) which can end up here from a csp.static_cast + if( !structType -> meta() ) + CSP_THROW( TypeError, "Struct csp.Struct has no field named " << field.value() ); m_fieldAccess = structType -> meta() -> field( field ); if( !m_fieldAccess ) CSP_THROW( TypeError, "Struct " << structType -> meta() -> name() << " has no field named " << field.value() ); diff --git a/csp/baselib.py b/csp/baselib.py index 1a628afe1..d6cf5c551 100644 --- a/csp/baselib.py +++ b/csp/baselib.py @@ -763,8 +763,11 @@ def static_cast(x: ts["T"], outType: "U") -> ts["U"]: This should only be used when the caller knows with 100% certainty that the type conversion is always valid as there will be no runtime type checking. """ - # Special case bool / int which are native types, but bool evaluates as a subclass of int - if not issubclass(outType, x.tstype.typ) or (outType is bool and x.tstype.typ is int): + # Allow static_cast on subclass types except for these exceptions: + # - type object which is a base of everything + # - case bool / int which are native types, but bool evaluates as a subclass of int + + if x.tstype.typ is object or not issubclass(outType, x.tstype.typ) or (outType is bool and x.tstype.typ is int): raise TypeError(f"Unable to csp.static_cast edge of type {x.tstype.typ.__name__} to {outType.__name__}") return Edge(ts[outType], nodedef=x.nodedef, output_idx=x.output_idx, basket_idx=x.basket_idx) diff --git a/csp/tests/test_baselib.py b/csp/tests/test_baselib.py index 0d362d55b..d4ce106b5 100644 --- a/csp/tests/test_baselib.py +++ b/csp/tests/test_baselib.py @@ -1207,6 +1207,7 @@ class D(Base): x_int = csp.const(1) x_bool = csp.const(True) x_float = csp.const(123.456) + x_object = csp.const.using(T=object)(1) x_b = csp.const.using(T=Base)(D(a=1, b=2.1)) @@ -1228,10 +1229,22 @@ def g(): with self.assertRaisesRegex(TypeError, "Unable to csp.static_cast edge of type int to bool"): csp.run(csp.static_cast(x_int, bool), starttime=utc_now(), endtime=timedelta()) + with self.assertRaisesRegex(TypeError, "Unable to csp.static_cast edge of type object to int"): + csp.run(csp.static_cast(x_object, int), starttime=utc_now(), endtime=timedelta()) + # Runtime type check with self.assertRaisesRegex(TypeError, 'expected output type on .* to be of type "int" got type "float"'): csp.run(csp.dynamic_cast(x_float, int), starttime=utc_now(), endtime=timedelta()) + class S(csp.Struct): + a: int + + with self.assertRaisesRegex(TypeError, "Struct csp.Struct has no field named a"): + # Was a crash before, dont crash on struct_field access when upcast from csp.Struct which has no metadata + csp.run( + csp.static_cast(csp.const.using(T=csp.Struct)(S(a=1)), S).a, starttime=utc_now(), endtime=timedelta() + ) + if __name__ == "__main__": unittest.main()