Yep, it really is a long-standing bug. Script-mode top-level locals are treated 
as globals (module-scope bindings) by the compiler, but their initial bindings 
are evaluated eagerly instead of lazily (as you’d want in a script). Taken 
together, this means that you can get this completely unsafe behavior.

So, why is ‘a’ accepted but ‘b’ not in your original example?

> func foo() -> Int { return b }
> let a = 1
> let b = 2
> print(foo())

The secret to the current behavior is that script mode is executed 
interactively, instead of parsing it all up front. To make things a little 
better, it actually parses any number of declarations until it sees something 
it actually needs to execute—a statement or a declaration with an initial value 
expression. This allows for recursive functions while still being “live”.

The consequence here is that one top-level binding after a series of functions 
may be visible. This is obviously not optimal.

To fix this, we should:

- Distinguish between script-mode top-level locals and module-scope variables 
that happen to be declared. My personal preference is to treat anything with 
explicit access control as a normal lazy global and anything without access as 
a top-level local.

- Consider parsing everything up front, even if we don’t type-check it, so that 
we can say “use of ‘b’ before it’s initialized” instead of “undeclared name ‘b’”

Note that we do need to be conservative here. This code should continue to be 
rejected, even though ‘f’ doesn’t refer to ‘local’ directly, because calling 
‘f' would be dangerous before the initialization of ‘local':

internal func f() -> Int {
  return g()
}
// more code here

let local = 42
private func g() -> Int {
  return local
}

Thanks for bringing this up, if only so I have an opportunity to write out the 
issue. :-)
Jordan


> On Sep 21, 2016, at 23:04, Jens Persson <j...@bitcycle.com> wrote:
> 
> Did you see the other code examples that came up in that twitter 
> conversations?
> For example:
> 
> This worrying little program compiles:
> func f() -> Int {
>     return a
> }
> let a = f()
> 
> 
> It also compiles if you print(a) at the end, and it will print 0.
> 
> If we replace Int with [Int] it will still compile but crash when run.
> 
> And also this:
> 
> AnotherFile.swift containing:
> func f() -> Int {
>     return a
> }
> let a = f()
> 
> main.swift containing
> print(a)
> 
> Compile, run (for eternity, at 0% CPU).
> 
> /Jens
> 
> 
> On Thu, Sep 22, 2016 at 3:13 AM, Joe Groff <jgr...@apple.com 
> <mailto:jgr...@apple.com>> wrote:
> 
> > On Sep 21, 2016, at 2:22 PM, Jens Persson via swift-users 
> > <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
> >
> > // This little Swift program compiles (and runs) fine:
> >
> > func foo() -> Int { return a }
> > let a = 1
> > let b = 2
> > print(foo())
> >
> > But if `foo()` returns `b` instead of `a`, I get this compile time error:
> > "Use of unresolved identifier `b`"
> 
> This looks like a bug to me (cc-ing Jordan, who's thought about global 
> scoping issues more than me). In "script mode", it shouldn't be possible to 
> refer to a variable before its initialization is executed. However, the way 
> this is currently modeled is…problematic, to say the least, among other 
> reasons because script globals are still visible to "library" files in the 
> same module.
> 
> -Joe
> 

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Reply via email to