Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/derive-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod pymodule;
mod pypayload;
mod pystructseq;
mod pytraverse;
mod text_signature;

use error::Diagnostic;
use metadata::PunctuatedNestedMeta;
Expand Down
53 changes: 52 additions & 1 deletion crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
sig_initializer,
sig_constructor,
sig_structseq,
text_signature,
} = extract_impl_attrs(attr, &impl_ty)?;
let payload_ty = attr_payload.unwrap_or(payload_guess);
let method_def = &context.method_items;
Expand Down Expand Up @@ -254,6 +255,7 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
sig_initializer,
sig_constructor,
sig_structseq,
text_signature.as_ref(),
);
// The table names `Self`, so it has to be an associated const on the
// payload, not a `static` item inside `extend_slots`.
Expand Down Expand Up @@ -321,8 +323,15 @@ pub(crate) fn impl_pyclass_impl(attr: PunctuatedNestedMeta, item: Item) -> Resul
let ExtractedImplAttrs {
with_impl,
with_slots,
text_signature,
..
} = extract_impl_attrs(attr, &trai.ident)?;
if text_signature.is_some() {
bail_span!(
trai.ident,
"text_signature belongs on the concrete class impl"
)
}

let method_def = &context.method_items;
let getset_impl = &context.getset_items;
Expand Down Expand Up @@ -1413,6 +1422,7 @@ where
),
args.context.self_ty_subst.as_ref(),
None,
item_meta.text_signature()?.as_ref(),
);
let doc_body_pending =
doc_body_pending_expr(args.context.self_ty_subst.as_ref(), &py_name, rust_doc);
Expand Down Expand Up @@ -1823,7 +1833,7 @@ impl ToTokens for GetSetNursery {
struct MethodItemMeta(ItemMetaInner);

impl ItemMeta for MethodItemMeta {
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw", "coexist"];
const ALLOWED_NAMES: &'static [&'static str] = &["name", "raw", "coexist", "text_signature"];

fn from_inner(inner: ItemMetaInner) -> Self {
Self(inner)
Expand Down Expand Up @@ -2578,6 +2588,7 @@ struct ExtractedImplAttrs {
sig_initializer: bool,
sig_constructor: bool,
sig_structseq: bool,
text_signature: Option<crate::text_signature::TextSignature>,
}

fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<ExtractedImplAttrs> {
Expand All @@ -2590,6 +2601,7 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
let mut has_initializer = false;
let mut has_constructor = false;
let mut has_structseq = false;
let mut text_signature = None;

for attr in attr {
match attr {
Expand Down Expand Up @@ -2687,6 +2699,21 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
} else {
bail_span!(value, "payload must be a string literal")
}
} else if path.is_ident("text_signature") {
if text_signature.is_some() {
bail_span!(path, "duplicate text_signature")
}
let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit),
..
}) = value
else {
bail_span!(value, "text_signature must be a string literal")
};
text_signature = Some(crate::text_signature::TextSignature::parse(
lit.value(),
lit.span(),
)?);
} else if path.is_ident("itemsize") {
itemsize = Some(value);
} else {
Expand Down Expand Up @@ -2717,6 +2744,7 @@ fn extract_impl_attrs(attr: PunctuatedNestedMeta, item: &Ident) -> Result<Extrac
sig_initializer: has_initializer,
sig_constructor: has_constructor,
sig_structseq: has_structseq,
text_signature,
})
}

Expand All @@ -2725,9 +2753,32 @@ fn class_internal_doc(
has_initializer: bool,
has_constructor: bool,
has_structseq: bool,
text_signature: Option<&crate::text_signature::TextSignature>,
) -> TokenStream {
let init_params = quote!(<<#payload as ::rustpython_vm::types::Initializer>::Args as ::rustpython_vm::function::FromArgs>::PARAMS);
let ctor_params = quote!(<<#payload as ::rustpython_vm::types::Constructor>::Args as ::rustpython_vm::function::FromArgs>::PARAMS);
if let Some(signature) = text_signature {
// An explicit class signature can contain an unrepresentable default.
// Check the actual constructor/initializer binder, not real_signature's
// filtered display metadata.
let args = if has_initializer {
quote!(<#payload as ::rustpython_vm::types::Initializer>::Args)
} else if has_constructor {
quote!(<#payload as ::rustpython_vm::types::Constructor>::Args)
} else {
return quote!(compile_error!(
"class text_signature requires Constructor or Initializer"
));
};
let name = quote!(<#payload as ::rustpython_vm::class::PyClassDef>::NAME);
let doc = quote!(<#payload as ::rustpython_vm::class::PyClassDef>::DOC);
let args_const = quote! {
const ARGS: &[::rustpython_vm::function::SigArg] = &[
::rustpython_vm::function::SigArg::from_arg::<#args>(""),
];
};
return crate::util::doc_from_signature_args(&name, doc, args_const, Some(signature));
}
let chosen = match (has_initializer, has_constructor) {
(true, true) => quote! {
::rustpython_vm::function::choose_class_params(#init_params, #ctor_params)
Expand Down
25 changes: 23 additions & 2 deletions crates/derive-impl/src/pymodule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ impl ModuleItem for FunctionItem {

let py_name = item_meta.simple_name()?;
let mut flags_signatures = vec![item_meta.signature_from_flags()?];
let mut text_signatures = vec![item_meta.text_signature()?];
let mut py_names = vec![py_name];
for attr_index in self.py_attrs.iter().rev() {
let mut loop_unit = || {
Expand All @@ -692,19 +693,31 @@ impl ModuleItem for FunctionItem {
));
}
flags_signatures.push(item_meta.signature_from_flags()?);
text_signatures.push(item_meta.text_signature()?);
py_names.push(py_name);
Ok(())
};
let r = loop_unit();
args.context.errors.ok_or_push(r);
}

if flags_signatures
.iter()
.zip(&text_signatures)
.any(|(flags, text)| *flags && text.is_some())
{
return Err(self.new_syn_error(
ident.span(),
"text_signature and signature_from_flags are mutually exclusive",
));
}
let module = args.doc_module();
let rust_doc = args.attrs.doc().filter(|doc| !doc.is_empty());
let docs = py_names
.iter()
.zip(flags_signatures)
.map(|(py_name, from_flags)| {
.zip(text_signatures)
.map(|((py_name, from_flags), text_signature)| {
let db_doc = if rust_doc.is_some() {
None
} else {
Expand All @@ -716,7 +729,15 @@ impl ModuleItem for FunctionItem {
if from_flags {
flags_signature_doc_tokens(func.sig(), py_name, doc)
} else {
internal_doc_tokens(func.sig(), py_name, None, doc, None, Some("$module"))
internal_doc_tokens(
func.sig(),
py_name,
None,
doc,
None,
Some("$module"),
text_signature.as_ref(),
)
}
})
.collect();
Expand Down
Loading
Loading