Skip to content
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

Type inference breaks inheritance #1119

Open
jonaskeller opened this issue Aug 1, 2018 · 5 comments
Open

Type inference breaks inheritance #1119

jonaskeller opened this issue Aug 1, 2018 · 5 comments

Comments

@jonaskeller
Copy link

Using artiq 3.6 py_42+git3e7cdaa5 on Windows 7 64bit

Mixing instances of a class and its descendant upsets the compiler's type inference. My naive notion is that it's always safe to assume that a descendant provides all the capabilities of its parent (of course, the opposite isn't true, and I wouldn't be surprised if things were more complicated altogether here).

Is this behavior intentional? And if so, can you recommend an elegant way around it?

For example, this

from artiq.experiment import *

class classA():
    def __init__(self):
        self.property = 42
        
class classB(classA):
    pass

class InheritanceIssue(EnvExperiment):
    """ Inheritance issue
    """
    def build(self):
        self.setattr_device("core")
        self.objects = [classA(), classB()]  # doesn't work
        # self.objects = [classB(), classB()]  # works
        
    @kernel
    def run(self):
        print(self.objects[0].property)

produces

root:While compiling <repository>\tests\inheritance_issue.py
<synthesized>:1:2-1:104: error: cannot unify <instance artiq_worker_<repository>\tests\inheritance_issue.classA> with <instance artiq_worker_<repository>\tests\inheritance_issue.classB>
[`<artiq_worker_<repository>\tests\inheritance_issue.classA object at 0x0000000004933048>`, `<artiq_worker_<repository>\tests\inheritance_issue.classB object at 0x0000000004933080>`]
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
<repository>\tests\inheritance_issue.py:21:1: note: expanded from here
def run(self):
^             
<synthesized>:1:2-1:104: note: a list element of type <instance artiq_worker_<repository>\tests\inheritance_issue.classA {
		__objectid__: numpy.int32
	}>
[`<artiq_worker_<repository>\tests\inheritance_issue.classA object at 0x0000000004933048>`, `<artiq_worker_<repository>\tests\inheritance_issue.classB object at 0x0000000004933080>`]
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                                                                         
<synthesized>:1:106-1:208: note: a list element of type <instance artiq_worker_<repository>\tests\inheritance_issue.classB {
		__objectid__: numpy.int32
	}>
[`<artiq_worker_<repository>\tests\inheritance_issue.classA object at 0x0000000004933048>`, `<artiq_worker_<repository>\tests\inheritance_issue.classB object at 0x0000000004933080>`]
																										 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
<repository>\tests\inheritance_issue.py:22:15-22:27: note: while inferring a type for an attribute 'objects' of a host object
		print(self.objects[0].property)
			  ^^^^^^^^^^^^             

In case the background matters: I'm using a class derived from SPIMaster that should be interchangeable with its parent (i.e. experiment code is agnostic to the type of SPI port a device is connected to). This works well as long as all devices of the same type use either one or the other. However, as soon as I have devices of the same type assigning different ones to their .bus member variable, I can't put them in lists to iterate over, since the compiler doesn't recognize them as the same type anymore.

@whitequark
Copy link
Contributor

Currently, inheritance in the ARTIQ compiler is only supported for code reuse, not for polymorphism. This means that you cannot make an array of objects of different types, even if one inherits from another.

@whitequark
Copy link
Contributor

The primary reason here is that inserting automatic upcast coercions results in surpising behavior when combined with global type inference. This is the same reason you have to explicitly cast e.g. a float argument to int when passing it to a function that accepts an int. There is no way to fix this other than mandatory type annotations on every function; it's a fundamental limitation of type inference. You'll notice that languages that feature both subtyping and type inference either make all subtyping explicit (OCaml) or require type annotations on all toplevel functions (Rust).

@jonaskeller
Copy link
Author

Thanks for the explanation. So is there a way to tell the compiler explicitly that upcasting is ok for, e.g. the items in a list?

@whitequark
Copy link
Contributor

So is there a way to tell the compiler explicitly that upcasting is ok for, e.g. the items in a list?

Currently not. The reason this isn't implemented is because as of 3.5, Python didn't have a typecast operator. In Python 3.6 there is the var: type = value, but it's not currently implemented in the compiler or in the parser.

@sbourdeauducq
Copy link
Member

Python 3.6 is also not supported by the packaging (#652).

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

No branches or pull requests

3 participants