/*
Copyright 2020 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package warn

import "testing"

func TestMissingReturnValueWarning(t *testing.T) {
	// empty return
	checkFindings(t, "return-value", `
def foo():
  if x:
    return x
  else:
    return
`, []string{
		`:5: Some but not all execution paths of "foo" return a value.`,
	}, scopeEverywhere)

	// empty return; implicit return in the end
	checkFindings(t, "return-value", `
def bar():
  if x:
    pass
  elif y:
    return y
  else:
    for z in t:
      return
`, []string{
		`:1: Some but not all execution paths of "bar" return a value.
The function may terminate by an implicit return in the end.`,
		`:8: Some but not all execution paths of "bar" return a value.`,
	}, scopeEverywhere)

	// implicit return in the end
	checkFindings(t, "return-value", `
def foo():
  if x:
    return x
  else:
    bar()
`, []string{
		`:1: Some but not all execution paths of "foo" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)

	// implicit return in the end
	checkFindings(t, "return-value", `
def bar():
  if x:
    return x
  elif y:
    return y
  else:
    foo
    if z:
      return z

  if foo:
     return not foo
`, []string{
		`:1: Some but not all execution paths of "bar" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)

	// only returned values and fail() statements, ok
	checkFindings(t, "return-value", `
def bar():
  if x:
    return x
  elif y:
    return y
  else:
    foo
    if z:
      return z

  if foo:
     return not foo
  else:
     fail("unreachable")
`, []string{}, scopeEverywhere)

	// implicit return in the end because fail() is not a statement
	checkFindings(t, "return-value", `
def bar():
  if x:
    return x
  elif y:
    return y
  else:
    foo
    if z:
      return z

  if foo:
     return not foo
  else:
     foo() or fail("unreachable")
`, []string{
		`:1: Some but not all execution paths of "bar" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)

	// only empty returns, ok
	checkFindings(t, "return-value", `
def bar():
  if x:
    x()
  elif y:
    return
  else:
    foo
    if z:
      fail()

  if foo:
     return
`, []string{}, scopeEverywhere)

	// no returns, ok
	checkFindings(t, "return-value", `
def foobar():
  pass
`, []string{}, scopeEverywhere)

	// only fails, ok
	checkFindings(t, "return-value", `
def foobar():
  if foo:
    fail()
`, []string{}, scopeEverywhere)

	// nested functions, no errors
	checkFindings(t, "return-value", `
def foo():
  def bar():
    pass

  return bar
`, []string{}, scopeEverywhere)

	// nested functions, missing return value in the outer function
	checkFindings(t, "return-value", `
def foo():
  def bar():
    pass

  if bar():
    return 42
`, []string{
		`:1: Some but not all execution paths of "foo" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)

	// nested functions, missing return value in the inner function
	checkFindings(t, "return-value", `
def foo():
  def bar():
    if something:
      return something

  if bar():
    return 42
  return 43
`, []string{
		`:2: Some but not all execution paths of "bar" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)

	// nested functions, missing return value in both
	checkFindings(t, "return-value", `
def foo():
  def bar():
    if something:
      return something

  if bar():
    return 42
`, []string{
		`:1: Some but not all execution paths of "foo" return a value.
The function may terminate by an implicit return in the end.`,
		`:2: Some but not all execution paths of "bar" return a value.
The function may terminate by an implicit return in the end.`,
	}, scopeEverywhere)
}

func TestUnreachableStatementWarning(t *testing.T) {
	// after return
	checkFindings(t, "unreachable", `
def foo():
  return
  bar()
  baz()
`, []string{
		`:3: The statement is unreachable.`,
	}, scopeEverywhere)

	// two returns
	checkFindings(t, "unreachable", `
def foo():
  return 1
  return 2
`, []string{
		`:3: The statement is unreachable.`,
	}, scopeEverywhere)

	// after fail()
	checkFindings(t, "unreachable", `
def foo():
  fail("die")
  bar()
  baz()
`, []string{
		`:3: The statement is unreachable.`,
	}, scopeEverywhere)

	// after break and continue
	checkFindings(t, "unreachable", `
def foo():
  for x in y:
    if x:
      break
      bar()  # unreachable
    if y:
      continue
      bar()  # unreachable

def bar():
  for x in y:
    if x:
      break
    elif y:
      continue
    else:
      return x

    foo()  # unreachable
  foobar()  # potentially reachable
`, []string{
		`:5: The statement is unreachable.`,
		`:8: The statement is unreachable.`,
		`:19: The statement is unreachable.`,
	}, scopeEverywhere)

	// ok
	checkFindings(t, "unreachable", `
def foo():
  if x:
    return
  bar()
`, []string{}, scopeEverywhere)

	// ok
	checkFindings(t, "unreachable", `
def foo():
  x() or fail("maybe")
  bar()
`, []string{}, scopeEverywhere)

	// unreacheable statement inside a nested function
	checkFindings(t, "unreachable", `
def foo():
  def bar():
    fail("die")
    baz()
`, []string{
		`:4: The statement is unreachable.`,
	}, scopeEverywhere)

}

func TestNoEffect(t *testing.T) {
	checkFindings(t, "no-effect", `
"""Docstring."""
def bar():
    """Other Docstring"""
    fct()
    pass
    return 2

[f() for i in rang(3)] # top-level comprehension is okay
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "no-effect", `
def foo():
    [fct() for i in range(3)]
	`,
		[]string{":2: Expression result is not used. Use a for-loop instead"},
		scopeEverywhere)

	checkFindings(t, "no-effect", `None`,
		[]string{":1: Expression result is not used."},
		scopeEverywhere)

	checkFindings(t, "no-effect", `
foo             # 1
foo()

def bar():
    [1, 2]      # 5
    if True:
      "string"  # 7
`,
		[]string{":1:", ":5:", ":7:"},
		scopeEverywhere)

	checkFindings(t, "no-effect", `
# A comment

"""A docstring"""

# Another comment

"""Not a docstring"""

def bar():
    """A docstring"""
    foo
    """ Not a docstring"""
    return foo
`,
		[]string{
			":7: Expression result is not used. Docstrings should be the first statements of a file or a function (they may follow comment lines).",
			":11: Expression result is not used.",
			":12: Expression result is not used. Docstrings should be the first statements of a file or a function (they may follow comment lines).",
		}, scopeEverywhere)

	checkFindings(t, "no-effect", `
foo == bar
foo = bar
a + b
c // d
-e
foo != bar

foo += bar
bar -= bar
bar *= bar
bar /= bar
bar //= bar
bar %= bar
bar &= bar
bar |= bar
bar ^= bar
bar <<= bar
bar >>= bar
`,
		[]string{":1:", ":3:", ":4:", ":5:", ":6:"},
		scopeEverywhere)

	checkFindings(t, "no-effect", `
def foo():
  """Doc."""
  def bar():
    """Doc."""
    foo == bar
`,
		[]string{":5:"},
		scopeEverywhere)
}

func TestWarnUnusedVariable(t *testing.T) {
	checkFindings(t, "unused-variable", `
load(":f.bzl", "x")
x = "unused"
y = "also unused"
z = "name"
t = "unused by design"  # @unused
_foo, _bar = pair  #@unused
cc_library(name = z)

def f():
  pass

def g():
  pass

g() + 3
`,
		[]string{":2: Variable \"x\" is unused.",
			":3: Variable \"y\" is unused.",
			":9: Function \"f\" is unused."},
		scopeDeclarative)

	checkFindings(t, "unused-variable", `
a = 1
b = 2
c = 3
d = (a if b else c)  # only d is unused
`,
		[]string{":4: Variable \"d\" is unused."},
		scopeDeclarative)

	checkFindings(t, "unused-variable", `
_a = 1
_a += 2
_b = 3
print(_b)

def _f(): pass
def _g(): pass
_g()
`,
		[]string{
			":1: Variable \"_a\" is unused.",
			":6: Function \"_f\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
a = 1

def foo(
    x,
    y = 0,
    z = 1):
  b = 2
  c = 3
  d = (a if b else c)  # only d is unused
  e = 7
  f = 8  # @unused
  # @unused
  g = 9

  return e + z

foo()
`,
		[]string{
			":4: Variable \"x\" is unused.",
			":5: Variable \"y\" is unused.",
			":9: Variable \"d\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
a = 1

def foo(a):
  b = 2
  return a

def foo():
  pass

def bar(c, cc):
  d = 3
  print(c)

  def baz():
    foo()
    d = 4
    return a

bar()
`,
		[]string{
			":4: Variable \"b\" is unused.",
			":10: Variable \"cc\" is unused.",
			":11: Variable \"d\" is unused.",
			":14: Function \"baz\" is unused.",
			":16: Variable \"d\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():
  a = 1
  b = 2
  c = 3

  def bar(
      x = a + baz(c = 4),
      y = b):
    pass

foo()
`,
		[]string{
			":4: Variable \"c\" is unused.",
			":6: Function \"bar\" is unused.",
			":7: Variable \"x\" is unused.",
			":8: Variable \"y\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():
  a = 1
  b = 2
  c = [x for a in aa if a % b for x in a]
  return c

foo()
`,
		[]string{
			":2: Variable \"a\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():
  a = 1
  b = 2
  c = [
    a + b
    for a in [b for b in bb if b]
    if a
  ]
  return c

foo()
`,
		[]string{
			":2: Variable \"a\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():
  a = 1
  b = 2

  def bar(*args):
    def baz(**kwargs):
      def foobar(*a,
                 **kw):
        return b
      return foobar(**kwargs)
    return baz(*args)
  return bar()

foo()
`,
		[]string{
			":2: Variable \"a\" is unused.",
			":7: Variable \"a\" is unused.",
			":8: Variable \"kw\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():
  a = 1
  b = 2
  c = 3
  d = 4
  e, f = 5, 6

  for x, yy in xx:
    for (y, z, _, _t) in yy:
      print(a + y)

  if bar:
    print(c)
  elif baz:
    print(d)
  else:
    print(e)

foo()
`,
		[]string{
			":3: Variable \"b\" is unused.",
			":6: Variable \"f\" is unused.",
			":8: Variable \"x\" is unused.",
			":9: Variable \"z\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo():

  # @unused
  def bar():
    pass

  def baz():
    pass

foo()
`,
		[]string{
			":7: Function \"baz\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo(my_iterable, arg, _some_unused_argument, _also_unused = None, *_args, **_kwargs):

  a, b, _c = 1, 2, 3  # ok to not use _c
  print(a)

  _d, _e = 4, 5  # all are underscored
  print(_d)

  for f, g, _h, _ in my_iterable:  # ok to not use any underscored
    print(f)

  for _i, (_j, _k) in another_iterable:  # ok to not use any of them
    pass

  [1 for (_y, _z) in bar]

foo()
`,
		[]string{
			":1: Variable \"arg\" is unused.",
			":3: Variable \"b\" is unused.",
			":6: Variable \"_e\" is unused.",
			":9: Variable \"g\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo(
    x,
    _y,
    z,  # @unused
    t = 42,  #@unused
    *args,  # @unused
    **kwargs,  ### also @unused
):
  pass

foo()
`,
		[]string{
			":2: Variable \"x\" is unused.",
		},
		scopeEverywhere)

	checkFindings(t, "unused-variable", `
def foo(
    name,
    x):
  pass


def bar(
    name = "",
    y = 3):
  pass


foo()
bar()
`,
		[]string{
			":3: Variable \"x\" is unused.",
			":9: Variable \"y\" is unused.",
		},
		scopeEverywhere)
}

func TestRedefinedVariable(t *testing.T) {
	checkFindings(t, "redefined-variable", `
x = "old_value"
x = "new_value"
x[1] = "new"
cc_library(name = x)`,
		[]string{":2: Variable \"x\" has already been defined."},
		scopeEverywhere)

	checkFindings(t, "redefined-variable", `
x = "a"

def foo():
  x = "b"
  y = "c"
  y = "d"

def bar():
  x = "e"
  y = "f"
  y = "g"`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "redefined-variable", `
x = [1, 2, 3]
y = [a for a in b]
z = list()
n = 43

x += something()
y += something()
z += something()
n += something()
x -= something()`,
		[]string{
			":9: Variable \"n\" has already been defined.",
			":10: Variable \"x\" has already been defined.",
		},
		scopeEverywhere)

	checkFindings(t, "redefined-variable", `
x = [1, 2, 3]
y = [a for a in b]
z = list()

a = something()
b = something()
c = something()
d = something()
e = something()

a += x
b += y
c += z
d += [42]
e += foo`,
		[]string{
			":15: Variable \"e\" has already been defined.",
		},
		scopeEverywhere)
}

func TestWarnUnusedLoad(t *testing.T) {
	checkFindingsAndFix(t, "load", `
load(":f.bzl", "s1", "s2")
load(":bar.bzl", "s1")
foo(name = s1)`, `
load(":f.bzl", "s1")
load(":bar.bzl", "s1")
foo(name = s1)`,
		[]string{
			":1: Loaded symbol \"s2\" is unused.",
			":2: A different symbol \"s1\" has already been loaded on line 1.",
		},
		scopeEverywhere)

	checkFindingsAndFix(t, "load", `
load("foo", "b", "a", "c")
load("foo", "a", "d", "e")

z = a + b + d`, `
load("foo", "a", "b")
load("foo", "d")

z = a + b + d`,
		[]string{
			":1: Loaded symbol \"c\" is unused.",
			":2: Symbol \"a\" has already been loaded on line 1.",
			":2: Loaded symbol \"e\" is unused.",
		},
		scopeEverywhere)

	checkFindingsAndFix(t, "load", `
load("foo", "a")
a(1)
load("bar", "a")
a(2)
load("bar", a = "a")
a(3)
load("bar", a = "b")
a(4)
load("foo", "a")
a(5)
load("foo", "a")
a(6)
load("foo", a = "a")
a(7)`, `
load("foo", "a")
a(1)
load("bar", "a")
a(2)

a(3)
load("bar", a = "b")
a(4)
load("foo", "a")
a(5)

a(6)

a(7)`,
		[]string{
			":3: A different symbol \"a\" has already been loaded on line 1.",
			":5: Symbol \"a\" has already been loaded on line 3.",
			":7: A different symbol \"a\" has already been loaded on line 5.",
			":9: A different symbol \"a\" has already been loaded on line 7.",
			":11: Symbol \"a\" has already been loaded on line 9.",
			":13: Symbol \"a\" has already been loaded on line 11.",
		},
		scopeEverywhere)

	checkFindingsAndFix(t, "load", `
load(
  ":f.bzl",
   "s1",
   "s2",  # @unused (s2)
)

# @unused - both s3 and s4
load(
  ":f.bzl",
   "s3",
   "s4",
)`, `
load(
  ":f.bzl",
   "s2",  # @unused (s2)
)

# @unused - both s3 and s4
load(
  ":f.bzl",
   "s3",
   "s4",
)`,
		[]string{":3: Loaded symbol \"s1\" is unused."},
		scopeEverywhere)

	checkFindingsAndFix(t, "load", `
load(":f.bzl", "x")
x = "unused"`, `
x = "unused"`,
		[]string{":1: Loaded symbol \"x\" is unused."},
		scopeEverywhere)

	checkFindings(t, "load", `
load(
  ":f.bzl",
   "s1",
)

def test(x: s1):
  pass
`,
		[]string{},
		scopeEverywhere)
	checkFindings(t, "load", `
load(
  ":f.bzl",
  "s1",
  "s2",
)

def test(x: s1) -> List[s2]:
  pass
`,
		[]string{},
		scopeEverywhere)
	checkFindingsAndFix(t, "load", `
load(
  ":f.bzl",
  "s1",
  "s2",
)

load(
  ":s.bzl",
  "s3",
)

def test(x: s1) -> List[s2]:
  pass
`, `
load(
  ":f.bzl",
  "s1",
  "s2",
)

def test(x: s1) -> List[s2]:
  pass
`,
		[]string{
			":9: Loaded symbol \"s3\" is unused.",
		},
		scopeEverywhere)
}

func TestUninitializedVariable(t *testing.T) {
	checkFindings(t, "uninitialized", `
def foo(x):
  if bar:
    x = 1
    y = 2

  bar = True
  print(x + y)
`,
		[]string{
			":2: Variable \"bar\" may not have been initialized.",
			":7: Variable \"y\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  for t in s:
    x = 1
    y = 2

  print(x + y)
`,
		[]string{
			":6: Variable \"y\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  if bar:
    x = 1
    y = 2
  else:
    if foobar:
      x = 3
      y = 4
    else:
      x = 5

  print(x + y)
`,
		[]string{
			":12: Variable \"y\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  if bar:
    t = 1
  else:
    for t in maybe_empty:  
      pass

  print(t)
`,
		[]string{
			":8: Variable \"t\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  if bar:
    t = 1
  else:
    for y in maybe_empty:  
      return

  print(t)
`,
		[]string{
			":8: Variable \"t\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  if bar:
    for t in [2, 3]:
      pass

  print(t)
`,
		[]string{
			":6: Variable \"t\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  print(t)  # maybe global or loaded
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  if bar:
    y = 1

  print(y)
  x, y = y, x
  print(y)
`,
		[]string{
			":5: Variable \"y\" may not have been initialized.",
			":6: Variable \"y\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  if a:
    x = 1
    y = 1
    z = 1
  elif b:
    x = 2
    z = 2
    t = 2
  else:
    x = 3
    y = 3
    t = 3

  print(x + y + z + t)
`,
		[]string{
			":15: Variable \"y\" may not have been initialized.",
			":15: Variable \"z\" may not have been initialized.",
			":15: Variable \"t\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(y):
  if y < 0:
    x = -1
  elif y > 0:
    x = 1
  else:
    fail()

  print(x)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(y):
  if y < 0:
    x = -1
  elif y > 0:
    x = 1
  else:
    if z:
      fail("z")
    else:
      fail("not z")

  print(x)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(y):
  if y < 0:
    return
  elif y > 0:
    x = 1
  else:
    return x  # not initialized

  print(x)
`,
		[]string{
			":7: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(y):
  if y < 0:
    return
  elif y > 0:
    x = 1
  else:
    pass

  print(x)  # not initialized
`,
		[]string{
			":9: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  for x in y:
    print(x)
    print(x.attr)

  print(x)
  print(x.attr)
`,
		[]string{
			":6: Variable \"x\" may not have been initialized.",
			":7: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  for x in y:
    a = x
    print(a)

  print(a)
`,
		[]string{
			":6: Variable \"a\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f():
    if foo:
        x = foo

    f(x = y)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f():
    if foo:
        x = foo

    f(x + y)
`,
		[]string{
			":5: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f():
    if foo:
        x = foo

    x, y = 1, x
`,
		[]string{
			":5: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f():
    if foo:
        x = foo

    x, y = 1, 2
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f(y):
    return [x for x in y if x]

    x = 1
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def f():
    if foo:
        f(x = foo)
    else:
        x = 3

    print(x)
`,
		[]string{
			":7: Variable \"x\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x):
  for y in x:
    if foo:
      break
    elif bar:
      continue
    elif baz:
      return
    else:
      z = 3
    print(z)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x: int, y: int = 2):
  if bar:
    x = 1
    y = 2
    z = 3

  print(x + y + z)
`,
		[]string{
			":7: Variable \"z\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x: int, y: int = 2):
  def bar(y=x):
    if baz:
      x = 1
      y = 2
      z = 3

    print(x + y + z)

  if something:
    x = bar()

  return x
`,
		[]string{
			":8: Variable \"x\" may not have been initialized.",
			":8: Variable \"z\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo(x: int, y: int = 2):
  if bar:
    y, z, t = 2, 3, 4
    w, s = 5, 6
    r = 7

  [t for t in range(5)]
  [a for a in range(z + y)]
  {b: c + s for b, c in [
    d * 2 for d in range(t)
    if d != baz(r=w)
  ]}
`,
		[]string{
			":8: Variable \"z\" may not have been initialized.",
			":9: Variable \"s\" may not have been initialized.",
			":10: Variable \"t\" may not have been initialized.",
			":11: Variable \"w\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  x = 1
  for y, z in t:
      print(y, z)

  def bar(x, y, s = z):
    pass
`,
		[]string{
			":6: Variable \"z\" may not have been initialized.",
		},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  for bar in baz:
    pass

  y = [baz.get(bar) for bar in bars]
  x = lambda bar: baz.get(bar)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  def bar(x):
    print(x)

  for x, y in z:
    bar(x)
`,
		[]string{},
		scopeEverywhere)

	checkFindings(t, "uninitialized", `
def foo():
  [x, y] = [1, 2]
  x = 3
  print(x)
`,
		[]string{},
		scopeEverywhere)
}
