microbit-foundation / python-editor-v3

Micro:bit Educational Foundation Python Editor V3
https://python.microbit.org
MIT License
54 stars 36 forks source link

Syntax checker is confused by except: without return #1136

Closed martinwork closed 9 months ago

martinwork commented 9 months ago

Bug Description

Arising from support ticket https://support.microbit.org/helpdesk/tickets/68245 (private)

Syntax checker is confused by except: without return

How To Reproduce

Paste this example into the editor, and notice message image

​from microbit import *

def notworking():
    return 0

def fnex():
    try:
        return 1
    except:
        notworking()

def fnreturn():
    try:
        return 1
    except:
        notworking()
        return 0

vx = fnex()
display.scroll(vx)

vr = fnreturn()
display.scroll(vr)
microbit-matt-hillsdon commented 9 months ago

I think this is behaving as expected.

In the example above, the try block in fnex can't raise. However, the syntax checker assumes if you write a try..except then the except case is possible under some circumstances.

In that circumstance the function will return None (by default, at the end of the function block).

So the possible return types are a number or None.

None isn't valid to pass to display.scroll so that case doesn't type check.

So this is warning the user of an error scenario up front where the try block raises, the function returns None and display.scroll gives an error because None is not a valid argument.

In practice it might be a scenario that doesn't happen often (though in that case why write try...except) or happens rarely.

It's interesting to get these reports because enabling the degree of type checking that we have is definitely an opinionated approach with some big upsides but also risk of confusion in some scenarios.

martinwork commented 9 months ago

Thanks @microbit-matt-hillsdon! That makes sense. As often happens, the error is only a clue to the cause. Being uncomfortable about dropping off the end of a function that returned a value led me to the fix of adding a return 0!

The actual example that I simplified did have a reason for except: and wouldn't return from notworking() because it called reset(). It's understandable that the syntax checker doesn't know that.

Not thinking about the exception case from the syntax checker's point of view led to confusion that the runtime argument type was always int (and would always be), while the syntax error was about text, which feels like a type rather than the name of the parameter.

Perhaps it would be a better clue if the message said "Possible argument type..." or even named the problematic argument type. e.g. Possible argument type "NoneType" does not match any parameter type for parameter "text"

The example below demonstrates the NoneType return.

from microbit import *

def notworking():
    return 0

def fnex():
    try:
        if ( button_a.is_pressed()):
            raise Exception('exception')
        return 1
    except:
        notworking()

while True:
    vx = fnex()
    print(type(vx))
    print(vx)
    display.scroll(vx)