def _cast_tuple(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(orig_stype, s_types.Tuple) # Make sure the source tuple expression is pinned in the scope, # so that we don't generate a cross-product of it by evaluating # the tuple indirections. pathctx.register_set_in_scope(ir_set, ctx=ctx) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) orig_subtypes = dict(orig_stype.iter_subtypes(ctx.env.schema)) if direct_cast is not None: # Direct casting to non-tuple involves casting each tuple # element and also keeping the cast around the whole tuple. # This is to trigger the downstream logic of casting # objects (in elements of the tuple). elements = [] for n in orig_subtypes: val = setgen.tuple_indirection_set( ir_set, source=orig_stype, ptr_name=n, ctx=ctx, ) val_type = setgen.get_set_type(val, ctx=ctx) # Element cast val = compile_cast(val, new_stype, ctx=ctx, srcctx=srcctx) elements.append(irast.TupleElement(name=n, val=val)) new_tuple = setgen.new_tuple_set( elements, named=orig_stype.is_named(ctx.env.schema), ctx=ctx, ) return _cast_to_ir(new_tuple, direct_cast, orig_stype, new_stype, ctx=ctx) if not new_stype.is_tuple(ctx.env.schema): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) assert isinstance(new_stype, s_types.Tuple) new_subtypes = list(new_stype.iter_subtypes(ctx.env.schema)) if len(orig_subtypes) != len(new_subtypes): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}: ' f'the number of elements is not the same', context=srcctx) # For tuple-to-tuple casts we generate a new tuple # to simplify things on sqlgen side. elements = [] for i, n in enumerate(orig_subtypes): val = setgen.tuple_indirection_set( ir_set, source=orig_stype, ptr_name=n, ctx=ctx, ) val_type = setgen.get_set_type(val, ctx=ctx) new_el_name, new_st = new_subtypes[i] if val_type != new_st: # Element cast val = compile_cast(val, new_st, ctx=ctx, srcctx=srcctx) elements.append(irast.TupleElement(name=new_el_name, val=val)) return setgen.new_tuple_set( elements, named=new_stype.is_named(ctx.env.schema), ctx=ctx, )
def type_to_typeref( schema: s_schema.Schema, t: s_types.Type, *, cache: Optional[Dict[TypeRefCacheKey, irast.TypeRef]] = None, typename: Optional[s_name.QualName] = None, include_descendants: bool = False, include_ancestors: bool = False, _name: Optional[str] = None, ) -> irast.TypeRef: """Return an instance of :class:`ir.ast.TypeRef` for a given type. An IR TypeRef is an object that fully describes a schema type for the purposes of query compilation. Args: schema: A schema instance, in which the type *t* is defined. t: A schema type instance. cache: Optional mapping from (type UUID, typename) to cached IR TypeRefs. typename: Optional name hint to use for the type in the returned TypeRef. If ``None``, the type name is used. include_descendants: Whether to include the description of all material type descendants of *t*. include_ancestors: Whether to include the description of all material type ancestors of *t*. _name: Optional subtype element name if this type is a collection within a Tuple, Returns: A ``TypeRef`` instance corresponding to the given schema type. """ result: irast.TypeRef material_type: s_types.Type key = (t.id, include_descendants, include_ancestors) if cache is not None and typename is None: cached_result = cache.get(key) if cached_result is not None: # If the schema changed due to an ongoing compilation, the name # hint might be outdated. if cached_result.name_hint == t.get_name(schema): return cached_result if t.is_anytuple(schema): result = irast.AnyTupleRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif t.is_any(schema): result = irast.AnyTypeRef( id=t.id, name_hint=typename or t.get_name(schema), ) elif not isinstance(t, s_types.Collection): assert isinstance(t, s_types.InheritingType) union_of = t.get_union_of(schema) if union_of: non_overlapping, union_is_concrete = ( s_utils.get_non_overlapping_union( schema, union_of.objects(schema), ) ) union = frozenset( type_to_typeref(schema, c, cache=cache) for c in non_overlapping ) else: union_is_concrete = False union = frozenset() intersection_of = t.get_intersection_of(schema) if intersection_of: intersection = frozenset( type_to_typeref(schema, c, cache=cache) for c in intersection_of.objects(schema) ) else: intersection = frozenset() schema, material_type = t.material_type(schema) material_typeref: Optional[irast.TypeRef] if material_type != t: material_typeref = type_to_typeref( schema, material_type, include_descendants=include_descendants, include_ancestors=include_ancestors, cache=cache, ) else: material_typeref = None if (isinstance(material_type, s_scalars.ScalarType) and not material_type.get_abstract(schema)): base_type = material_type.get_topmost_concrete_base(schema) if base_type == material_type: base_typeref = None else: assert isinstance(base_type, s_types.Type) base_typeref = type_to_typeref( schema, base_type, cache=cache ) else: base_typeref = None tname = t.get_name(schema) if typename is not None: name = typename else: name = tname common_parent_ref: Optional[irast.TypeRef] if union_of: common_parent = s_utils.get_class_nearest_common_ancestor( schema, union_of.objects(schema)) assert isinstance(common_parent, s_types.Type) common_parent_ref = type_to_typeref( schema, common_parent, cache=cache ) else: common_parent_ref = None descendants: Optional[FrozenSet[irast.TypeRef]] if material_typeref is None and include_descendants: descendants = frozenset( type_to_typeref( schema, child, cache=cache, include_descendants=True, include_ancestors=include_ancestors, ) for child in t.children(schema) if not child.get_is_derived(schema) ) else: descendants = None ancestors: Optional[FrozenSet[irast.TypeRef]] if material_typeref is None and include_ancestors: ancestors = frozenset( type_to_typeref( schema, ancestor, cache=cache, include_descendants=include_descendants, include_ancestors=False ) for ancestor in t.get_ancestors(schema).objects(schema) ) else: ancestors = None result = irast.TypeRef( id=t.id, name_hint=name, material_type=material_typeref, base_type=base_typeref, descendants=descendants, ancestors=ancestors, union=union, union_is_concrete=union_is_concrete, intersection=intersection, common_parent=common_parent_ref, element_name=_name, is_scalar=t.is_scalar(), is_abstract=t.get_abstract(schema), is_view=t.is_view(schema), is_opaque_union=t.get_is_opaque_union(schema), ) elif isinstance(t, s_types.Tuple) and t.is_named(schema): schema, material_type = t.material_type(schema) if material_type != t: material_typeref = type_to_typeref( schema, material_type, cache=cache ) else: material_typeref = None result = irast.TypeRef( id=t.id, name_hint=typename or t.get_name(schema), material_type=material_typeref, element_name=_name, collection=t.schema_name, in_schema=t.get_is_persistent(schema), subtypes=tuple( type_to_typeref(schema, st, _name=sn) # note: no cache for sn, st in t.iter_subtypes(schema) ) ) else: schema, material_type = t.material_type(schema) if material_type != t: material_typeref = type_to_typeref( schema, material_type, cache=cache ) else: material_typeref = None result = irast.TypeRef( id=t.id, name_hint=typename or t.get_name(schema), material_type=material_typeref, element_name=_name, collection=t.schema_name, in_schema=t.get_is_persistent(schema), subtypes=tuple( type_to_typeref(schema, st, cache=cache) for st in t.get_subtypes(schema) ) ) if cache is not None and typename is None and _name is None: # Note: there is no cache for `_name` variants since they are only used # for Tuple subtypes and thus they will be cached on the outer level # anyway. # There's also no variant for types with custom typenames since they # proved to have a very low hit rate. # This way we save on the size of the key tuple. cache[key] = result return result