Note to myself or whoever tackles this: the challenge is pretty much entirely in finding the line number. Adding it to the message will be trivial, but first we have to dig it up. The only thing I can think of off the top of my head is to dive through the Thread.currentThread().getStackTrace() until you find an element with the file name WorkflowScript. That, however, wouldn't work for such errors in shared libraries (we wouldn't actually be hosed if we renamed WorkflowScript to something else, well, not so long as we updated it here at the same time as in CpsFlowExecution)...perhaps we could figure out the specific stacks that could come after the CPS transformed line (i.e., in the linked case, at com.cloudbees.groovy.cps.sandbox.SandboxInvoker.methodCall(SandboxInvoker.java:17), though we'd have something else for non-sandboxed runs) but ehhhh. Yeah.
Shame we can't report this at compile time, but we can't easily tell at that point whether foo() is actually a method defined in the Jenkinsfile, a shared library foo.groovy, or some other dynamic magic. I'll see if I can think of all the possible things it could be - if we can be 100% sure we've got all the possible legit cases for foo(), we could then theoretically throw a compile error if none of them were satisfied.
The line number is there - look for at WorkflowScript.run(WorkflowScript:21) - it's just a pain to find. Yeah, a better message would be good.