-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Use new style args for int.__new__ #755
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
Use new style args for int.__new__ #755
Conversation
or keyword arguments.
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 for working on this! I was planning something similar but hadn't had the time yet. I have a few concerns I'd like addressed before we merge this though - it's going to be used a lot so I want us to take the time to make sure the design is right.
vm/src/builtins.rs
Outdated
end: Option<PyStringRef>, | ||
flush: bool, | ||
#[keyword] | ||
sep: OptionalArg<Option<PyStringRef>>, |
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 prefer to avoid using OptionalArg
in these:
OptionalArg<Option<...>>
feels very weird- We can derive whether an arg is optional or not based on the presence of a future
default
attribute (or perhaps something like #[pyarg(optional = true)], basically I want to avoid reusing any of theFromArgs
extractors inside of these structs)
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.
Agreed, OptionalArg<Option<...>> is awkward, but wouldn't be needed when I implement the default attribute argument. I'm split about having OptionalArg in these structs, I think in some cases we need to differentiate between a missing argument and passing None.
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 should handle this entirely with attributes rather than reusing OptionalArg
. In the following, None
refers to a Python None
and Option::None
refers to Rust's none.
Thing is required, and cannot be None
.
#[derive(FromArgs)]
struct MyArgs {
#[pyarg(positional)]
thing: ThingRef,
}
Thing is required, but can be None
:
#[derive(FromArgs)]
struct MyArgs {
#[pyarg(positional)]
thing: Option<ThingRef>,
}
Thing is not required, can be None
, and will use the Default
impl for Option
if not passed, which is Option::None
.
#[derive(FromArgs)]
struct MyArgs {
#[pyarg(positional, default)]
thing: Option<ThingRef>,
}
Thing is not required, cannot be None
, and will use the result of if the argument is not present.
#[derive(FromArgs)]
struct MyArgs {
#[pyarg(positional, default = "<method that returns ThingRef>")]
thing: ThingRef,
}
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 still think in some situations it's not sufficient to just have a default, some functions need to detect if its argument was provided. For example, if the base argument is provided to int.new then the first argument can then only be a string, otherwise, it can be other types like int or float.
CPython implements int.new by using NULL as the default. https://github.com/python/cpython/blob/master/Objects/longobject.c#L4950
I have added an 'optional 'argument to pyarg, to indicate when we are want to convert into OptionalArg. e.g.
#[derive(FromArgs)]
struct IntOptions {
#[pyarg(positional, optional = true)]
val_options: OptionalArg<PyObjectRef>,
#[pyarg(positional_keyword, optional = true)]
base: OptionalArg<u32>,
}
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.
For example, if the base argument is provided to int.new then the first argument can then only be a string, otherwise, it can be other types like int or float.
In cases like these, the "function overloading" trick might work, e.g. range does:
fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let range = if args.args.len() <= 2 {
let (cls, stop) = args.bind(vm)?;
PyRangeRef::new(cls, stop, vm)
} else {
let (cls, start, stop, step) = args.bind(vm)?;
PyRangeRef::new_from(cls, start, stop, step, vm)
}?;
Ok(range.into_object())
}
Basically there can be two different int_new
methods (that accept different types) decided upon based on arity.
Codecov Report
@@ Coverage Diff @@
## master #755 +/- ##
==========================================
- Coverage 61.86% 61.85% -0.02%
==========================================
Files 80 80
Lines 12656 12744 +88
Branches 2592 2621 +29
==========================================
+ Hits 7830 7883 +53
- Misses 3163 3185 +22
- Partials 1663 1676 +13
Continue to review full report at Codecov.
|
Thanks, this is looking really great! I'd still like to figure out a way to avoid |
# Conflicts: # vm/src/function.rs
To achieve this I anhanced FromArgs derive proc macro to support positional arguments. Positional-only arguments have the #[positional] attribute in front of it's field and while keyword-only arguments have #[keyword] attribute in front of it's field.