diff --git a/majit/majit-translate/src/annotator/bookkeeper.rs b/majit/majit-translate/src/annotator/bookkeeper.rs index 371f0ffe97c..bbd7c10206a 100644 --- a/majit/majit-translate/src/annotator/bookkeeper.rs +++ b/majit/majit-translate/src/annotator/bookkeeper.rs @@ -756,26 +756,34 @@ impl Bookkeeper { pairs.sort(); for (root, variant_names) in pairs { - // Materialize the discriminant-only base classdef before a - // variant references it through `getmro`; idempotent with the - // struct-root loop. `canonical_struct_name(root)` is the same - // spelling `intern_enum_variant_host` resolves the base under, - // so the pre-mint and the discriminant-narrowing resolver share - // one base lineage and the variant subtree numbers as one - // bracket. - let canon_root = majit_ir::descr::canonical_struct_name(&root); - let base = self.intern_class_by_qualname(&canon_root); - let _ = self.getuniqueclassdef(&base); - for variant in variant_names { - // The SAME interning primitive the discriminant-narrowing - // resolver ([`Self::getuniqueclassdef_for_enum_variant`]) - // and the variant ctor arm (`flowspace_adapter`) use, so - // all three sites resolve ONE variant classdef under the - // `::`-qualified key — no `.`-vs-`::` split that would mint - // a second, distinct sibling the single numbering pass never - // reaches. - let variant_host = self.intern_enum_variant_host(&root, &variant); - let _ = self.getuniqueclassdef(&variant_host); + // Each variant is resolved through the canonical resolver, which + // materializes the discriminant-only base first + // ([`Self::getuniqueclassdef_for_struct_root`]) before interning + // the variant subclass — so the pre-mint and the + // discriminant-narrowing resolver share one base lineage and the + // variant subtree still numbers as one contiguous bracket. + for variant in &variant_names { + // Resolve each variant through the canonical resolver rather + // than a bare intern. It mints the discriminant-only base, + // interns the variant subclass under the same `::`-qualified + // key the discriminant-narrowing resolver and the variant ctor + // arm use (so all three resolve ONE variant classdef, no + // `.`-vs-`::` sibling split the single numbering pass misses), + // AND projects the variant's payload rows — draining any + // struct the payload first reaches — before it returns. + // + // The payload projection is what a bare intern skipped: a + // variant field typed `*mut PyObject` would otherwise stay an + // untyped FORCE shell until subject-flow narrowing first + // resolves it (`enum_variant_narrowing_knowntypedata` -> + // `getuniqueclassdef_for_enum_variant`), where a first-intern + // of the pointee struct mid-fixpoint generalises an + // already-annotated cell. Projecting here — at prologue time, + // before `assign_inheritance_ids` and any subject flow, with + // the pending drain inside the call — makes the variant's + // payload classdefs order-independent, the same contract the + // struct-root loop above relies on. + let _ = self.getuniqueclassdef_for_enum_variant(&root, variant); } } } diff --git a/majit/majit-translate/src/front/mir.rs b/majit/majit-translate/src/front/mir.rs index c15760b4ee4..51cb24ff0bf 100644 --- a/majit/majit-translate/src/front/mir.rs +++ b/majit/majit-translate/src/front/mir.rs @@ -1872,7 +1872,22 @@ fn harden_duplicate_leaf_metadata( ) { let mut by_leaf: std::collections::HashMap<&str, Vec<&str>> = std::collections::HashMap::new(); for key in struct_fields.fields.keys() { - if let Some((_, leaf)) = key.rsplit_once("::") { + if let Some((head, leaf)) = key.rsplit_once("::") { + // A `{Enum}::{Variant}` key whose variant leaf ALSO names an + // enum base is not a peer type-leaf of that base: its bare + // alias is withdrawn by the variant-leaf pass below (keyed on + // the `Enum::Variant` tail), so admitting it into this + // type-leaf bucket lets the variant (`typedef::BytesSubArg::Buffer`, + // rows `[__pos_0]`) withdraw the same-named enum type's bare + // alias (`buffer::Buffer`, rows `[__discriminant]`) on a + // spurious row divergence. Restricting the skip to a leaf that + // is itself an enum base withdraws only the genuine + // type-vs-variant name collision, leaving every other + // variant-leaf bucket (whose withdrawal the resume numbering + // depends on) intact. + if struct_fields.is_enum_base(head) && struct_fields.is_enum_base(leaf) { + continue; + } by_leaf.entry(leaf).or_default().push(key); } } diff --git a/pyre/pyre-interpreter/src/builtins.rs b/pyre/pyre-interpreter/src/builtins.rs index df5e67614be..457fb70ccae 100644 --- a/pyre/pyre-interpreter/src/builtins.rs +++ b/pyre/pyre-interpreter/src/builtins.rs @@ -4257,6 +4257,23 @@ pub(crate) fn split_builtin_kwargs(args: &[PyObjectRef]) -> (&[PyObjectRef], Opt (args, None) } +/// Length of the leading non-null run of `args`. +/// +/// `bind_kwargs_to_signature` pads the flat argument slice out to the full +/// parameter count with PY_NULL for keyword-only slots and absent optionals, +/// so the true positional count is the prefix before the first PY_NULL. +/// A single named function keeps the count off the annotator's shared +/// iterator-adapter graph: a `take_while` closure inlined per wrapper gives +/// every `__pyre_wrap_*` shim its own closure type, and merging those +/// distinct types on one `TakeWhile` graph's input has no common base class. +pub(crate) fn leading_non_null_count(args: &[PyObjectRef]) -> usize { + let mut count = 0; + while count < args.len() && !args[count].is_null() { + count += 1; + } + count +} + /// Cold dictionary-strategy half of the flat builtin-keyword ABI. /// /// The caller first proves `last` is a dict. Keeping the strategy dispatch diff --git a/pyre/pyre-interpreter/src/gateway.rs b/pyre/pyre-interpreter/src/gateway.rs index a3709b3a787..c52b3725b6d 100644 --- a/pyre/pyre-interpreter/src/gateway.rs +++ b/pyre/pyre-interpreter/src/gateway.rs @@ -1191,6 +1191,24 @@ pub fn make_builtin_function_with_text_signature( function } +/// `make_builtin_function_with_text_signature` that also records an argument +/// `Signature` (`Some` routes through the keyword-binding constructor; `None` +/// falls back to the positional-only one). Used by the `#[pyre_methods]` +/// all-required instance-method arm, which wants both the generated +/// `__text_signature__` for introspection and by-name keyword binding. +pub fn make_builtin_function_with_text_signature_and_sig( + name: &'static str, + func: BuiltinCodeFn, + text_signature: &'static str, + signature: Option, +) -> PyObjectRef { + let function = make_builtin_function_maybe_sig(name, func, signature); + unsafe { + crate::function::fset_func_text_signature(function, pyre_object::w_str_new(text_signature)); + } + function +} + /// Fixed-arity twin of `make_builtin_function_with_text_signature`. pub fn make_builtin_function_with_arity_and_text_signature( name: &'static str, diff --git a/pyre/pyre-interpreter/src/module/_random/macro_smoke.rs b/pyre/pyre-interpreter/src/module/_random/macro_smoke.rs index 04a76456553..646e503ea28 100644 --- a/pyre/pyre-interpreter/src/module/_random/macro_smoke.rs +++ b/pyre/pyre-interpreter/src/module/_random/macro_smoke.rs @@ -39,6 +39,20 @@ impl Demo { fn __reduce__(&self) -> PyObjectRef { crate::pytuple![type_object(), crate::pytuple![], self.getstate()] } + // A positional-or-keyword parameter plus a keyword-only default: the + // instance-method arm must build a `Signature` (`self` posonly, then + // `factor`, then a `marker_kwonly()` tail with `bias`) so the call path + // binds keywords by name and the wrapper preamble needs no marker-dict + // strip. + fn combine( + &self, + factor: i64, + #[kwonly] + #[default(0i64)] + bias: i64, + ) -> i64 { + self.state as i64 * factor + bias + } // `#[getter]` / `#[setter]` / `#[deleter]` GetSetProperty quad. #[getter(doc = "raw 64-bit state as a signed int")] fn raw_state(&self) -> i64 { @@ -193,6 +207,44 @@ mod tests { assert_eq!(Demo::from_obj(obj).expect("initialized Demo").state, 37); } + /// A `#[pyre_methods]` instance method with a positional-or-keyword and a + /// keyword-only parameter binds identically whether the keyword arrives + /// positionally or by name through `bind_kwargs_to_signature` — the same + /// invariant the caller relies on to hand the wrapper a marker-free, + /// PY_NULL-padded scope. + #[test] + fn instance_method_binds_keyword_only_through_signature() { + crate::typedef::init_typeobjects(); + let cls = type_object(); + let obj = __pyre_wrap___new__(&[cls]).expect("synthesized __new__"); + __pyre_wrap___init__(&[obj, w_int_new(7)]).expect("Demo.__init__"); + + // The `Signature` the instance-method arm derives for + // `combine(&self, factor, #[kwonly] bias)`: `self` positional-only, + // then `factor`, then a keyword-only `bias`. + let signature = crate::gateway::Signature::new( + vec!["self", "factor", "bias"], + None, + None, + /*kwonlyargcount*/ 1, + /*posonlyargcount*/ 1, + ); + let bound = crate::call::bind_kwargs_to_signature( + &signature, + "combine", + &[obj, w_int_new(3)], + &[(rustpython_wtf8::Wtf8Buf::from("bias"), w_int_new(5))], + ) + .expect("signature binding"); + // 7 * 3 + 5 through the bound (marker-free, PY_NULL-padded) scope. + let via_keyword = __pyre_wrap_combine(&bound).expect("keyword-bound combine"); + assert_eq!(unsafe { w_int_get_value(via_keyword) }, 26); + + // The all-positional call omits `bias`, so its `#[default(0)]` applies. + let via_positional = __pyre_wrap_combine(&[obj, w_int_new(3)]).expect("positional combine"); + assert_eq!(unsafe { w_int_get_value(via_positional) }, 21); + } + /// TypeDef ownership is process-global: another OS thread must observe /// the exact same Python type object, not a fresh TLS allocation. #[test] diff --git a/pyre/pyre-interpreter/src/objspace/std/mapdict.rs b/pyre/pyre-interpreter/src/objspace/std/mapdict.rs index e4ac5a6baa7..7317dc8fd1c 100644 --- a/pyre/pyre-interpreter/src/objspace/std/mapdict.rs +++ b/pyre/pyre-interpreter/src/objspace/std/mapdict.rs @@ -690,12 +690,16 @@ pub unsafe fn instance_node_getdictvalue_checked( ensure_mapdict_initialized(obj); let inst = &mut *(obj as *mut pyre_object::W_ObjectObject); let map = inst._get_mapdict_map(); - let w_res = unsafe { node_read_checked(map, inst, name, DICT) }; + let w = unsafe { node_read_checked(map, inst, name, DICT) }?; // mapdict.py:846-847 getdictvalue → read → _direct_read (592-598): lazily // migrate to boxed storage when the read attribute is unboxed and its class - // has frozen unboxing. + // has frozen unboxing. A raise in `read` unwinds before the migration tail + // (846-847 → 58 → 312-313 returns None on the miss path, never reaching + // _direct_read), so migration is skipped whenever the read raised; + // propagating the read error here mirrors that — and `maybe_migrate_to_boxed` + // re-derives the None on the raising path, so it is a no-op there regardless. unsafe { maybe_migrate_to_boxed(map, inst, name, DICT) }; - w_res + Ok(w) } /// `deldictvalue` routed to the mapdict node layer (mapdict.py:852-857 diff --git a/pyre/pyre-interpreter/src/typedef.rs b/pyre/pyre-interpreter/src/typedef.rs index 53c33b2c320..fa6c7396a88 100644 --- a/pyre/pyre-interpreter/src/typedef.rs +++ b/pyre/pyre-interpreter/src/typedef.rs @@ -2072,8 +2072,16 @@ unsafe fn retag_classmethod_descriptors(type_obj: PyObjectRef) { /// it those carry no owner and report their errors under the bare method name, /// where the namespace entry for the same method reports `type.name`. /// +/// `dont_look_inside`: reads the prebuilt `method_owner` table's per-type +/// `static OWNER` — a field-bearing frozen instance carrying a `fn` pointer, +/// which the annotator cannot model as a prebuilt constant. Making this a +/// residual-call boundary keeps the receiver-owner setup opaque to the JIT +/// so a lifting caller (`getattr_str_impl`'s bound-method assembly) is not +/// dragged into the unmodellable static read. +/// /// # Safety /// `func` must be a valid, live function object. +#[majit_macros::dont_look_inside] pub(crate) unsafe fn stamp_builtin_owner(func: PyObjectRef, type_name: &str) { let Some(owner) = method_owner(type_name) else { return; @@ -2598,6 +2606,19 @@ pub(crate) fn make_new_descr_with_signature( crate::make_builtin_function_as_builtin_with_signature("__new__", func, signature) } +/// [`make_new_descr`] optionally carrying a `Signature`: `Some` binds keyword +/// arguments by name before the constructor runs; `None` (a variadic +/// whole-args `__new__`) keeps the positional-only carrier. +pub(crate) fn make_new_descr_maybe_sig( + func: fn(&[PyObjectRef]) -> Result, + signature: Option, +) -> PyObjectRef { + match signature { + Some(signature) => make_new_descr_with_signature(func, signature), + None => make_new_descr(func), + } +} + /// `typeobject.c tp_new_wrapper` — `__new__` takes the class to instantiate /// as its first argument, so a call without one is rejected before the body /// reads the value positionals behind it. diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs index 756dbae4867..2e955c2c57a 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/inline_call.rs @@ -2031,13 +2031,11 @@ pub(crate) fn walker_ec_leave( /// descriptor. The first instruction's arraylen descriptor is deliberately /// not interchangeable with the later getarrayitem descriptor. pub(super) fn wrapper_args_item_descr_index(code: &[u8]) -> Option { - // Keyword-capable generated wrappers first call - // `split_builtin_kwargs(args)` before their arity/unwrap code. Its - // positional-slice result can therefore occupy a register other than the - // wrapper input r0, and register colouring can move it again between the - // arity check and unwrap. Generated gateways perform their argument - // extraction before entering the typed body, so the first Ref item read - // after the first slice-length read is the wrapper-argument descriptor. + // Generated gateways perform their argument extraction before entering the + // typed body, reading the slice length before any element. The first Ref + // item read after the first slice-length read is therefore the + // wrapper-argument descriptor, independent of which register colouring + // assigns to the slice. let arraylen_pc = crate::jitcode_runtime::decoded_ops(code) .find(|decoded| decoded.key == "arraylen_gc/rd>i") .map(|decoded| decoded.pc)?; diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs index 27100f28fd3..9fdcb3328b6 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs @@ -143,45 +143,40 @@ fn builtin_wrapper_heapcache_uses_item_not_length_descr() { } #[test] -fn keyword_builtin_wrapper_finds_colored_argument_slice_item_descr() { +fn signature_bound_wrapper_reads_argument_slice_with_distinct_item_descr() { let wrapper = named_jitcode("__pyre_wrap_getrandbits").expect("getrandbits builtin wrapper jitcode"); - let mut ops = crate::jitcode_runtime::decoded_ops(&wrapper.code); - let first = ops.next().expect("wrapper first op"); - // The result colour is not pinned. `split_builtin_kwargs` returns the - // aggregate `(&[PyObjectRef], Option)`; when the codewriter - // materializes that pair the entry call yields it by reference - // (`inline_call_r_r`), and when it inlines the body far enough to leave - // only the leading `args.is_empty()` test at the entry the same call - // yields that by value (`inline_call_r_i`). Both start the wrapper by - // splitting positional from keyword arguments, which is what this asserts. + let first = crate::jitcode_runtime::decoded_ops(&wrapper.code) + .next() + .expect("wrapper first op"); + // `getrandbits` registers with a `Signature`, so the call path resolves + // keywords into positional PY_NULL-padded slots before the wrapper runs. + // There is no `split_builtin_kwargs(args)` peel at the entry, so the + // wrapper does not open with the splitter's `inline_call_*`; it reads its + // argument array directly. assert!( - first.opname.starts_with("inline_call_"), - "keyword wrapper starts by splitting positional and keyword arguments, got {}", + !first.opname.starts_with("inline_call_"), + "signature-bound wrapper does not open with a keyword-split call, got {}", first.key ); let item_descr_index = wrapper_args_item_descr_index(&wrapper.code).expect("wrapper item descriptor"); - // Select by descr identity rather than by position. The inlined splitter - // reads `args.len()` off the wrapper's own `r0` before the split, so "the - // first `arraylen_gc`" names that read and not the positional slice's once - // the body inlines; the item descr names the slice under every inline - // depth. + // Select by descr identity rather than by position: the length read names + // the array itself and the item read names its elements, so the two carry + // distinct heap-cache descriptors even though both index the same slice. let getitem = crate::jitcode_runtime::decoded_ops(&wrapper.code) .find(|op| { op.key == "getarrayitem_gc_r/rid>r" && item_pool_descr_index(&wrapper.code, op.pc + 3) == item_descr_index }) - .expect("keyword wrapper argument-slice item read"); + .expect("wrapper argument-slice item read"); let slice_reg = wrapper.code[getitem.pc + 1]; - assert_ne!( - slice_reg, 0, - "register coloring keeps the argument slice off r0 at extraction" - ); + // With no keyword split the argument slice is the wrapper input itself + // (r0); the length read names the same register. crate::jitcode_runtime::decoded_ops(&wrapper.code) .find(|op| op.key == "arraylen_gc/rd>i" && wrapper.code[op.pc + 1] == slice_reg) - .expect("keyword wrapper reads the argument-slice length off the colored slice"); + .expect("wrapper reads the argument-slice length off the same slice register"); } /// Descr-pool index encoded little-endian at `at`, resolved to its diff --git a/pyre/pyre-macros/src/lib.rs b/pyre/pyre-macros/src/lib.rs index 07afdd187b0..a79ec9d6c1c 100644 --- a/pyre/pyre-macros/src/lib.rs +++ b/pyre/pyre-macros/src/lib.rs @@ -133,11 +133,8 @@ fn expand_pyre_function(func: ItemFn) -> syn::Result { // optionals would otherwise be counted as positional. PY_NULL is // never a real argument, so the leading non-null run is the true // positional count on both the bound and the raw path. - let __pyre_positional_count = crate::builtins::split_builtin_kwargs(args) - .0 - .iter() - .take_while(|slot| !slot.is_null()) - .count(); + let __pyre_positional_count = + crate::builtins::leading_non_null_count(crate::builtins::split_builtin_kwargs(args).0); const __PYRE_PARAM_NAMES: &[&str] = &[ #(#name_lits),* ]; const __PYRE_PARAM_REQUIRED: &[bool] = &[ #(#req_lits),* ]; let __pyre_bound_args; @@ -1896,9 +1893,15 @@ fn expand_pyre_methods( let mut param_names = Vec::::new(); let mut param_required = Vec::::new(); let mut param_positional = Vec::::new(); + // `SignatureBuilder` statements mirroring `#[pyre_function]` + // (see `expand_pyre_function`): each parameter appends its name, a + // `#[kwonly]` param emits `marker_kwonly()` at the tail's start, and a + // `#[kwargs]` param records the `**kwargs` name instead of appending. + let mut sig_stmts = Vec::::new(); let mut has_varargs = false; let mut kwonly_tail = false; let mut kwonly_start = None; + let mut kwonly_marked = false; for (offset, arg) in inputs.enumerate() { let FnArg::Typed(pt) = arg else { return Err(syn::Error::new( @@ -1919,7 +1922,22 @@ fn expand_pyre_methods( kwonly_start = Some(offset); } } - param_names.push(param_name(offset, pt)); + let pname = param_name(offset, pt); + // A bare `&[PyObjectRef]` (raw whole-args passthrough) suppresses + // the signature entirely (`method_sig_body` below), so it + // contributes no builder statement. + if !is_varargs_param(&pt.ty) { + if is_kwargs { + sig_stmts.push(quote! { __b.kwargname = ::std::option::Option::Some(#pname); }); + } else { + if is_kwonly && !kwonly_marked { + sig_stmts.push(quote! { __b.marker_kwonly(); }); + kwonly_marked = true; + } + sig_stmts.push(quote! { __b.append(#pname); }); + } + } + param_names.push(pname); // Optional iff it has a `#[default(...)]` or is `Option`. param_required.push(arg_default(pt)?.is_none() && option_inner(&pt.ty).is_none()); param_positional.push(!kwonly_tail && !is_kwargs); @@ -1967,45 +1985,16 @@ fn expand_pyre_methods( let __pyre_positional_count = args.len(); } } else { - let mut all_names: Vec = Vec::new(); - let mut all_required: Vec = Vec::new(); - if matches!(kind, MethodKind::Instance) { - all_names.push("self".to_string()); - all_required.push(true); - } - all_names.extend(param_names.iter().cloned()); - all_required.extend(param_required.iter().copied()); - let name_lits = all_names.iter().map(|n| quote! { #n }); - let req_lits = all_required.iter().map(|b| quote! { #b }); - let fn_name_str = mname.to_string(); quote! { - let __pyre_has_kwargs = crate::builtins::has_builtin_kwargs(args); - // `bind_kwargs_to_signature` pads `args` out to the - // full parameter count with PY_NULL, so keyword-only - // slots and absent optionals would otherwise be counted - // as positional. PY_NULL is never a real argument, so - // the leading non-null run is the true positional count - // on both the bound and the raw path. - let __pyre_positional_count = crate::builtins::split_builtin_kwargs(args) - .0 - .iter() - .take_while(|slot| !slot.is_null()) - .count(); - const __PYRE_PARAM_NAMES: &[&str] = &[ #(#name_lits),* ]; - const __PYRE_PARAM_REQUIRED: &[bool] = &[ #(#req_lits),* ]; - let __pyre_bound_args; - let args: &[::pyre_object::PyObjectRef] = - if __pyre_has_kwargs { - __pyre_bound_args = crate::builtins::bind_builtin_kwargs( - args, - __PYRE_PARAM_NAMES, - __PYRE_PARAM_REQUIRED, - #fn_name_str, - )?; - &__pyre_bound_args - } else { - args - }; + // Every `#[pyre_methods]` shim registers with a + // `Signature` (see `method_sig` below), so the call path + // (`call::bind_kwargs_to_signature`) has already resolved + // keywords into positional PY_NULL-padded slots before the + // wrapper runs — there is no trailing `__pyre_kw__` marker + // dict to peel. PY_NULL is never a real argument, so the + // leading non-null run is the true positional count on both + // the keyword-bound and the raw positional path. + let __pyre_positional_count = crate::builtins::leading_non_null_count(args); } } }; @@ -2177,39 +2166,41 @@ fn expand_pyre_methods( func: #wrapper_name, }; }); + // The argument `Signature` this method binds keywords against. An + // instance method prepends `self` as a positional-only slot (filled by + // the receiver, never by keyword), mirroring interp2app's `self` + // handling (`UnwrapSpec_Check.visit__W_Root`, gateway.py:227-236); a + // raw whole-args slice has no by-name binding and carries `None`. + // `#[staticmethod]` / `#[classmethod]` name every slot (including + // `cls`) as a typed parameter, so they append no synthetic receiver. + let sig_receiver = if matches!(kind, MethodKind::Instance) { + quote! { + __b.append("self"); + __b.marker_posonly(); + } + } else { + quote! {} + }; + let method_sig_body = if has_varargs { + quote! { ::std::option::Option::None } + } else { + quote! { + let mut __b = crate::SignatureBuilder::default(); + #sig_receiver + #(#sig_stmts)* + ::std::option::Option::Some(__b.signature()) + } + }; + let method_sig = quote! {{ #method_sig_body }}; + // gateway.py:1146-1211 `_generate_text_signature`: a regular // interp2app instance method whose arguments are all required and // non-variadic has a lossless generated signature. Defaults need // their Python repr, so those continue to require an explicit // declaration rather than guessing from Rust tokens. - let raw_fn = if let Some(kwonly_start) = kwonly_start { - let names = param_names.iter().map(|name| name.as_str()); - let receiver = if matches!(kind, MethodKind::Instance) { - quote! { - __b.append("self"); - __b.marker_posonly(); - } - } else { - quote! {} - }; - let before_kwonly = names.clone().take(kwonly_start); - let after_kwonly = names.skip(kwonly_start); - quote! { - crate::make_builtin_function_maybe_sig( - #py_name, - #wrapper_name, - { - let mut __b = crate::SignatureBuilder::default(); - #receiver - #(__b.append(#before_kwonly);)* - __b.marker_kwonly(); - #(__b.append(#after_kwonly);)* - ::std::option::Option::Some(__b.signature()) - }, - ) - } - } else if matches!(kind, MethodKind::Instance) + let raw_fn = if matches!(kind, MethodKind::Instance) && !has_varargs + && kwonly_start.is_none() && param_required.iter().all(|required| *required) { let mut parts = vec!["$self".to_string()]; @@ -2217,22 +2208,21 @@ fn expand_pyre_methods( parts.push("/".to_string()); let text_signature = format!("({})", parts.join(", ")); quote! { - crate::gateway::make_builtin_function_with_text_signature( + crate::gateway::make_builtin_function_with_text_signature_and_sig( #py_name, #wrapper_name, #text_signature, + #method_sig, ) } - } else if is_new { - // A `tp_new` wrapper is `builtin_function_or_method`, the type - // `make_new_descr` hands to every by-hand `__new__`. `inspect` - // decides whether a class has a user-defined `__new__` by testing - // for that type, so leaving this one a plain `function` routes the - // class down the bound-method path instead of reading its - // `__text_signature__`. - quote! { crate::gateway::make_builtin_function_as_builtin(#py_name, #wrapper_name) } } else { - quote! { crate::make_builtin_function(#py_name, #wrapper_name) } + quote! { + crate::make_builtin_function_maybe_sig( + #py_name, + #wrapper_name, + #method_sig, + ) + } }; match &kind { MethodKind::Instance => { @@ -2247,7 +2237,7 @@ fn expand_pyre_methods( MethodKind::Static if py_name == "__new__" => { registrations.push(quote! { unsafe { pyre_object::dictmultiobject::w_dict_setitem_str_no_proxy(ns, #py_name, - crate::typedef::make_new_descr(#wrapper_name)) }; + crate::typedef::make_new_descr_maybe_sig(#wrapper_name, #method_sig)) }; }); } MethodKind::Static => {