-
Notifications
You must be signed in to change notification settings - Fork 877
Introspection: Introduce TypeHint struct #5438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
7d6b267
to
7bb5b56
Compare
src/inspect/mod.rs
Outdated
pub struct TypeHint { | ||
/// The type hint annotation | ||
#[doc(hidden)] | ||
pub annotation: &'static str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have preferred it to be an enum but sadly I don't see how to serialize it: const functions cannot concatenate and recursive macros are not possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would the enum definition be? I can see if I can come up with any crazy ideas if I know the preferred form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like:
enum TypeHint {clashes
BuiltIn { name: &'static str },
Member { module: &'static str, name: &'static str },
Union(&[TypeHint]),
Subscript { value: TypeHint, parameters: &[TypeHint] },
Callable { arguments: &[TypeHint], return: TypeHint } // Special case because of the nested [] syntax
}
This way it's easy to ensure the syntax is valid and to do manipulation like introducing aliases in case of name conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I think if we split the const fn up into one which (recursively) calculates the final length, and a second which serializes to it with &mut [u8]
similar to what I did in the concat.rs
module, I think we could probably make the const fn work?
Unfortunately this would require MSRV 1.83. But this is currently an experimental feature, I would personally be ok with having it limited to MSRV 1.83.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
This works fairly well. I have not introduced macros to build the TypeHint
enum yet, I plan to do it in a follow up. My guess is that the real use case is for the custom type hint in the signature
, most of INPUT_TYPE
and OUTPUT_TYPE
are likely fine with just the const constructors.
I now serialize the type hint as an AST, this allows to manipulate it when building the type stubs. As an example, I wrote some code that generates nice from X import Y
imports and generate aliases in case of name conflicts.
src/inspect/mod.rs
Outdated
/// assert_eq!(T.to_string(), "collections.abc.Sequence"); | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! type_hint { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better as a const function. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, is there a way to merge all the macros into one? Maybe also with implicit syntax
type_hint!(a.A); // basic type hint
type_hint!(a.A | b.B); // union type hint
type_hint!(a.A[b.B]); // parameterised type hint
we could infer all the imports using the dotted path rules, maybe in complex cases we require users to write the imports?
type_hint!(
from a import A;
from b import B;
A.NestedClass | B
);
or maybe can use mixed syntax somehow
type_hint!(type_hint("a", "A.NestedClass") | b.B);
the "implicit syntax" seems nicest for the 99% of cases which users will write, so while I'm not sure exactly how to tie it all together it seems desirable to me (I think?) to have an intuitive syntax for the common case and then an escape hatch for the awkward ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks indeed much nicer. I think we need a syntax for composition like in #[derive(FromPyObject)]
: we want their to build type hints that is stuff like the union of type hints. So we want to write things like type_hint!(tuple[A::INPUT_TYPE, B::INPUT_TYPE])
. But my guess it's mostly for auto-generated code.
What about having two macros:
- For static type hints like
type_hint!(a.A[b.B]);
with hopefully support for thefrom a import A
syntax. - For composition
compose_type_hint!()
where elements are all supposed to be other type hints likecompose_type_hint!(PyTuple::INPUT_TYPE [ A::INPUT_TYPE, B::INPUT_TYPE ])
. But I am not sure it's more readable than the current state.
WDYT?
It might also take into account in this design the type stubs in signatures like signature = (foo: collections.abs.Sequence[int]) -> list[int]
. My guess is that we can allow there the same syntax than type_hint!
and allow to set the from a import A
in #[pymodule]
. But it makes name conflict detection more fun...
47a4fc1
to
4ece557
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this looks like the right direction for making the type hint definitions reliable wrt import resolution.
I think we can iterate a bit to find the best design for users, I wrote down some ideas.
src/inspect/mod.rs
Outdated
}}; | ||
} | ||
|
||
/// Allows to build a [`TypeHint`] that is the subscripted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sentence looks unfinished.
src/inspect/mod.rs
Outdated
pub struct TypeHint { | ||
/// The type hint annotation | ||
#[doc(hidden)] | ||
pub annotation: &'static str, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would the enum definition be? I can see if I can come up with any crazy ideas if I know the preferred form.
src/inspect/mod.rs
Outdated
/// assert_eq!(T.to_string(), "collections.abc.Sequence"); | ||
/// ``` | ||
#[macro_export] | ||
macro_rules! type_hint { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, is there a way to merge all the macros into one? Maybe also with implicit syntax
type_hint!(a.A); // basic type hint
type_hint!(a.A | b.B); // union type hint
type_hint!(a.A[b.B]); // parameterised type hint
we could infer all the imports using the dotted path rules, maybe in complex cases we require users to write the imports?
type_hint!(
from a import A;
from b import B;
A.NestedClass | B
);
or maybe can use mixed syntax somehow
type_hint!(type_hint("a", "A.NestedClass") | b.B);
the "implicit syntax" seems nicest for the 99% of cases which users will write, so while I'm not sure exactly how to tie it all together it seems desirable to me (I think?) to have an intuitive syntax for the common case and then an escape hatch for the awkward ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the annotation_stub
stuff is still present, should we remove it at the same time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code to extract modules was in the stubs.rs
module and got removed
inspect::TypeHint is composed of an "annotation" string and a list of "imports" ("from X import Y" kind) The type is expected to be built using the macros `type_hint!(module, name)`, `type_hint_union!(*args)` and `type_hint_subscript(main, *args)` that take care of maintaining the import list Introspection data generation is done using the hidden type_hint_json macro to avoid that the proc macros generate too much code Sadly, outside `type_hint` these macros can't be converted into const functions because they need to do some concatenation. I introduced `type_hint!` for consistency, happy to convert it to a const function. Miscellaneous changes: - Rename PyType{Info,Check}::TYPE_INFO into TYPE_HINT - Drop redundant PyClassImpl::TYPE_NAME
4ece557
to
02ce980
Compare
#[derive(Debug, Eq, PartialEq, Clone, Hash)] | ||
pub enum TypeHint { | ||
Ast(TypeHintExpr), | ||
Plain(String), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for custom type hint witten in the #[pyo3(signature = ]
attribute.
02ce980
to
1f56ea9
Compare
1f56ea9
to
3fee134
Compare
"chrono-tz", | ||
"either", | ||
"experimental-async", | ||
"experimental-inspect", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now added it dynamically in the _get_feature_sets
function of the noxfile. Not sure if it's the best way to do it
/// Provides the full python type as a type hint. | ||
#[cfg(feature = "experimental-inspect")] | ||
const PYTHON_TYPE: &'static str = "typing.Any"; | ||
const TYPE_HINT: TypeHint = TypeHint::module_member("typing", "Any"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can drop this and only have the field on PyTypeCheck
. I will do it in a follow up except if you prefer it done in this MR
inspect::TypeHint is an AST of a Python type hint.
It is supposed to be created using a set of
const
constructors and used only in const context.Introspection data generation is done using two associated methods. The serialized data is also an AST.
I make use of the AST to generate nice
from X import Y as Z
lines in the type stubs with code that auto-detect conflicts.Miscellaneous changes: