def try_bind_call_args( args: Sequence[Tuple[s_types.Type, irast.Set]], kwargs: Mapping[str, Tuple[s_types.Type, irast.Set]], func: s_func.CallableLike, *, ctx: context.ContextLevel) -> Optional[BoundCall]: return_type = func.get_return_type(ctx.env.schema) is_abstract = func.get_abstract(ctx.env.schema) resolved_poly_base_type: Optional[s_types.Type] = None def _get_cast_distance( arg: irast.Set, arg_type: s_types.Type, param_type: s_types.Type, ) -> int: nonlocal resolved_poly_base_type if in_polymorphic_func: # Compiling a body of a polymorphic function. if arg_type.is_polymorphic(schema): if param_type.is_polymorphic(schema): if arg_type.test_polymorphic(schema, param_type): return 0 else: return -1 else: if arg_type.resolve_polymorphic(schema, param_type): return 0 else: return -1 if param_type.is_polymorphic(schema): if not arg_type.test_polymorphic(schema, param_type): return -1 resolved = param_type.resolve_polymorphic(schema, arg_type) if resolved is None: return -1 if resolved_poly_base_type is None: resolved_poly_base_type = resolved if resolved_poly_base_type == resolved: return s_types.MAX_TYPE_DISTANCE if is_abstract else 0 ctx.env.schema, ct = ( resolved_poly_base_type.find_common_implicitly_castable_type( resolved, ctx.env.schema, ) ) if ct is not None: # If we found a common implicitly castable type, we # refine our resolved_poly_base_type to be that as the # more general case. resolved_poly_base_type = ct return s_types.MAX_TYPE_DISTANCE if is_abstract else 0 else: return -1 if arg_type.issubclass(schema, param_type): return 0 return arg_type.get_implicit_cast_distance(param_type, schema) schema = ctx.env.schema in_polymorphic_func = ( ctx.env.options.func_params is not None and ctx.env.options.func_params.has_polymorphic(schema) ) has_empty_variadic = False no_args_call = not args and not kwargs has_inlined_defaults = func.has_inlined_defaults(schema) func_params = func.get_params(schema) if not func_params: if no_args_call: # Match: `func` is a function without parameters # being called with no arguments. bargs: List[BoundArg] = [] if has_inlined_defaults: bytes_t = ctx.env.get_track_schema_type( sn.QualName('std', 'bytes')) typeref = typegen.type_to_typeref(bytes_t, env=ctx.env) argval = setgen.ensure_set( irast.BytesConstant(value=b'\x00', typeref=typeref), typehint=bytes_t, ctx=ctx) bargs = [BoundArg(None, bytes_t, argval, bytes_t, 0)] return BoundCall( func, bargs, set(), return_type, False) else: # No match: `func` is a function without parameters # being called with some arguments. return None named_only = func_params.find_named_only(schema) if no_args_call and func_params.has_required_params(schema): # A call without arguments and there is at least # one parameter without default. return None bound_args_prep: List[Union[MissingArg, BoundArg]] = [] params = func_params.get_in_canonical_order(schema) nparams = len(params) nargs = len(args) has_missing_args = False ai = 0 pi = 0 matched_kwargs = 0 # Bind NAMED ONLY arguments (they are compiled as first set of arguments). while True: if pi >= nparams: break param = params[pi] if param.get_kind(schema) is not _NAMED_ONLY: break pi += 1 param_shortname = param.get_parameter_name(schema) param_type = param.get_type(schema) if param_shortname in kwargs: matched_kwargs += 1 arg_type, arg_val = kwargs[param_shortname] cd = _get_cast_distance(arg_val, arg_type, param_type) if cd < 0: return None bound_args_prep.append( BoundArg(param, param_type, arg_val, arg_type, cd)) else: if param.get_default(schema) is None: # required named parameter without default and # without a matching argument return None has_missing_args = True bound_args_prep.append(MissingArg(param, param_type)) if matched_kwargs != len(kwargs): # extra kwargs? return None # Bind POSITIONAL arguments (compiled to go after NAMED ONLY arguments). while True: if ai < nargs: arg_type, arg_val = args[ai] ai += 1 if pi >= nparams: # too many positional arguments return None param = params[pi] param_type = param.get_type(schema) param_kind = param.get_kind(schema) pi += 1 if param_kind is _NAMED_ONLY: # impossible condition raise RuntimeError('unprocessed NAMED ONLY parameter') if param_kind is _VARIADIC: param_type = cast(s_types.Array, param_type) var_type = param_type.get_subtypes(schema)[0] cd = _get_cast_distance(arg_val, arg_type, var_type) if cd < 0: return None bound_args_prep.append( BoundArg(param, param_type, arg_val, arg_type, cd)) for arg_type, arg_val in args[ai:]: cd = _get_cast_distance(arg_val, arg_type, var_type) if cd < 0: return None bound_args_prep.append( BoundArg(param, param_type, arg_val, arg_type, cd)) break cd = _get_cast_distance(arg_val, arg_type, param_type) if cd < 0: return None bound_args_prep.append( BoundArg(param, param_type, arg_val, arg_type, cd)) else: break # Handle yet unprocessed POSITIONAL & VARIADIC arguments. for pi in range(pi, nparams): param = params[pi] param_kind = param.get_kind(schema) if param_kind is _POSITIONAL: if param.get_default(schema) is None: # required positional parameter that we don't have a # positional argument for. return None has_missing_args = True param_type = param.get_type(schema) bound_args_prep.append(MissingArg(param, param_type)) elif param_kind is _VARIADIC: has_empty_variadic = True elif param_kind is _NAMED_ONLY: # impossible condition raise RuntimeError('unprocessed NAMED ONLY parameter') # Populate defaults. defaults_mask = 0 null_args: Set[str] = set() bound_param_args: List[BoundArg] = [] if has_missing_args: if has_inlined_defaults or named_only: for i, barg in enumerate(bound_args_prep): if isinstance(barg, BoundArg): bound_param_args.append(barg) continue if barg.param is None: # Shouldn't be possible; the code above takes care of this. raise RuntimeError( f'failed to resolve the parameter for the arg #{i}') param = barg.param param_shortname = param.get_parameter_name(schema) null_args.add(param_shortname) defaults_mask |= 1 << i if not has_inlined_defaults: param_default = param.get_default(schema) assert param_default is not None default = dispatch.compile(param_default.qlast, ctx=ctx) empty_default = ( has_inlined_defaults or irutils.is_empty(default) ) param_type = param.get_type(schema) if empty_default: default_type = None if param_type.is_any(schema): if resolved_poly_base_type is None: raise errors.QueryError( f'could not resolve "anytype" type for the ' f'${param_shortname} parameter') else: default_type = resolved_poly_base_type else: default_type = param_type else: default_type = param_type if has_inlined_defaults: default = setgen.new_empty_set( stype=default_type, alias=param_shortname, ctx=ctx) default = setgen.ensure_set( default, typehint=default_type, ctx=ctx) bound_param_args.append( BoundArg( param, param_type, default, param_type, 0, ) ) else: bound_param_args = [ barg for barg in bound_args_prep if isinstance(barg, BoundArg) ] else: bound_param_args = cast(List[BoundArg], bound_args_prep) if has_inlined_defaults: # If we are compiling an EdgeQL function, inject the defaults # bit-mask as a first argument. bytes_t = ctx.env.get_track_schema_type( sn.QualName('std', 'bytes')) bm = defaults_mask.to_bytes(nparams // 8 + 1, 'little') typeref = typegen.type_to_typeref(bytes_t, env=ctx.env) bm_set = setgen.ensure_set( irast.BytesConstant(value=bm, typeref=typeref), typehint=bytes_t, ctx=ctx) bound_param_args.insert(0, BoundArg(None, bytes_t, bm_set, bytes_t, 0)) if return_type.is_polymorphic(schema): if resolved_poly_base_type is not None: ctx.env.schema, return_type = return_type.to_nonpolymorphic( ctx.env.schema, resolved_poly_base_type) elif not in_polymorphic_func: return None # resolved_poly_base_type may be legitimately None within # bodies of polymorphic functions if resolved_poly_base_type is not None: for i, barg in enumerate(bound_param_args): if barg.param_type.is_polymorphic(schema): ctx.env.schema, ptype = barg.param_type.to_nonpolymorphic( ctx.env.schema, resolved_poly_base_type) bound_param_args[i] = BoundArg( barg.param, ptype, barg.val, barg.valtype, barg.cast_distance, ) return BoundCall( func, bound_param_args, null_args, return_type, has_empty_variadic)
def _create_begin(self, schema, context): from edb.ir import utils as irutils fullname = self.classname shortname = sn.shortname_from_fullname(fullname) schema, cp = self._get_param_desc_from_delta(schema, context, self) signature = f'{shortname}({", ".join(p.as_str(schema) for p in cp)})' func = schema.get(fullname, None) if func: raise errors.DuplicateFunctionDefinitionError( f'cannot create the `{signature}` function: ' f'a function with the same signature ' f'is already defined', context=self.source_context) schema = super()._create_begin(schema, context) params: FuncParameterList = self.scls.get_params(schema) language = self.scls.get_language(schema) return_type = self.scls.get_return_type(schema) return_typemod = self.scls.get_return_typemod(schema) from_function = self.scls.get_from_function(schema) has_polymorphic = params.has_polymorphic(schema) polymorphic_return_type = return_type.is_polymorphic(schema) named_only = params.find_named_only(schema) session_only = self.scls.get_session_only(schema) # Certain syntax is only allowed in "EdgeDB developer" mode, # i.e. when populating std library, etc. if not context.stdmode and not context.testmode: if has_polymorphic or polymorphic_return_type: raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'generic types are not supported in ' f'user-defined functions', context=self.source_context) elif from_function: raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'"FROM SQL FUNCTION" is not supported in ' f'user-defined functions', context=self.source_context) elif language != qlast.Language.EdgeQL: raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'"FROM {language}" is not supported in ' f'user-defined functions', context=self.source_context) if polymorphic_return_type and not has_polymorphic: raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'function returns a generic type but has no ' f'generic parameters', context=self.source_context) overloaded_funcs = schema.get_functions(shortname, ()) has_from_function = from_function for func in overloaded_funcs: func_params = func.get_params(schema) func_named_only = func_params.find_named_only(schema) func_from_function = func.get_from_function(schema) if func_named_only.keys() != named_only.keys(): raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'overloading another function with different ' f'named only parameters: ' f'"{func.get_shortname(schema)}' f'{func_params.as_str(schema)}"', context=self.source_context) if ((has_polymorphic or func_params.has_polymorphic(schema)) and (func.get_return_typemod(schema) != return_typemod)): func_return_typemod = func.get_return_typemod(schema) raise errors.InvalidFunctionDefinitionError( f'cannot create the polymorphic `{signature} -> ' f'{return_typemod.to_edgeql()} ' f'{return_type.get_displayname(schema)}` ' f'function: overloading another function with different ' f'return type {func_return_typemod.to_edgeql()} ' f'{func.get_return_type(schema).get_displayname(schema)}', context=self.source_context) if session_only != func.get_session_only(schema): raise errors.InvalidFunctionDefinitionError( f'cannot create `{signature}` function: ' f'overloading another function with different ' f'`session_only` flag', context=self.source_context) if func_from_function: has_from_function = func_from_function if has_from_function: if (from_function != has_from_function or any( f.get_from_function(schema) != has_from_function for f in overloaded_funcs)): raise errors.InvalidFunctionDefinitionError( f'cannot create the `{signature}` function: ' f'overloading "FROM SQL FUNCTION" functions is ' f'allowed only when all functions point to the same ' f'SQL function', context=self.source_context) if (language == qlast.Language.EdgeQL and any( p.get_typemod(schema) is ft.TypeModifier.SET_OF for p in params.objects(schema))): raise errors.UnsupportedFeatureError( f'cannot create the `{signature}` function: ' f'SET OF parameters in user-defined EdgeQL functions are ' f'not supported', context=self.source_context) # check that params of type 'anytype' don't have defaults for p in params.objects(schema): p_default = p.get_default(schema) if p_default is None: continue p_type = p.get_type(schema) try: ir_default = p.get_ir_default(schema=schema) except Exception as ex: raise errors.InvalidFunctionDefinitionError( f'cannot create the `{signature}` function: ' f'invalid default value {p_default.text!r} of parameter ' f'{p.get_displayname(schema)!r}: {ex}', context=self.source_context) check_default_type = True if p_type.is_polymorphic(schema): if irutils.is_empty(ir_default.expr): check_default_type = False else: raise errors.InvalidFunctionDefinitionError( f'cannot create the `{signature}` function: ' f'polymorphic parameter of type ' f'{p_type.get_displayname(schema)} cannot ' f'have a non-empty default value', context=self.source_context) elif (p.get_typemod(schema) is ft.TypeModifier.OPTIONAL and irutils.is_empty(ir_default.expr)): check_default_type = False if check_default_type: default_type = ir_default.stype if not default_type.assignment_castable_to(p_type, schema): raise errors.InvalidFunctionDefinitionError( f'cannot create the `{signature}` function: ' f'invalid declaration of parameter ' f'{p.get_displayname(schema)!r}: ' f'unexpected type of the default expression: ' f'{default_type.get_displayname(schema)}, expected ' f'{p_type.get_displayname(schema)}', context=self.source_context) return schema
def try_bind_call_args(args: typing.List[typing.Tuple[s_types.Type, irast.Base]], kwargs: typing.Dict[str, typing.Tuple[s_types.Type, irast.Base]], func: s_func.CallableObject, *, ctx: context.ContextLevel) -> BoundCall: def _get_cast_distance(arg, arg_type, param_type) -> int: nonlocal resolved_poly_base_type if in_polymorphic_func: # Compiling a body of a polymorphic function. if arg_type.is_polymorphic(schema): if param_type.is_polymorphic(schema): if arg_type.test_polymorphic(schema, param_type): return 0 else: return -1 else: if arg_type.resolve_polymorphic(schema, param_type): return 0 else: return -1 else: if arg_type.is_polymorphic(schema): raise errors.QueryError( f'a polymorphic argument in a non-polymorphic function', context=arg.context) if param_type.is_polymorphic(schema): if not arg_type.test_polymorphic(schema, param_type): return -1 resolved = param_type.resolve_polymorphic(schema, arg_type) if resolved is None: return -1 if resolved_poly_base_type is None: resolved_poly_base_type = resolved if resolved_poly_base_type == resolved: return 0 ct = resolved_poly_base_type.find_common_implicitly_castable_type( resolved, ctx.env.schema) if ct is not None: # If we found a common implicitly castable type, we # refine our resolved_poly_base_type to be that as the # more general case. resolved_poly_base_type = ct return 0 else: return -1 if arg_type.issubclass(schema, param_type): return 0 return arg_type.get_implicit_cast_distance(param_type, schema) schema = ctx.env.schema in_polymorphic_func = (ctx.func is not None and ctx.func.get_params(schema).has_polymorphic(schema)) has_empty_variadic = False resolved_poly_base_type = None no_args_call = not args and not kwargs has_inlined_defaults = func.has_inlined_defaults(schema) func_params = func.get_params(schema) if not func_params: if no_args_call: # Match: `func` is a function without parameters # being called with no arguments. args = [] if has_inlined_defaults: bytes_t = schema.get('std::bytes') argval = setgen.ensure_set(irast.BytesConstant( value=b'\x00', typeref=irtyputils.type_to_typeref(schema, bytes_t)), typehint=bytes_t, ctx=ctx) args = [BoundArg(None, bytes_t, argval, bytes_t, 0)] return BoundCall(func, args, set(), func.get_return_type(schema), False) else: # No match: `func` is a function without parameters # being called with some arguments. return _NO_MATCH pg_params = s_func.PgParams.from_params(schema, func_params) named_only = func_params.find_named_only(schema) if no_args_call and pg_params.has_param_wo_default: # A call without arguments and there is at least # one parameter without default. return _NO_MATCH bound_param_args = [] params = pg_params.params nparams = len(params) nargs = len(args) has_missing_args = False ai = 0 pi = 0 matched_kwargs = 0 # Bind NAMED ONLY arguments (they are compiled as first set of arguments). while True: if pi >= nparams: break param = params[pi] if param.get_kind(schema) is not _NAMED_ONLY: break pi += 1 param_shortname = param.get_shortname(schema) param_type = param.get_type(schema) if param_shortname in kwargs: matched_kwargs += 1 arg_type, arg_val = kwargs[param_shortname] cd = _get_cast_distance(arg_val, arg_type, param_type) if cd < 0: return _NO_MATCH bound_param_args.append( BoundArg(param, param_type, arg_val, arg_type, cd)) else: if param.get_default(schema) is None: # required named parameter without default and # without a matching argument return _NO_MATCH has_missing_args = True bound_param_args.append( BoundArg(param, param_type, None, param_type, 0)) if matched_kwargs != len(kwargs): # extra kwargs? return _NO_MATCH # Bind POSITIONAL arguments (compiled to go after NAMED ONLY arguments). while True: if ai < nargs: arg_type, arg_val = args[ai] ai += 1 if pi >= nparams: # too many positional arguments return _NO_MATCH param = params[pi] param_type = param.get_type(schema) param_kind = param.get_kind(schema) pi += 1 if param_kind is _NAMED_ONLY: # impossible condition raise RuntimeError('unprocessed NAMED ONLY parameter') if param_kind is _VARIADIC: var_type = param.get_type(schema).get_subtypes(schema)[0] cd = _get_cast_distance(arg_val, arg_type, var_type) if cd < 0: return _NO_MATCH bound_param_args.append( BoundArg(param, param_type, arg_val, arg_type, cd)) for arg_type, arg_val in args[ai:]: cd = _get_cast_distance(arg_val, arg_type, var_type) if cd < 0: return _NO_MATCH bound_param_args.append( BoundArg(param, param_type, arg_val, arg_type, cd)) break cd = _get_cast_distance(arg_val, arg_type, param.get_type(schema)) if cd < 0: return _NO_MATCH bound_param_args.append( BoundArg(param, param_type, arg_val, arg_type, cd)) else: break # Handle yet unprocessed POSITIONAL & VARIADIC arguments. for pi in range(pi, nparams): param = params[pi] param_kind = param.get_kind(schema) if param_kind is _POSITIONAL: if param.get_default(schema) is None: # required positional parameter that we don't have a # positional argument for. return _NO_MATCH has_missing_args = True param_type = param.get_type(schema) bound_param_args.append( BoundArg(param, param_type, None, param_type, 0)) elif param_kind is _VARIADIC: has_empty_variadic = True elif param_kind is _NAMED_ONLY: # impossible condition raise RuntimeError('unprocessed NAMED ONLY parameter') # Populate defaults. defaults_mask = 0 null_args = set() if has_missing_args: if has_inlined_defaults or named_only: for i in range(len(bound_param_args)): barg = bound_param_args[i] if barg.val is not None: continue param = barg.param param_shortname = param.get_shortname(schema) null_args.add(param_shortname) defaults_mask |= 1 << i if not has_inlined_defaults: ql_default = param.get_ql_default(schema) default = dispatch.compile(ql_default, ctx=ctx) empty_default = (has_inlined_defaults or irutils.is_empty(default)) param_type = param.get_type(schema) if empty_default: default_type = None if param_type.is_any(): if resolved_poly_base_type is None: raise errors.QueryError( f'could not resolve "anytype" type for the ' f'${param_shortname} parameter') else: default_type = resolved_poly_base_type else: default_type = param_type else: default_type = param_type if has_inlined_defaults: default = setgen.new_empty_set(stype=default_type, alias=param_shortname, ctx=ctx) default = setgen.ensure_set(default, typehint=default_type, ctx=ctx) bound_param_args[i] = BoundArg( param, param_type, default, barg.valtype, barg.cast_distance, ) else: bound_param_args = [ barg for barg in bound_param_args if barg.val is not None ] if has_inlined_defaults: # If we are compiling an EdgeQL function, inject the defaults # bit-mask as a first argument. bytes_t = schema.get('std::bytes') bm = defaults_mask.to_bytes(nparams // 8 + 1, 'little') bm_set = setgen.ensure_set(irast.BytesConstant( value=bm, typeref=irtyputils.type_to_typeref(ctx.env.schema, bytes_t)), typehint=bytes_t, ctx=ctx) bound_param_args.insert(0, BoundArg(None, bytes_t, bm_set, bytes_t, 0)) return_type = func.get_return_type(schema) if return_type.is_polymorphic(schema): if resolved_poly_base_type is not None: return_type = return_type.to_nonpolymorphic( schema, resolved_poly_base_type) elif not in_polymorphic_func: return _NO_MATCH # resolved_poly_base_type may be legitimately None within # bodies of polymorphic functions if resolved_poly_base_type is not None: for i, barg in enumerate(bound_param_args): if barg.param_type.is_polymorphic(schema): bound_param_args[i] = BoundArg( barg.param, barg.param_type.to_nonpolymorphic(schema, resolved_poly_base_type), barg.val, barg.valtype, barg.cast_distance, ) return BoundCall(func, bound_param_args, null_args, return_type, has_empty_variadic)