Thanks for the discussion so far. It seems to me some feel quite strongly about (a?.b).c. I took some time to reflect on what I’ve written yesterday on why limiting the scope of short-circuiting should should be rejected.
One of my arguments against it was that type checkers should need to warn about accessing .c on an optional value again. On closer inspection this could also be an argument in favor though. type checkers and IDEs will warn you if it’s used incorrectly. Do I still believe it should almost always be used with a fallback? Yes! However, we’re all consenting adults here. If someone really once to use that pattern without fallback, why not let them? Of course, if it can be prevented on a language level that’s great but if not, linters could always emit a warning for it.
Thinking one step further, they might also be able to change (a?.b.c)?.d to just a?.b.c.d if they recognize it’s a safe transformation.
Where does that leave us? The whole idea for none-aware access operators is that it’s basically a transformation which is applied to an expression. a?.b is equivalent to
_t.b if ((_t := a) is not None) else None
where a and b can be replaced with any other “arbitrary” expression. It might get complicated to read for two or more ?. due to the left-recursive nature but it should still be possible. Applying the same logic to (a?.b).c just replacing the inner group part would then be equivalent to
(_t.b if ((_t := a) is not None) else None).c
which would indeed raise an AttributeError if a = None thus the short-circuiting would not escape the group itself. I believe that’s what all you guys were trying to tell me before.
That just leave the question how to implement it but as @gcewing rightly called out the implementation should not drive the design. As a maintainer of pylint and contributor to mypy I frequently work with the AST generated by Python, so I feel strongly that any change we make to existing nodes should be as limited as possible. Thankfully, there might be a solution which we haven’t discussed so far. We could add a new group attribute (maybe someone will come up with a better name) to the Call, Attribute, Subscript, NoneAwareAttribute and NoneAwareSubscript nodes which will be set to 1 if it’s the topmost node in a group. We can then use the attribute to start a new block with its own jump target during bytecode generation.
To summarize, I’ll be updating my draft again to point out that short-circuiting does not escape a group.