Why Isn't Swift Recognizing a Child Class is a Member of a Parent Class?

I have a class Node which has a bunch of child classes, BinaryNode is one of them. In a function I pass a node object as an argument, with intent of checking which child of the node class it is and performing operations based on which one it is.

It is throwing errors that Value of type 'Node' has no member '_____' despite those values being a part of the subclass already selected by an if statement. Its best to just see the code directly.

I'm guessing the error has something to do with the order of passing a parent class object in and expecting swift to understand that it might be a subclass too, any feedback is appreciated!

Class Declaration:

public class Node {
    var spec: String
    var val: String
    
    init(spec: String, val: String) {
        self.spec = spec
        self.val = val
    }
}

// required to differentiate between Nodes
extension Node: Equatable {
    public static func ==(lhs: Node, rhs: Node) -> Bool {
        return lhs.spec == rhs.spec && lhs.val == rhs.val
    }
    
}


class UnaryNode: Node {

    var child: Node
    
    init(spec: String, val: String, child: Node) {
        self.child = child
        super.init(spec: spec, val: val)
    }
}


class BinaryNode: Node {

    var leftChild: Node
    var rightChild: Node
    
    init(spec: String, val: String, lChild: Node, rChild: Node) {
        self.leftChild = lChild
        self.rightChild = rChild
        super.init(spec: spec, val: val)
    }
}

And the part with the error:

public func printFunction(root: Node?) {
        if let node = root {
            
            if node is BinaryNode {
                let left = node.leftChild
                    //Value of type 'Node' has no member 'leftChild'
                let right = node.rightChild
                    //Value of type 'Node' has no member 'rightChild'

                //...

            } else if node is UnaryNode {
                let child = node.child
                    //Value of type 'Node' has no member 'child'

                //...
        }
    }
Answered by DTS Engineer in 730761022

You might need public on those declarations, but that's not the cause of this particular error. You have code like this:

            if node is BinaryNode {
                let left = node.leftChild

The problem here is that although you've checked that the object referred to by node is a BinaryNode, the node variable itself is still of type Node, which doesn't have a leftChild. Instead, try writing your code like this:

            if let binaryNode = node as? BinaryNode {
                let left = binaryNode.leftChild

That creates a new variable of type BinaryNode which refers to the same object as node. However, it now has the correct type, so you can retrieve the leftChild.

If you prefer, you don't have to use a different name for the new variable. This is called "shadowing" a variable, and looks like this:

            if let node = node as? BinaryNode {
                let left = node.leftChild

There are actually two nodes here: the "outer" one of type Node, and the "inner" one of type BinaryNode. This is perfectly acceptable, idiomatic Swift. Some programmers find it confusing to have two variables with the same name. Other programmers find it clearer to use the same name for two variables referring to the same object. Chose whichever approach you prefer for your code.

Incidentally, the behavior you had in mind in your original question, where node automatically had type BinaryNode after the if test, is called "type narrowing". It's a feature that the Swift language currently does not have, although other languages do.

You need 'public' on those variable declarations

Accepted Answer

You might need public on those declarations, but that's not the cause of this particular error. You have code like this:

            if node is BinaryNode {
                let left = node.leftChild

The problem here is that although you've checked that the object referred to by node is a BinaryNode, the node variable itself is still of type Node, which doesn't have a leftChild. Instead, try writing your code like this:

            if let binaryNode = node as? BinaryNode {
                let left = binaryNode.leftChild

That creates a new variable of type BinaryNode which refers to the same object as node. However, it now has the correct type, so you can retrieve the leftChild.

If you prefer, you don't have to use a different name for the new variable. This is called "shadowing" a variable, and looks like this:

            if let node = node as? BinaryNode {
                let left = node.leftChild

There are actually two nodes here: the "outer" one of type Node, and the "inner" one of type BinaryNode. This is perfectly acceptable, idiomatic Swift. Some programmers find it confusing to have two variables with the same name. Other programmers find it clearer to use the same name for two variables referring to the same object. Chose whichever approach you prefer for your code.

Incidentally, the behavior you had in mind in your original question, where node automatically had type BinaryNode after the if test, is called "type narrowing". It's a feature that the Swift language currently does not have, although other languages do.

Why Isn't Swift Recognizing a Child Class is a Member of a Parent Class?
 
 
Q