Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Constructor failing argument matching, silently selects default constructor #238

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

Closed
sigbjorn opened this issue Jul 3, 2016 · 9 comments · Fixed by #1651
Closed

Constructor failing argument matching, silently selects default constructor #238

sigbjorn opened this issue Jul 3, 2016 · 9 comments · Fixed by #1651
Labels
Milestone

Comments

@sigbjorn
Copy link

sigbjorn commented Jul 3, 2016

(Version 2.1)

Given .NET class:

      public class A {

           public A() { a=1.0;}

           public A(double ax) { a=ax;}

           public double a;

     }

then in python:

from Xxx import A

a1 = A(2)   # surprise! default constructor is selected, silently, basically because 
            # there is no int->double type promotion in constructor binder
            # causing first bind to fail, then second bind with no args (that was a fix
            # to allow .NET objects to be super-classes (or be sub-classed) from
            # python succeeds..
a2 = A(2.0) # Ok, now we get the second constructor, because it's a perfect match.

I am fully aware that super-classes, argument-matching, (automatic) type-conversion,
and bridging the gap between python and .NET (C#) , is very complex.
And providing a full implementation that 'just do the thing we want' is not trivial.

It might be that some of the issues related to sub-classing, as well as argument matching/conversion is related, - but I hope the simple example above illustrates that
some minor improvement at this stage could be quite useful.

There is a few fixes, that could improve this slightly:

(a.) Make simple type-promotion/argument conversion happen during constructor argument matching
(b.) If construction of a .NET object is attempted, and argument match fail, then always raise Type error

I will provide a PR for (a.) since it's a simple fix to verify a simple type-promotion from int->double etc.

Fixing (b.), raise error if no .NET constructor matches the arguments is easy, but will then break the sub-class mechanism. If we knew, by the time we are calling the .NET constructor, that it's a super-class, then we could either have a relaxed matching (as is to day, default to the default-ct, if fail), and then insist on match for all other cases.

After all, providing something that fixes (b.), and at the same time supports, and possibly extends super()/sub-classing mechanism seems harder to me, since we need to know the context in which the .NET object is constructed.

If anyone see a easy solution to (b.) , - as in knowing how to get the context of the .NET class construction, that would be great.

We could then provide a fix where

' you succeed with your intention creating a new object with the supplied parameters, and get the expected result, or we raise Type-exception notifying about the problem'

@den-run-ai
Copy link
Contributor

den-run-ai commented Jul 4, 2016

@sigbjorn here is really where bad logic starts and where it should have failed:

https://github.com/pythonnet/pythonnet/blob/master/src/runtime/constructorbinder.cs#L84

if (binding == null)
            {
                // It is possible for __new__ to be invoked on construction
                // of a Python subclass of a managed class, so args may
                // reflect more args than are required to instantiate the
                // class. So if we cant find a ctor that matches, we'll see
                // if there is a default constructor and, if so, assume that
                // any extra args are intended for the subclass' __init__.

                IntPtr eargs = Runtime.PyTuple_New(0);
                binding = this.Bind(inst, eargs, kw);
                Runtime.Decref(eargs);

                if (binding == null)
                {
                    Exceptions.SetError(Exceptions.TypeError,
                        "no constructor matches given arguments"
                        );
                    return null;
                }
            }

@den-run-ai
Copy link
Contributor

I'm big minus one on auto-conversion from integer to double or vice-verse. The class could have contained constructors for both types.

@filmor
Copy link
Member

filmor commented Jul 4, 2016

@denfromufa Let's move the discussion on part a) to the pull-request #239.

Regarding part b), I'd rather have us fixing this one. I'll check what breaks if we remove the respective section.

@sigbjorn
Copy link
Author

sigbjorn commented Jul 4, 2016

I think that If we remove that section, then sub-classing a .NET class (with arguments) stops working. As indicated by the comment, and some test covering it, it plays a role when you use a .NET class as super class.

I am not sure that it's fully working now either, because, if you explicitely pass arguments to the super-class, It should be possible to select precise constructors by just beeing very explicit (not bad).

@sigbjorn
Copy link
Author

sigbjorn commented Jul 4, 2016

Promoting from int->double etc. is standard, even in some strict languages.
Here we are in the middle, python and .NET, pythonnet, so I think it's reasonable, and possibly also consistent with how mapping of functions and methods are done. ( I might be wrong, but we could write a test f(double).. and call f(3000) and see what happens)

You got a point that the class could have A(int), A(double) etc, and then we of-course would like the pythonnet to 'do the right thing'.

On the other hand, as it is now, it is silently selecting the default contstructor (if such one exists),
ignoring the userspecified arguments.

I think thats is in the order of magnitude a bigger problem, so if we could get a pedandic version that just forces the user to be explicit about types, this is all fine for me, and most other I would think -because then the user is at least notified.

@koliyo
Copy link

koliyo commented Nov 15, 2019

Ouch, this was not expected. And definitely a huge risk of bugs with the silent default constructor calling.

Not allowing inherited constructors seem much less problematic.

Additionally, I don't fully understand the problem of checking base class constructors if there already is logic to check the current class constructors (?)

I understand that doing automatic type coercion matching can be tricky, but better to just throw an error which describes the received parameter list types and it should be relatively easy to spot the error.

Eg

Vec3(1,2,3)
=> Error: No matching constructor for `Vec3(int, int, int)`

would be a lot better than getting a default value imo

@lostmsu
Copy link
Member

lostmsu commented Nov 15, 2019

Oh wow, did not know about this problem. @filmor have you fiddled with it yet?

@lostmsu lostmsu added this to the 3.0.0 milestone Sep 23, 2021
@lostmsu
Copy link
Member

lostmsu commented Sep 23, 2021

I believe this is already fixed in 3.0, but we need a test.

lostmsu added a commit to losttech/pythonnet that referenced this issue Dec 29, 2021
was: tp_new implementation would call .NET constructor and return a fully constructed object

now:
Except for some special .NET types tp_new creates uninitialized .NET object, which is later initialized by calling __init__.

__init__ is set using type dictionary to a MethodObject, that contains ConstructorInfo[] instead of MethodInfo[]

This allows Python to:
1) freely override __init__
2) when deriving from .NET types call base __init__ (e.g. .NET constructor), and choose the overload as needed

fixes pythonnet#238
lostmsu added a commit to losttech/pythonnet that referenced this issue Dec 30, 2021
was: tp_new implementation would call .NET constructor and return a fully constructed object

now:
Except for some special .NET types tp_new creates uninitialized .NET object, which is later initialized by calling __init__.

__init__ is set using type dictionary to a MethodObject, that contains ConstructorInfo[] instead of MethodInfo[]

This allows Python to:
1) freely override __init__
2) when deriving from .NET types call base __init__ (e.g. .NET constructor), and choose the overload as needed

fixes pythonnet#238
@lostmsu
Copy link
Member

lostmsu commented Dec 30, 2021

It was not fixed, but the fix PR is there )

lostmsu added a commit to losttech/pythonnet that referenced this issue Jan 4, 2022
was: tp_new implementation would call .NET constructor and return a fully constructed object

now:
Except for some special .NET types tp_new creates uninitialized .NET object, which is later initialized by calling __init__.

__init__ is set using type dictionary to a MethodObject, that contains ConstructorInfo[] instead of MethodInfo[]

This allows Python to:
1) freely override __init__
2) when deriving from .NET types call base __init__ (e.g. .NET constructor), and choose the overload as needed

fixes pythonnet#238
lostmsu added a commit to losttech/pythonnet that referenced this issue Jan 4, 2022
was: tp_new implementation would call .NET constructor and return a fully constructed object

now:
Except for some special .NET types tp_new creates uninitialized .NET object, which is later initialized by calling __init__.

__init__ is set using type dictionary to a MethodObject, that contains ConstructorInfo[] instead of MethodInfo[]

This allows Python to:
1) freely override __init__
2) when deriving from .NET types call base __init__ (e.g. .NET constructor), and choose the overload as needed

fixes pythonnet#238
lostmsu added a commit that referenced this issue Jan 4, 2022
was: tp_new implementation would call .NET constructor and return a fully constructed object

now:
Except for some special .NET types tp_new creates uninitialized .NET object, which is later initialized by calling __init__.

__init__ is set using type dictionary to a MethodObject, that contains ConstructorInfo[] instead of MethodInfo[]

This allows Python to:
1) freely override __init__
2) when deriving from .NET types call base __init__ (e.g. .NET constructor), and choose the overload as needed

fixes #238
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants