I’m not always keen on Liskov, because he seems to limit what you can do with inheritance based on behavior and not on “essence”. In my opinion, inheritance has always been intended as an “is” relationship, rather than “acting exactly the same”.
Having said that the wikipedia article goes into detail about why some people think this is bad using your exact example:
A typical example that violates the LSP is the Square class, which comes from the Rectangle class, assuming that the getter and setter methods exist both in width and in height.
A square class always assumes that the width is equal to the height. If the Square object is used in the context where the Rectangle is expected, unexpected behavior may occur, since the size of the square cannot (or rather should not) be changed independently.
This problem cannot be easily fixed: if we can modify the setter methods in the Square class to keep the square invariant (i.e. keep the dimensions equal), then these methods weakened (violated) the postconditions for the rectangle that claim that the sizes can be changed independently.
So, looking at your code along with the equivalent Rectangle code:
s = Square.new(100) r = Rectangle.new(100,100) s.width = 50 r.width = 50 puts s.height puts r.height
the output will be 50 on the left and 100 on the right.
But, this is an important bit from the article, in my opinion:
Violations of an LSP, like this one, may or may not be in practice, depending on the postconditions or invariants that are actually expected from code that uses classes that violate the LSP.
In other words, if code using classes understands behavior, there is no problem.
The bottom line, the square is the correct subset of the rectangle, for the free definition of the rectangle :-)