@@ -435,6 +435,12 @@ class ModuleAspect(enum.Enum):
435435 LATE = enum .auto ()
436436
437437
438+ @dataclasses .dataclass (frozen = True , kw_only = True )
439+ class Backlink :
440+ source : reflection .ObjectType
441+ pointer : reflection .Pointer
442+
443+
438444class SchemaGenerator :
439445 def __init__ (
440446 self ,
@@ -451,6 +457,9 @@ def __init__(
451457 self ._std_modules : list [SchemaPath ] = []
452458 self ._types : Mapping [str , reflection .Type ] = {}
453459 self ._casts : reflection .CastMatrix
460+ self ._backlinks : dict [
461+ reflection .ObjectType , dict [str , list [Backlink ]]
462+ ] = {}
454463 self ._operators : reflection .OperatorMatrix
455464 self ._functions : list [reflection .Function ]
456465 self ._globals : list [reflection .Global ]
@@ -579,6 +588,7 @@ def run(self, outdir: pathlib.Path) -> tuple[Schema, set[pathlib.Path]]:
579588 all_casts = self ._casts ,
580589 all_operators = self ._operators ,
581590 all_globals = self ._globals ,
591+ all_backlinks = self ._backlinks ,
582592 modules = self ._modules ,
583593 schema_part = self ._schema_part ,
584594 )
@@ -604,6 +614,7 @@ def run(self, outdir: pathlib.Path) -> tuple[Schema, set[pathlib.Path]]:
604614 all_casts = self ._casts ,
605615 all_operators = self ._operators ,
606616 all_globals = self ._globals ,
617+ all_backlinks = self ._backlinks ,
607618 modules = all_modules ,
608619 schema_part = self ._schema_part ,
609620 )
@@ -659,6 +670,24 @@ def introspect_schema(self) -> Schema:
659670 if reflection .is_object_type (t ):
660671 name = t .schemapath
661672 self ._modules [name .parent ]["object_types" ][name .name ] = t
673+
674+ for p in t .pointers :
675+ if (
676+ reflection .is_link (p )
677+ # For now don't include std::BaseObject.__type__
678+ # Users should just select on the appropriate type
679+ and p .name != "__type__"
680+ ):
681+ target = self ._types [p .target_id ]
682+ assert isinstance (target , reflection .ObjectType )
683+ if target not in self ._backlinks :
684+ self ._backlinks [target ] = {}
685+ if p .name not in self ._backlinks [target ]:
686+ self ._backlinks [target ][p .name ] = []
687+ self ._backlinks [target ][p .name ].append (
688+ Backlink (source = t , pointer = p )
689+ )
690+
662691 elif reflection .is_scalar_type (t ):
663692 name = t .schemapath
664693 self ._modules [name .parent ]["scalar_types" ][name .name ] = t
@@ -694,6 +723,7 @@ def _generate_common_types(
694723 all_casts = self ._casts ,
695724 all_operators = self ._operators ,
696725 all_globals = self ._globals ,
726+ all_backlinks = self ._backlinks ,
697727 modules = self ._modules ,
698728 schema_part = self ._schema_part ,
699729 )
@@ -944,6 +974,9 @@ def __init__(
944974 all_casts : reflection .CastMatrix ,
945975 all_operators : reflection .OperatorMatrix ,
946976 all_globals : list [reflection .Global ],
977+ all_backlinks : Mapping [
978+ reflection .ObjectType , Mapping [str , Sequence [Backlink ]]
979+ ],
947980 modules : Collection [SchemaPath ],
948981 schema_part : reflection .SchemaPart ,
949982 ) -> None :
@@ -954,6 +987,7 @@ def __init__(
954987 self ._casts = all_casts
955988 self ._operators = all_operators
956989 self ._globals = all_globals
990+ self ._backlinks = all_backlinks
957991 schema_obj_type = None
958992 for t in all_types .values ():
959993 self ._types_by_name [t .name ] = t
@@ -2340,7 +2374,7 @@ def write_generic_types(
23402374 unpack = self .import_name ("typing_extensions" , "Unpack" )
23412375 geltype = self .import_name (BASE_IMPL , "GelType" )
23422376 geltypemeta = self .import_name (BASE_IMPL , "GelTypeMeta" )
2343- gelmodel = self .import_name (BASE_IMPL , "GelModel " )
2377+ gelobjectmodel = self .import_name (BASE_IMPL , "GelObjectModel " )
23442378 gelmodelmeta = self .import_name (BASE_IMPL , "GelModelMeta" )
23452379 anytuple = self .import_name (BASE_IMPL , "AnyTuple" )
23462380 anynamedtuple = self .import_name (BASE_IMPL , "AnyNamedTuple" )
@@ -2367,7 +2401,7 @@ def write_generic_types(
23672401 geltype ,
23682402 ],
23692403 SchemaPath ("std" , "anyobject" ): [
2370- gelmodel ,
2404+ gelobjectmodel ,
23712405 "anytype" ,
23722406 ],
23732407 SchemaPath ("std" , "anytuple" ): [
@@ -4086,6 +4120,9 @@ def _mangle_default_shape(name: str) -> str:
40864120 if proplinks :
40874121 self .write_object_type_link_models (objtype )
40884122
4123+ if objtype .name != "std::FreeObject" :
4124+ self ._write_object_backlinks (objtype )
4125+
40894126 anyobject_meta = self .get_object (
40904127 SchemaPath ("std" , "__anyobject_meta__" ),
40914128 aspect = ModuleAspect .SHAPES ,
@@ -4213,6 +4250,16 @@ def write_id_computed(
42134250 self .write (f"__gel_type_class__ = __{ name } _ops__" )
42144251 if objtype .name == "std::BaseObject" :
42154252 write_id_attr (objtype , "RequiredId" )
4253+
4254+ if objtype .name != "std::FreeObject" :
4255+ backlinks_model_name = self ._mangle_backlinks_model_name (name )
4256+ g_oblm_desc = self .import_name (
4257+ BASE_IMPL , "GelObjectBacklinksModelDescriptor"
4258+ )
4259+ oblm_desc = f"{ g_oblm_desc } [{ backlinks_model_name } ]"
4260+ self .write (f"__backlinks__: { oblm_desc } = { oblm_desc } ()" )
4261+ self .write ()
4262+
42164263 self ._write_base_object_type_body (objtype , include_tname = True )
42174264 with self .type_checking ():
42184265 self ._write_object_type_qb_methods (objtype )
@@ -4333,6 +4380,208 @@ def write_id_computed(
43334380
43344381 self .write ()
43354382
4383+ @staticmethod
4384+ def _mangle_backlinks_model_name (name : str ) -> str :
4385+ return f"__{ name } _backlinks__"
4386+
4387+ def _write_object_backlinks (
4388+ self ,
4389+ objtype : reflection .ObjectType ,
4390+ ) -> None :
4391+ type_name = objtype .schemapath
4392+ name = type_name .name
4393+
4394+ schema_path = self .import_name (BASE_IMPL , "SchemaPath" )
4395+ parametric_type_name = self .import_name (
4396+ BASE_IMPL , "ParametricTypeName"
4397+ )
4398+ computed_multi_link = self .import_name (BASE_IMPL , "ComputedMultiLink" )
4399+ std_base_object_t = self .get_object (
4400+ SchemaPath .from_segments ("std" , "BaseObject" ),
4401+ aspect = ModuleAspect .SHAPES ,
4402+ )
4403+
4404+ objtype_bases = [
4405+ base_type
4406+ for base_ref in objtype .bases
4407+ if (base_type := self ._types .get (base_ref .id , None ))
4408+ if isinstance (base_type , reflection .ObjectType )
4409+ ]
4410+ objtype_name = type_name .as_python_code (
4411+ schema_path , parametric_type_name
4412+ )
4413+
4414+ backlinks_model_name = self ._mangle_backlinks_model_name (name )
4415+
4416+ backlinks_class_bases : list [str ]
4417+ backlinks_reflection_class_bases : list [str ]
4418+
4419+ # Backlinks' reflections' pointers combine the current types
4420+ # backlinks with those of their base types.
4421+ #
4422+ # Eg. With `type A` and `type B extending A`,
4423+ # - __A_backlinks__.__gel_reflection__.pointers will contain
4424+ # all backlinks to A (and BaseObject)
4425+ # - __B_backlinks__.__gel_reflection__.pointers will contain
4426+ # all backlinks to B, backlinks to A.
4427+ #
4428+ # No base type backlinks are needed for BaseObject.
4429+ backlinks_reflection_pointer_bases : list [str ]
4430+
4431+ if objtype .name == "std::BaseObject" :
4432+ # __BaseObject_backlinks__ derives from GelObjectBacklinksModel
4433+ # while all other backlinks derive from it directly on indirectly.
4434+ object_backlinks_model = self .import_name (
4435+ BASE_IMPL , "GelObjectBacklinksModel"
4436+ )
4437+ backlinks_class_bases = [object_backlinks_model ]
4438+ backlinks_reflection_class_bases = [
4439+ f"{ object_backlinks_model } .__gel_reflection__"
4440+ ]
4441+ backlinks_reflection_pointer_bases = []
4442+ else :
4443+ backlinks_class_bases = [
4444+ self .get_object (
4445+ SchemaPath (
4446+ base_type .schemapath .parent ,
4447+ self ._mangle_backlinks_model_name (
4448+ base_type .schemapath .name
4449+ ),
4450+ ),
4451+ aspect = ModuleAspect .SHAPES ,
4452+ )
4453+ for base_type in objtype_bases
4454+ ]
4455+ backlinks_reflection_class_bases = [
4456+ f"{ bbt } .__gel_reflection__" for bbt in backlinks_class_bases
4457+ ]
4458+ backlinks_reflection_pointer_bases = backlinks_class_bases
4459+
4460+ object_backlinks = self ._backlinks .get (objtype , {})
4461+
4462+ # The backlinks model class
4463+ with self ._class_def (backlinks_model_name , backlinks_class_bases ):
4464+ with self ._class_def (
4465+ "__gel_reflection__" , backlinks_reflection_class_bases
4466+ ):
4467+ self .write (f"name = { objtype_name } " )
4468+ self .write (f"type_name = { objtype_name } " )
4469+ self ._write_backlinks_pointers_reflection (
4470+ object_backlinks , backlinks_reflection_pointer_bases
4471+ )
4472+
4473+ for backlink_name in object_backlinks :
4474+ backlink_t = f"{ computed_multi_link } [{ std_base_object_t } ]"
4475+ self .write (f"{ backlink_name } : { backlink_t } " )
4476+
4477+ self .export (backlinks_model_name )
4478+
4479+ self .write ()
4480+
4481+ def _write_backlinks_pointers_reflection (
4482+ self ,
4483+ object_backlinks : Mapping [str , Sequence [Backlink ]],
4484+ backlinks_reflection_pointer_bases : Sequence [str ],
4485+ ) -> None :
4486+ dict_ = self .import_name (
4487+ "builtins" , "dict" , import_time = ImportTime .typecheck
4488+ )
4489+ str_ = self .import_name (
4490+ "builtins" , "str" , import_time = ImportTime .typecheck
4491+ )
4492+ gel_ptr_ref = self .import_name (
4493+ BASE_IMPL ,
4494+ "GelPointerReflection" ,
4495+ import_time = ImportTime .runtime
4496+ if object_backlinks
4497+ else ImportTime .typecheck ,
4498+ )
4499+ lazyclassproperty = self .import_name (BASE_IMPL , "LazyClassProperty" )
4500+ ptr_ref_t = f"{ dict_ } [{ str_ } , { gel_ptr_ref } ]"
4501+ with self ._classmethod_def (
4502+ "pointers" ,
4503+ [],
4504+ ptr_ref_t ,
4505+ decorators = (f'{ lazyclassproperty } ["{ ptr_ref_t } "]' ,),
4506+ ):
4507+ if object_backlinks :
4508+ self .write (f"my_ptrs: { ptr_ref_t } = {{" )
4509+ classes = {
4510+ "SchemaPath" : self .import_name (BASE_IMPL , "SchemaPath" ),
4511+ "ParametricTypeName" : self .import_name (
4512+ BASE_IMPL , "ParametricTypeName"
4513+ ),
4514+ "GelPointerReflection" : gel_ptr_ref ,
4515+ "Cardinality" : self .import_name (BASE_IMPL , "Cardinality" ),
4516+ "PointerKind" : self .import_name (BASE_IMPL , "PointerKind" ),
4517+ "StdBaseObject" : self .get_object (
4518+ SchemaPath .from_segments ("std" , "BaseObject" ),
4519+ aspect = ModuleAspect .SHAPES ,
4520+ ),
4521+ }
4522+ with self .indented ():
4523+ for (
4524+ backlink_name ,
4525+ backlink_values ,
4526+ ) in object_backlinks .items ():
4527+ r = self ._reflect_backlink (
4528+ backlink_name , backlink_values , classes
4529+ )
4530+ self .write (f"{ backlink_name !r} : { r } ," )
4531+ self .write ("}" )
4532+ else :
4533+ self .write (f"my_ptrs: { ptr_ref_t } = {{}}" )
4534+
4535+ if backlinks_reflection_pointer_bases :
4536+ pp = "__gel_reflection__.pointers"
4537+ ret = self .format_list (
4538+ "return ({list})" ,
4539+ [
4540+ "my_ptrs" ,
4541+ * _map_name (
4542+ lambda s : f"{ s } .{ pp } " ,
4543+ backlinks_reflection_pointer_bases ,
4544+ ),
4545+ ],
4546+ separator = " | " ,
4547+ carry_separator = True ,
4548+ )
4549+ else :
4550+ ret = "return my_ptrs"
4551+
4552+ self .write (ret )
4553+
4554+ self .write ()
4555+
4556+ def _reflect_backlink (
4557+ self ,
4558+ name : str ,
4559+ backlinks : Sequence [Backlink ],
4560+ classes : dict [str , str ],
4561+ ) -> str :
4562+ kwargs : dict [str , str ] = {
4563+ "name" : repr (name ),
4564+ "type" : classes ["StdBaseObject" ],
4565+ "kind" : (
4566+ f"{ classes ['PointerKind' ]} ({ str (reflection .PointerKind .Link )!r} )"
4567+ ),
4568+ "cardinality" : (
4569+ f"{ classes ['Cardinality' ]} ({ str (reflection .Cardinality .Many )!r} )"
4570+ ),
4571+ "computed" : "True" ,
4572+ "readonly" : "True" ,
4573+ "has_default" : "False" ,
4574+ "mutable" : "False" ,
4575+ }
4576+
4577+ # For now don't get any back link props
4578+ kwargs ["properties" ] = "None"
4579+
4580+ return self .format_list (
4581+ f"{ classes ['GelPointerReflection' ]} ({{list}})" ,
4582+ [f"{ k } ={ v } " for k , v in kwargs .items ()],
4583+ )
4584+
43364585 @contextlib .contextmanager
43374586 def _object_type_variant (
43384587 self ,
@@ -4354,8 +4603,8 @@ def _object_type_variant(
43544603 )
43554604
43564605 if not list (variant_bases ):
4357- gel_model = self .import_name (BASE_IMPL , "GelModel " )
4358- bases .append (gel_model )
4606+ gel_object_model = self .import_name (BASE_IMPL , "GelObjectModel " )
4607+ bases .append (gel_object_model )
43594608
43604609 with self ._class_def (
43614610 variant ,
0 commit comments