Inconsistent behavior in handling shadowed functions

I notice some inconsistent behavior with how shadowed functions are handled. Is this intended behavior or is it a bug?

Consider this sequence and try to guess what it will do before trying it out:

axis = 10
clear axis
axis equal

If you type it at the Octave command prompt, it calls the function axis in command form:

octave:1> axis = 10
axis = 10
octave:2> clear axis
octave:3> axis equal
octave:4> # Here it creates a figure window with an empty plot

Now create a file called tmp.m and save the same three lines in it.

$ cat tmp.m 
axis = 10
clear axis
axis equal

Invoke it from inside Octave and it causes an error:

octave:1> tmp
error: parse error near line 3 of file /tmp/tmp.m

  axis: invalid use of symbol as both variable and command

>>> axis equal

Pass it as an argument to Octave and the same error happens:

$ octave tmp.m
error: parse error near line 3 of file /tmp/tmp.m

  axis: invalid use of symbol as both variable and command

>>> axis equal

Call Octave with redirected input and it works again:

$ octave <tmp.m
axis = 10

## An empty plot window briefly flashes here before exiting

I do not have a preference for whether that code should run or cause an error, but if it works in the REPL and then fails in a script file, it can make debugging of scripts very difficult, so having the same behavior in both environments would be preferable. Thoughts?

What does Matlab do for the same code?

I don’t have access to Matlab myself so I’ll wait for someone who does.

With Matlab R2022a, it behaves the same as in Octave when run from the command line.
When I put that code into a script, the linter that is integrated into their editor complains with: “Line 3: The current use of ‘axis’ is inconsistent with its previous use or definition (line 1).”
And the symbol that is supposed to indicate the “code quality” is red. That symbol is green when “everything is fine”. It’s orange if there are “suggestions” (like silencing output from an assignment with a semicolon). And usually it’s only red for a syntax error (e.g., if there are too many or too few end keywords or the number of opening and closing brackets don’t match somewhere).

However, when running the script, it still opens a figure with empty axes without any further error or warning.

So, it looks like they discourage that pattern. But they support it.

What happens if you use a function instead of a script?

function foo ()
  axis = 10
  clear axis
  axis equal

Is there no warning or error about using axis as a variable and later as a function?

I seem to recall that we discussed something like this the last time I worked on bug related to command-style function parsing.

Maybe the difference is that now there is no ambiguity detected here because Matlab is always parsing the axis equal statement as a function call and then only searching for functions instead of doing “normal” symbol resolution that first checks for variables, then functions?

In the context of a function Matlab issues an error:

>> test_shadow_clear
Error: File: test_shadow_clear.m Line: 4 Column: 3
Using identifier 'axis' as both a variable and a command is not supported. For more information, see "How MATLAB Recognizes Command Syntax".

That is kind of inconsistent imho.
But maybe scripts are treated more like command line input (where recovering from shadowing a function needs to be possible).

Octave parses an entire script before executing any of it, so in that sense it is like a function except that it can’t accept any arguments and it executes in the same scope/context of the calling function (or script).

At the command line, Octave executes code a single line (or statement, if it spans multiple input lines) at a time. So by the time it parses the axis equal statement, there is no axis variable because it has already been cleared.

When input comes from redirection, Octave doesn’t know that there is a script file containing the statements. It is just reading the standard input stream one line at a time, similar to command line input.

Also note that these are different:

$ octave foo.m    ## command line file execution, parse entire script file and then execute
$ octave < foo.m  ## redirection, parse and execute each line (or statement) sequentially

EDIT: I should also add that with redirection, Octave doesn’t actually know that redirection has happened. That is set up by the OS / command interpreter. Octave only knows that it is reading commands from some input stream that was not named as a file in the list of arguments passed to Octave.

If one considers the difference between input from the command line (or redirection) vs. a script vs. a function as an inconsistency, both Octave and Matlab act inconsistent (though in a different way). Since Matlab also (partly) treads that code as incorrect in a script (linter error), why not go the whole way?
I’m not sure if we need to follow Matlab exactly here. Imho, it’d be ok to keep Octave as it is in this respect. (And I wouldn’t be surprised if Matlab went the same way at some point in the future…)

Retaining current behavior is OK. The one place it can surprise the user is during early stage / exploratory coding: typically I incrementally build a statement at the REPL like clear; statement1; statement2; statement3; whos and keep appending new statements to the end of the line after examining variables. Once everything is running correctly, I save the long line with 10 statements as the initial version of a script file and add line breaks. The Octave behavior would cause an error at that point, but since the error message is clear, it should be possible for the user to change variable names and fix the script.