Repository: aws/chalice
Issue: Issue using dataclasses.asdict in response body

Hi, 

Btw, I love Chalice. I ran into an issue where I tried to have a body variable in my response as follows:


```
@app.route('/private/users', cors=CORS_CONFIG)
def get_user():
    user_dicts = [dataclasses.asdict(user) for user in users.list_users()]

    return Response(
        status_code=201,
        headers=CORS_ENABLED_HEADERS,
        body=user_dicts
    )
```

User is a `@dataclass` file. It's pretty simple: 

```
from dataclasses import dataclass
from typing import List

from chalicelib.models.Message import Message


@dataclass(frozen=True)
class User:
    user_id: str
    sms_number: str
    email_address: str
    trusted_devices: List[str]
    conversation_history: List[Message]

```


This works when I run `chalice local`. However, when I run `chalice deploy` and try to build, I get this message. 


```
  File "/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.7/lib/python3.7/symtable.py", line 95, in lookup
    flags = self._table.symbols[name]
KeyError: 'user_dicts'
```


Full error log here: [chalice_deploy_err.txt](https://github.com/aws/chalice/files/5106624/chalice_deploy_err.txt)

Replacing the body variable with an empty dict fixes the issue and I can run `chalice deploy`. 


Changed files:
tests/unit/test_analyzer.py, chalice/analyzer.py

diff --git a/chalice/analyzer.py b/chalice/analyzer.py
index 9b2f75e..81805f6 100644
--- a/chalice/analyzer.py
+++ b/chalice/analyzer.py
@@ -70,8 +70,9 @@ def get_client_calls_for_app(source_code):
 
     """
     parsed = parse_code(source_code)
-    parsed.parsed_ast = AppViewTransformer().visit(parsed.parsed_ast)
-    ast.fix_missing_locations(parsed.parsed_ast)
+    parsed.parsed_ast = ast.fix_missing_locations(
+        AppViewTransformer().visit(parsed.parsed_ast)
+    )
     t = SymbolTableTypeInfer(parsed)
     binder = t.bind_types()
     collector = APICallCollector(binder)
@@ -267,11 +268,15 @@ class ChainedSymbolTable(object):
         symbol = cast(TypedSymbol, self._local_table.lookup(name))
         symbol.inferred_type = inferred_type
 
-    def lookup_sub_namespace(self, name):
-        # type: (str) -> ChainedSymbolTable
+    def lookup_sub_namespace(self, name, lineno=None):
+        # type: (str, Optional[int]) -> ChainedSymbolTable
         for child in self._local_table.get_children():
             if child.get_name() == name:
-                return self.__class__(child, self._local_table)
+                if lineno is not None:
+                    if child.get_lineno() == lineno:
+                        return self.__class__(child, self._local_table)
+                else:
+                    return self.__class__(child, self._local_table)
         for child in self._global_table.get_children():
             if child.get_name() == name:
                 return self.__class__(child, self._global_table)
@@ -493,7 +498,8 @@ class SymbolTableTypeInfer(ast.NodeVisitor):
         # function, then we know that the inferred type for
         # calling the function is the .return_type type.
         function_name = node.func.id
-        sub_table = self._symbol_table.lookup_sub_namespace(function_name)
+        sub_table = self._symbol_table.lookup_sub_namespace(
+            function_name, node.lineno)
         ast_node = self._symbol_table.lookup_ast_node_for_symbol(
             function_name)
 
@@ -696,4 +702,10 @@ class AppViewTransformer(ast.NodeTransformer):
                 args=[], keywords=[], starargs=None, kwargs=None
             )
         )
+        # We're using the same line numbers as the original
+        # function definition so that way we can match up symbols
+        # (which have linenos with the corresponding functiondef)
+        # in the case that a symbol has multiple namespaces associated
+        # with it.
+        ast.copy_location(auto_invoke, node)
         return [node, auto_invoke]
diff --git a/tests/unit/test_analyzer.py b/tests/unit/test_analyzer.py
index 4f82226..bb33d80 100644
--- a/tests/unit/test_analyzer.py
+++ b/tests/unit/test_analyzer.py
@@ -711,6 +711,44 @@ def test_can_analyze_transfer_manager_methods():
     """) == {'s3': set(['download_file'])}
 
 
+def test_can_handle_replacing_function_name():
+    assert chalice_aws_calls("""\\
+        from chalice import Chalice
+        import boto3
+
+        app = Chalice(app_name='sqs-event')
+
+        def index():
+            pass
+
+        @app.on_sqs_message(queue='myqueue')
+        def index(event):
+            foo = boto3.client('s3').list_buckets()
+
+    """) == {'s3': set(['list_buckets'])}
+
+
+def test_can_handle_multiple_shadowing():
+    assert chalice_aws_calls("""\\
+        from chalice import Chalice
+        import boto3
+
+        app = Chalice(app_name='sqs-event')
+
+        def index():
+            pass
+
+        @app.on_sqs_message(queue='myqueue')
+        def index(event):
+            foo = boto3.client('s3').list_buckets()
+
+        @app.on_s3_event(bucket='mybucket')
+        def index(event):
+            bar = boto3.client('s3').head_bucket(Bucket='foo')
+
+    """) == {'s3': set(['list_buckets', 'head_bucket'])}
+
+
 # def test_tuple_assignment():
 #     assert aws_calls("""\\
 #         import boto3