ironpython3 icon indicating copy to clipboard operation
ironpython3 copied to clipboard

Multiple targets could match in write function of io.BytesIO subclass

Open slozier opened this issue 4 years ago • 6 comments

For example:

import io

class Test(io.BytesIO): pass

x = Test()
x.write(b"")

results in a

Multiple targets could match: write(object), write(IBufferProtocol)

I'm guessing it's because the object overload is an override of a virtual? @BCSharp any idea?

Comes from test_gzip (see https://github.com/IronLanguages/ironpython3/pull/1071).


Also affects truncate and readinto (and probably other io type/method combinations):

import io

class Test(io.BytesIO): pass

x = Test()
x.truncate(0)
x.readinto(bytearray())

slozier avatar Dec 02 '20 23:12 slozier

Does making the IBufferProtocol overload virtual too make the error go away? I can't test it, am AFK for a while. If it does, then a possible explanation is the ambiguity between

write(Test self, object data)
write(BytesIO self, IBufferProtocol data)

(Actually, this is the only ambiguity I can come up with.)

The DLR prefers an overload with a derived type over a base type, so argument self makes the first overload preferred, while argument data favours the second. Hence the ambiguity.

BCSharp avatar Dec 03 '20 05:12 BCSharp

Yep, making it virtual makes the error go away. I stepped through the code and it looks like the two candidates are exactly as you suggest:

{MethodCandidate(System.Numerics.BigInteger write(IronPython.Runtime.CodeContext, System.Object) on IronPython.NewTypes.IronPython.Modules.BytesIO_10$10)} {MethodCandidate(System.Numerics.BigInteger write(IronPython.Runtime.CodeContext, IronPython.Runtime.IBufferProtocol) on IronPython.Modules.PythonIOModule+BytesIO)}

Do you think the overload resolution should be able to figure out that the method was only overridden in ByteIO? Or is this actually what's expected for virtual methods?

It looks like truncate and readinto (and probably other overloads) have this issue...

Possible solutions:

  1. Change the overload resolution.
  2. Make the overloads virtual.
  3. Make the overloads internal and let everything go through the public virtual overload.

slozier avatar Dec 03 '20 15:12 slozier

Made the write overload virtual in https://github.com/IronLanguages/ironpython3/pull/1071 to get the test passing. Should revisit once we decide how to resolve this.

slozier avatar Dec 04 '20 01:12 slozier

Do you think the overload resolution should be able to figure out that the method was only overridden in ByteIO? Or is this actually what's expected for virtual methods?

Frankly, I don't know. My intuition tells me that the overload resolution should be able to figure it out, but I don't know what the original intention of the DLR authors was, or whether there would be some unintended consequences. Current handling of the implicit self by the DLR in general seems to me somewhat hackish, so maybe it would be good to give it some extra thought and a little redesign at some point.

For now I think it is fortunate that a simple fix makes it work, seemingly without adverse effects.

BCSharp avatar Dec 04 '20 18:12 BCSharp

Ran into this issue again (this time due to readinto) in test_urllib.

slozier avatar Apr 11 '21 18:04 slozier

While working on #1456, I've found tests in test_methodbinder2.py that test exactly this case: MethodBinder2Test.test_arg_Derived_Number. From those tests it appears that the intention was to report such cases as ambiguous. The only non-ambiguous upcasting case is when the argument matches the base class parameter exactly.

BCSharp avatar May 15 '22 21:05 BCSharp