-
Notifications
You must be signed in to change notification settings - Fork 286
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
PEP 237: int/long unification #1329
Conversation
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.
Didn't manage to go through it all (probably going to take me a few passes to review). Here are my initial comments.
I'm guessing one of the follow-ups will be killing class test(System.Int32): pass
and getting rid of uses of Extensible<int>
?
@@ -656,7 +656,7 @@ public static object eval(CodeContext/*!*/ context, [NotNull]FunctionCode code, | |||
return (BigInteger)res; | |||
} | |||
|
|||
public static PythonType @int => DynamicHelpers.GetPythonTypeFromType(typeof(int)); | |||
public static PythonType @int => DynamicHelpers.GetPythonTypeFromType(typeof(BigInteger)); |
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.
Not necessarily for this PR, but we should consider using TypeCache.BigInteger
instead. Think I saw this in other places as well.
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.
Good point. I'll add it to my follow-up list.
@@ -44,7 +52,8 @@ def remove_clr_specific_attrs(attr_list): | |||
|
|||
# CLR array shortcut | |||
array_cli = System.Array | |||
array_int = System.Array[int] | |||
array_int = System.Array[System.Int32] |
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.
Probably a good idea to make a note of this difference in Upgrading from IronPython 2 to 3. I'm sure I've used System.Array[int]
in my own code assuming I'll get System.Array[System.Int32]
.
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 know, me too. I didn't notice Upgrading from IronPython 2 to 3 before. It will put a comprehensive note there (separate PR).
return cls.CreateInstance(context, value); | ||
#region Constructors | ||
|
||
private static object FastNew(CodeContext/*!*/ context, object o, int @base = 10) { |
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 guess this one will become the master and replace Int32Ops.FastNew
? I wonder if there's a "nicer" way to do this from a git history perspective (e.g. instead of replicating the code here we could update and call the Int32Ops
versions). Maybe it's a non-issue and the follow-up LongOps
/IntOps
unification will take care of it...
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.
Yes, I was planning to handle it in the follow-up LongOps
/IntOps
unification. It is kind of a separate issue since there are several ways of doing it. The idea I had was to make BigIntegerOps
the main version and call it from Int32Ops
as appropriate. This also applies to other methods in Int32Ops
.
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.
Alright, turns out I had a bit more time. Here are some more comments after my first pass.
Thanks for the review. Yes. I didn't post the list of follow-ups I have because it is tentative and more like a jot-down of ideas and mental anchors than clean text, but maybe it is useful to post it anyway, esp. if you will be going through in multiple passes; you may get more questions for follow-ups that may or may not be on the list (in which case please raise the issue). So here it is:
i = 1 # Int32
j = 1<<64 # BigInteger
# before import System
assert set(dir(j)) == set(dir(i))
assert set(dir(i)) == set(dir(j))
import System
assert set(dir(i)) - set(dir(j)) == {'MinValue', 'MaxValue'}
assert set(dir(j)) - set(dir(i)) == {}, "TODO: should be empty: " + str(set(dir(j)) - set(dir(i))) |
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.
Looks good to me!
Side note, guess we'll have to update https://ironpython.net/documentation/dotnet/dotnet.html#mapping-between-python-builtin-types-and-net-types
@@ -47,13 +47,13 @@ def test_add_mul(self): | |||
self.assertEqual((1,2,3) * 2, (1,2,3,1,2,3)) | |||
self.assertEqual(2 * (1,2,3), (1,2,3,1,2,3)) | |||
|
|||
class mylong(long): pass | |||
class mylong(int): pass |
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.
Maybe not worth the effort but I guess we could search the codebase for this pattern and use myint
. Probably something for a follow-up.
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 thought about it too but tried to restraint myself from too much cleanup in this PR to keep is as small as possible, but also because the idea of cleaning up tests can become a big distraction, there is so much that can be cleaned up and it is tempting.
In this particular case, if you just object to the name mylong
, I have planned a pass on that in my follow ups. Since Python long
is gone, long
can only be read as Int64
and better not used at all except as a C# type.
If you meant using myint
from type_util
, then I was not planning of using it here, because the test also defines/uses mylong2
and I think it is more explicit about the test intentions to have both types defined side-by-side. So here probably I would use names myint1
/myint2
instead but keep the class definitions.
|
||
public ListGenericWrapper(IList<object> value) { _value = value; } | ||
|
||
#region IList<T> Members | ||
|
||
public int IndexOf(T item) { | ||
return _value.IndexOf(item); | ||
int pos = _value.IndexOf(item); | ||
if (IsBigIntWrapper && item is BigInteger bi && bi >= int.MinValue && bi <= int.MaxValue) { |
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 hope the jit is smart enough to discard all this and inline the thing. I wish C# had generic specialization...
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'm not sure it will get inlined or even compiled in an optimal way. For instance, even if T
is BigInteger
, going from T item
to BigInteger bi
likely involves boxing, similarly (in other places) casting a BigInteger
result to T
. I have put on my follow-up list to look into the prefromance of the conversion wrappers, but right now, short of creating dedicated classes for BigInteger
and Nullable<BigInteger>
or dropping down to IL, I don't have any ideas.
@@ -721,7 +721,7 @@ def test_buffering_kwparam(self): | |||
with self.assertRaises(ValueError): # can't have unbuffered text I/O | |||
open(file=fname, mode='w', buffering=0) | |||
|
|||
self.assertRaisesMessage(TypeError, "expected int, got float" if is_cli else "integer argument expected, got float", | |||
self.assertRaisesMessage(TypeError, "expected Int32, got float" if is_cli else "integer argument expected, got float", |
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.
Not a big fan of having Int32
pop up on a TypeError
for a standard Python operation. Though not a showstopper since it's an error message... Wonder if we'd be able to make the distinction between a method on a PythonType
and a regular .NET method.
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.
Interesting, I had the same reaction here. But I do like seeing the actual type in error messages from calling regular .NET methods, but then again how to tell the difference between them and methods used to implement Python operations? Maybe check if the method comes from one of IronPython assemblies?
Frankly at this moment, the whole error message creation in DLR is half-broken and would benefit from a redesign, though it is not high on my list of interests... If it comes to that point, I can keep this case in mind.
@@ -753,48 +753,54 @@ def classmeth(cls): pass | |||
self.assertEqual(D.classmeth.__class__, MethodType) | |||
|
|||
def test_cases(self): | |||
def runTest(testCase): | |||
from collections import deque |
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.
Hah, was wondering how it worked before without the deque import, but I guess it didn't even run! Good catch.
Is this page generated from some source, like RST? If so, where is it? The style sheets look rather outdated. I would be OK to review and update the page, and probably learn a few things for myself in the process. Another side note: the link "Tools" at the top of that page is broken. And more side notes: I thought I saw somewhere an issue report about some documentation that didn't get properly migrated from project main (or ironpython2) but now I can't locate it. Any ideas? If indeed there is some documentation missing, it could be taken/migrated in the same cleanup action (though probably separate PRs). |
Unfortunately that particular page seems to be in html instead of RST. Were you thinking of this issue? #1295 |
Yes! Thank you. |
This PR implements PEP 237, as discussed under #52. I have tried to keep it functionally self-contained (i.e. working consistently) but still as small as possible. As a result, there is still a dozen or so smaller updates coming up, mostly cleanup (notably, in
LongOps
/IntOps
). Therefore I checked off PEP 237 as implemented in "WhatsNewInPython30" but want to keep #52 open for a while until everything gets into the mainline.Despite that, it is still a sizable PR, though most of the changes are in the tests. The old tests used
long
profusely, which was aliased toint
. This made the tests passing, but sinceint
wasInt32
,BigInteger
codepaths were not really well tested and the tests were often testing just the same as for oldint
. I've reviewed each case oflong
usage one by one and replacedlong
with eitherint
orbig
, which is a function creatingBigInteger
instances for small integer values that normally would beInt32
. Some tests that became obsolete are removed, and some new tests are added, but in most cases the existing tests are repurposed to test bothInt32
andBigInteger
.Before going full scale with this implementation, I did some prototyping of the remaining possible scenarios. All together, I've considered the following scenarios:
int
is pureBigInteger
andInt32
is treated like all other unmanaged .NET types (e.g.Int64
).int
type isBigInteger
but instances of bothBigInteger
andInt32
are treated asint
for performance reasons (this proposal).int
type staysInt32
but instances of bothBigInteger
andInt32
are treated asint
for performance reasons.BigInteger
andInt32
but both namedint
and further indistinguishable from the Python level.After going back and forth between them, I am happy to conclude that the chosen scenario seems to be the best choice. Each of the scenarios has some strengths and weaknesses, but scenario 2 seems like the best compromise.
For instance, scenario 1 is the most clean and logical, but gives in some performance and is not so convenient for interop, since lots of .NET API return just
Int32
and notBigInteger
.Scenario 3 comes close and has the advantage that the existing IronPython 2.x code would be easier to migrate to 3.x, but is difficult to keep free of surprises, mostly because not all of
int
instances would fit in objects/collections/generic methods strongly typed forint
. I wouldn't even try to explain it to the people.Scenario 4 becomes convoluted to get full compatibility with CPython.
This leaves scenario 2 which is fairly clean, straightforward and (mostly) free of surprises. For the migration, one must remember that Python 2.x
long
is renamed toint
in Python 3.x, and Python 2.xint
is "renamed" toSystem.Int32
. In practice it seems that the only times that the developer has to pay attention to that distinction is when dealing with generics, since usingint
will implyBigInteger
rather thanInt32
.There is one regression: instances of subclasses of
int
behave differently during overloaded method resolution. The root cause is not so much this PR as the fact that the method resolution with arguments of typeBigInteger
andInt32
works differently. I think something is wrong here. For instance, given argumentBigInteger(-200)
, and a method group with overloads taking eitherInt32
orByte
, why would the resolver choose the latter one and then choke on it with an overflow exception? The tests for those cases are intest_methodbinder2
, which, according to the code comment, are originally algorithmically generated, but maybe never got properly reviewed and simply froze incorrect behaviour. In any case, this is one of the loose ends I want to investigate in a separate PR, so for now themyint
tests in that test module are commented out.Incidentally, this PR also resolves #894.