We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"null" as Parameter Name Throws Incorrect Error
### CloudFormation Lint Version

0.85.2_1

### What operating system are you using?

Mac

### Describe the bug

Setting a Parameter name to "null" produces incorrect or misleading errors:
"E0002 Unknown exception while processing rule E2011: object of type 'NoneType' has no len()"
"E0002 Unknown exception while processing rule E2003: expected string or buffer"
(further, the document line reference is 1:1; which is not correct)

The Cloudformation service itself returns the following more specific error message:
"Template format error: [/Parameters] encountered malformed key"

### Expected behavior

cfn-lint returns correct line number and a more accurate error message, such as:
"E000X Parameters encountered malformed key"

### Reproduction template

```yaml
AWSTemplateFormatVersion: 2010-09-09

Parameters:
  null:
    Description: anything
    Type: String

#...
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/decode/cfn_yaml.py`
Content:
```
1 """
2 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 
6 import fileinput
7 import logging
8 import sys
9 
10 from yaml import MappingNode, ScalarNode, SequenceNode
11 from yaml.composer import Composer
12 from yaml.constructor import ConstructorError, SafeConstructor
13 from yaml.reader import Reader
14 from yaml.resolver import Resolver
15 from yaml.scanner import Scanner
16 
17 import cfnlint
18 from cfnlint.decode.node import dict_node, list_node, str_node, sub_node
19 
20 try:
21     from yaml._yaml import CParser as Parser  # pylint: disable=ungrouped-imports,
22 
23     cyaml = True
24 except ImportError:
25     from yaml.parser import Parser  # type: ignore # pylint: disable=ungrouped-imports
26 
27     cyaml = False
28 
29 UNCONVERTED_SUFFIXES = ["Ref", "Condition"]
30 FN_PREFIX = "Fn::"
31 
32 LOGGER = logging.getLogger(__name__)
33 
34 
35 class CfnParseError(ConstructorError):
36     """
37     Error thrown when the template contains Cfn Error
38     """
39 
40     def __init__(self, filename, errors):
41         if isinstance(errors, cfnlint.rules.Match):
42             errors = [errors]
43 
44         # Call the base class constructor with the parameters it needs
45         super().__init__(errors[0].message)
46 
47         # Now for your custom code...
48         self.filename = filename
49         self.matches = errors
50 
51 
52 def build_match(filename, message, line_number, column_number, key):
53     return cfnlint.rules.Match(
54         line_number + 1,
55         column_number + 1,
56         line_number + 1,
57         column_number + 1 + len(key),
58         filename,
59         cfnlint.rules.ParseError(),
60         message=message,
61     )
62 
63 
64 class NodeConstructor(SafeConstructor):
65     """
66     Node Constructors for loading different types in Yaml
67     """
68 
69     def __init__(self, filename):
70         # Call the base class constructor
71         super().__init__()
72 
73         self.filename = filename
74 
75     # To support lazy loading, the original constructors first yield
76     # an empty object, then fill them in when iterated. Due to
77     # laziness we omit this behaviour (and will only do "deep
78     # construction") by first exhausting iterators, then yielding
79     # copies.
80     def construct_yaml_map(self, node):
81         # Check for duplicate keys on the current level, this is not desirable
82         # because a dict does not support this. It overwrites it with the last
83         # occurance, which can give unexpected results
84         mapping = {}
85         self.flatten_mapping(node)
86         matches = []
87         for key_node, value_node in node.value:
88             key = self.construct_object(key_node, False)
89             value = self.construct_object(value_node, False)
90 
91             for key_dup in mapping:
92                 if key_dup == key:
93                     if not matches:
94                         matches.extend(
95                             [
96                                 build_match(
97                                     filename=self.filename,
98                                     message=f'Duplicate found "{key}" (line {key_dup.start_mark.line + 1})',
99                                     line_number=key_dup.start_mark.line,
100                                     column_number=key_dup.start_mark.column,
101                                     key=key,
102                                 ),
103                                 build_match(
104                                     filename=self.filename,
105                                     message=f'Duplicate found "{key}" (line {key_node.start_mark.line + 1})',
106                                     line_number=key_node.start_mark.line,
107                                     column_number=key_node.start_mark.column,
108                                     key=key,
109                                 ),
110                             ],
111                         )
112                     else:
113                         matches.append(
114                             build_match(
115                                 filename=self.filename,
116                                 message=f'Duplicate found "{key}" (line {key_node.start_mark.line + 1})',
117                                 line_number=key_node.start_mark.line,
118                                 column_number=key_node.start_mark.column,
119                                 key=key,
120                             ),
121                         )
122             try:
123                 mapping[key] = value
124             except Exception as exc:
125                 raise CfnParseError(
126                     self.filename,
127                     [
128                         build_match(
129                             filename=self.filename,
130                             message=f'Unhashable type "{key}" (line {key.start_mark.line + 1})',
131                             line_number=key.start_mark.line,
132                             column_number=key.start_mark.column,
133                             key=key,
134                         ),
135                     ],
136                 ) from exc
137 
138         if matches:
139             raise CfnParseError(
140                 self.filename,
141                 matches,
142             )
143 
144         (obj,) = SafeConstructor.construct_yaml_map(self, node)
145 
146         if len(mapping) == 1:
147             if "Fn::Sub" in mapping:
148                 return sub_node(obj, node.start_mark, node.end_mark)
149 
150         return dict_node(obj, node.start_mark, node.end_mark)
151 
152     def construct_yaml_str(self, node):
153         obj = SafeConstructor.construct_yaml_str(self, node)
154         assert isinstance(obj, (str))
155         return str_node(obj, node.start_mark, node.end_mark)
156 
157     def construct_yaml_seq(self, node):
158         (obj,) = SafeConstructor.construct_yaml_seq(self, node)
159         assert isinstance(obj, list)
160         return list_node(obj, node.start_mark, node.end_mark)
161 
162 
163 NodeConstructor.add_constructor(  # type: ignore
164     "tag:yaml.org,2002:map", NodeConstructor.construct_yaml_map
165 )
166 
167 NodeConstructor.add_constructor(  # type: ignore
168     "tag:yaml.org,2002:str", NodeConstructor.construct_yaml_str
169 )
170 
171 NodeConstructor.add_constructor(  # type: ignore
172     "tag:yaml.org,2002:seq", NodeConstructor.construct_yaml_seq
173 )
174 
175 
176 # pylint: disable=too-many-ancestors
177 class MarkedLoader(Reader, Scanner, Parser, Composer, NodeConstructor, Resolver):
178     """
179     Class for marked loading YAML
180     """
181 
182     # pylint: disable=non-parent-init-called,super-init-not-called
183 
184     def __init__(self, stream, filename):
185         Reader.__init__(self, stream)
186         Scanner.__init__(self)
187         if cyaml:
188             Parser.__init__(self, stream)
189         else:
190             Parser.__init__(self)
191         Composer.__init__(self)
192         SafeConstructor.__init__(self)
193         Resolver.__init__(self)
194         NodeConstructor.__init__(self, filename)
195 
196     def construct_getatt(self, node):
197         """
198         Reconstruct !GetAtt into a list
199         """
200 
201         if isinstance(node.value, (str)):
202             return list_node(node.value.split(".", 1), node.start_mark, node.end_mark)
203         if isinstance(node.value, list):
204             return [self.construct_object(child, deep=False) for child in node.value]
205 
206         raise ValueError(f"Unexpected node type: {type(node.value)}")
207 
208 
209 def multi_constructor(loader, tag_suffix, node):
210     """
211     Deal with !Ref style function format
212     """
213 
214     if tag_suffix not in UNCONVERTED_SUFFIXES:
215         tag_suffix = f"{FN_PREFIX}{tag_suffix}"
216 
217     constructor = None
218     if tag_suffix == "Fn::GetAtt":
219         constructor = loader.construct_getatt
220     elif isinstance(node, ScalarNode):
221         constructor = loader.construct_scalar
222     elif isinstance(node, SequenceNode):
223         constructor = loader.construct_sequence
224     elif isinstance(node, MappingNode):
225         constructor = loader.construct_mapping
226     else:
227         raise f"Bad tag: !{tag_suffix}"
228 
229     if tag_suffix == "Fn::Sub":
230         return sub_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)
231 
232     return dict_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)
233 
234 
235 def loads(yaml_string, fname=None):
236     """
237     Load the given YAML string
238     """
239     loader = MarkedLoader(yaml_string, fname)
240     loader.add_multi_constructor("!", multi_constructor)
241     template = loader.get_single_data()
242     # Convert an empty file to an empty dict
243     if template is None:
244         template = {}
245 
246     return template
247 
248 
249 def load(filename):
250     """
251     Load the given YAML file
252     """
253 
254     content = ""
255 
256     if not sys.stdin.isatty():
257         filename = "-" if filename is None else filename
258         if sys.version_info.major <= 3 and sys.version_info.minor <= 9:
259             for line in fileinput.input(files=filename):
260                 content = content + line
261         else:
262             for line in fileinput.input(  # pylint: disable=unexpected-keyword-arg
263                 files=filename, encoding="utf-8"
264             ):
265                 content = content + line
266     else:
267         with open(filename, encoding="utf-8") as fp:
268             content = fp.read()
269 
270     return loads(content, filename)
271
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/decode/cfn_yaml.py b/src/cfnlint/decode/cfn_yaml.py
--- a/src/cfnlint/decode/cfn_yaml.py
+++ b/src/cfnlint/decode/cfn_yaml.py
@@ -88,6 +88,19 @@
             key = self.construct_object(key_node, False)
             value = self.construct_object(value_node, False)
 
+            if key is None:
+                raise CfnParseError(
+                    self.filename,
+                    [
+                        build_match(
+                            filename=self.filename,
+                            message=f"Null key {key_node.value!r} not supported (line {key_node.start_mark.line + 1})",
+                            line_number=key_node.start_mark.line,
+                            column_number=key_node.start_mark.column,
+                            key=key_node.value,
+                        ),
+                    ],
+                )
             for key_dup in mapping:
                 if key_dup == key:
                     if not matches:

{"golden_diff": "diff --git a/src/cfnlint/decode/cfn_yaml.py b/src/cfnlint/decode/cfn_yaml.py\n--- a/src/cfnlint/decode/cfn_yaml.py\n+++ b/src/cfnlint/decode/cfn_yaml.py\n@@ -88,6 +88,19 @@\n             key = self.construct_object(key_node, False)\n             value = self.construct_object(value_node, False)\n \n+            if key is None:\n+                raise CfnParseError(\n+                    self.filename,\n+                    [\n+                        build_match(\n+                            filename=self.filename,\n+                            message=f\"Null key {key_node.value!r} not supported (line {key_node.start_mark.line + 1})\",\n+                            line_number=key_node.start_mark.line,\n+                            column_number=key_node.start_mark.column,\n+                            key=key_node.value,\n+                        ),\n+                    ],\n+                )\n             for key_dup in mapping:\n                 if key_dup == key:\n                     if not matches:\n", "issue": "\"null\" as Parameter Name Throws Incorrect Error\n### CloudFormation Lint Version\n\n0.85.2_1\n\n### What operating system are you using?\n\nMac\n\n### Describe the bug\n\nSetting a Parameter name to \"null\" produces incorrect or misleading errors:\r\n\"E0002 Unknown exception while processing rule E2011: object of type 'NoneType' has no len()\"\r\n\"E0002 Unknown exception while processing rule E2003: expected string or buffer\"\r\n(further, the document line reference is 1:1; which is not correct)\r\n\r\nThe Cloudformation service itself returns the following more specific error message:\r\n\"Template format error: [/Parameters] encountered malformed key\"\n\n### Expected behavior\n\ncfn-lint returns correct line number and a more accurate error message, such as:\r\n\"E000X Parameters encountered malformed key\"\n\n### Reproduction template\n\n```yaml\r\nAWSTemplateFormatVersion: 2010-09-09\r\n\r\nParameters:\r\n  null:\r\n    Description: anything\r\n    Type: String\r\n\r\n#...\r\n```\n", "before_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\n\nimport fileinput\nimport logging\nimport sys\n\nfrom yaml import MappingNode, ScalarNode, SequenceNode\nfrom yaml.composer import Composer\nfrom yaml.constructor import ConstructorError, SafeConstructor\nfrom yaml.reader import Reader\nfrom yaml.resolver import Resolver\nfrom yaml.scanner import Scanner\n\nimport cfnlint\nfrom cfnlint.decode.node import dict_node, list_node, str_node, sub_node\n\ntry:\n    from yaml._yaml import CParser as Parser  # pylint: disable=ungrouped-imports,\n\n    cyaml = True\nexcept ImportError:\n    from yaml.parser import Parser  # type: ignore # pylint: disable=ungrouped-imports\n\n    cyaml = False\n\nUNCONVERTED_SUFFIXES = [\"Ref\", \"Condition\"]\nFN_PREFIX = \"Fn::\"\n\nLOGGER = logging.getLogger(__name__)\n\n\nclass CfnParseError(ConstructorError):\n    \"\"\"\n    Error thrown when the template contains Cfn Error\n    \"\"\"\n\n    def __init__(self, filename, errors):\n        if isinstance(errors, cfnlint.rules.Match):\n            errors = [errors]\n\n        # Call the base class constructor with the parameters it needs\n        super().__init__(errors[0].message)\n\n        # Now for your custom code...\n        self.filename = filename\n        self.matches = errors\n\n\ndef build_match(filename, message, line_number, column_number, key):\n    return cfnlint.rules.Match(\n        line_number + 1,\n        column_number + 1,\n        line_number + 1,\n        column_number + 1 + len(key),\n        filename,\n        cfnlint.rules.ParseError(),\n        message=message,\n    )\n\n\nclass NodeConstructor(SafeConstructor):\n    \"\"\"\n    Node Constructors for loading different types in Yaml\n    \"\"\"\n\n    def __init__(self, filename):\n        # Call the base class constructor\n        super().__init__()\n\n        self.filename = filename\n\n    # To support lazy loading, the original constructors first yield\n    # an empty object, then fill them in when iterated. Due to\n    # laziness we omit this behaviour (and will only do \"deep\n    # construction\") by first exhausting iterators, then yielding\n    # copies.\n    def construct_yaml_map(self, node):\n        # Check for duplicate keys on the current level, this is not desirable\n        # because a dict does not support this. It overwrites it with the last\n        # occurance, which can give unexpected results\n        mapping = {}\n        self.flatten_mapping(node)\n        matches = []\n        for key_node, value_node in node.value:\n            key = self.construct_object(key_node, False)\n            value = self.construct_object(value_node, False)\n\n            for key_dup in mapping:\n                if key_dup == key:\n                    if not matches:\n                        matches.extend(\n                            [\n                                build_match(\n                                    filename=self.filename,\n                                    message=f'Duplicate found \"{key}\" (line {key_dup.start_mark.line + 1})',\n                                    line_number=key_dup.start_mark.line,\n                                    column_number=key_dup.start_mark.column,\n                                    key=key,\n                                ),\n                                build_match(\n                                    filename=self.filename,\n                                    message=f'Duplicate found \"{key}\" (line {key_node.start_mark.line + 1})',\n                                    line_number=key_node.start_mark.line,\n                                    column_number=key_node.start_mark.column,\n                                    key=key,\n                                ),\n                            ],\n                        )\n                    else:\n                        matches.append(\n                            build_match(\n                                filename=self.filename,\n                                message=f'Duplicate found \"{key}\" (line {key_node.start_mark.line + 1})',\n                                line_number=key_node.start_mark.line,\n                                column_number=key_node.start_mark.column,\n                                key=key,\n                            ),\n                        )\n            try:\n                mapping[key] = value\n            except Exception as exc:\n                raise CfnParseError(\n                    self.filename,\n                    [\n                        build_match(\n                            filename=self.filename,\n                            message=f'Unhashable type \"{key}\" (line {key.start_mark.line + 1})',\n                            line_number=key.start_mark.line,\n                            column_number=key.start_mark.column,\n                            key=key,\n                        ),\n                    ],\n                ) from exc\n\n        if matches:\n            raise CfnParseError(\n                self.filename,\n                matches,\n            )\n\n        (obj,) = SafeConstructor.construct_yaml_map(self, node)\n\n        if len(mapping) == 1:\n            if \"Fn::Sub\" in mapping:\n                return sub_node(obj, node.start_mark, node.end_mark)\n\n        return dict_node(obj, node.start_mark, node.end_mark)\n\n    def construct_yaml_str(self, node):\n        obj = SafeConstructor.construct_yaml_str(self, node)\n        assert isinstance(obj, (str))\n        return str_node(obj, node.start_mark, node.end_mark)\n\n    def construct_yaml_seq(self, node):\n        (obj,) = SafeConstructor.construct_yaml_seq(self, node)\n        assert isinstance(obj, list)\n        return list_node(obj, node.start_mark, node.end_mark)\n\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:map\", NodeConstructor.construct_yaml_map\n)\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:str\", NodeConstructor.construct_yaml_str\n)\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:seq\", NodeConstructor.construct_yaml_seq\n)\n\n\n# pylint: disable=too-many-ancestors\nclass MarkedLoader(Reader, Scanner, Parser, Composer, NodeConstructor, Resolver):\n    \"\"\"\n    Class for marked loading YAML\n    \"\"\"\n\n    # pylint: disable=non-parent-init-called,super-init-not-called\n\n    def __init__(self, stream, filename):\n        Reader.__init__(self, stream)\n        Scanner.__init__(self)\n        if cyaml:\n            Parser.__init__(self, stream)\n        else:\n            Parser.__init__(self)\n        Composer.__init__(self)\n        SafeConstructor.__init__(self)\n        Resolver.__init__(self)\n        NodeConstructor.__init__(self, filename)\n\n    def construct_getatt(self, node):\n        \"\"\"\n        Reconstruct !GetAtt into a list\n        \"\"\"\n\n        if isinstance(node.value, (str)):\n            return list_node(node.value.split(\".\", 1), node.start_mark, node.end_mark)\n        if isinstance(node.value, list):\n            return [self.construct_object(child, deep=False) for child in node.value]\n\n        raise ValueError(f\"Unexpected node type: {type(node.value)}\")\n\n\ndef multi_constructor(loader, tag_suffix, node):\n    \"\"\"\n    Deal with !Ref style function format\n    \"\"\"\n\n    if tag_suffix not in UNCONVERTED_SUFFIXES:\n        tag_suffix = f\"{FN_PREFIX}{tag_suffix}\"\n\n    constructor = None\n    if tag_suffix == \"Fn::GetAtt\":\n        constructor = loader.construct_getatt\n    elif isinstance(node, ScalarNode):\n        constructor = loader.construct_scalar\n    elif isinstance(node, SequenceNode):\n        constructor = loader.construct_sequence\n    elif isinstance(node, MappingNode):\n        constructor = loader.construct_mapping\n    else:\n        raise f\"Bad tag: !{tag_suffix}\"\n\n    if tag_suffix == \"Fn::Sub\":\n        return sub_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)\n\n    return dict_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)\n\n\ndef loads(yaml_string, fname=None):\n    \"\"\"\n    Load the given YAML string\n    \"\"\"\n    loader = MarkedLoader(yaml_string, fname)\n    loader.add_multi_constructor(\"!\", multi_constructor)\n    template = loader.get_single_data()\n    # Convert an empty file to an empty dict\n    if template is None:\n        template = {}\n\n    return template\n\n\ndef load(filename):\n    \"\"\"\n    Load the given YAML file\n    \"\"\"\n\n    content = \"\"\n\n    if not sys.stdin.isatty():\n        filename = \"-\" if filename is None else filename\n        if sys.version_info.major <= 3 and sys.version_info.minor <= 9:\n            for line in fileinput.input(files=filename):\n                content = content + line\n        else:\n            for line in fileinput.input(  # pylint: disable=unexpected-keyword-arg\n                files=filename, encoding=\"utf-8\"\n            ):\n                content = content + line\n    else:\n        with open(filename, encoding=\"utf-8\") as fp:\n            content = fp.read()\n\n    return loads(content, filename)\n", "path": "src/cfnlint/decode/cfn_yaml.py"}], "after_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\n\nimport fileinput\nimport logging\nimport sys\n\nfrom yaml import MappingNode, ScalarNode, SequenceNode\nfrom yaml.composer import Composer\nfrom yaml.constructor import ConstructorError, SafeConstructor\nfrom yaml.reader import Reader\nfrom yaml.resolver import Resolver\nfrom yaml.scanner import Scanner\n\nimport cfnlint\nfrom cfnlint.decode.node import dict_node, list_node, str_node, sub_node\n\ntry:\n    from yaml._yaml import CParser as Parser  # pylint: disable=ungrouped-imports,\n\n    cyaml = True\nexcept ImportError:\n    from yaml.parser import Parser  # type: ignore # pylint: disable=ungrouped-imports\n\n    cyaml = False\n\nUNCONVERTED_SUFFIXES = [\"Ref\", \"Condition\"]\nFN_PREFIX = \"Fn::\"\n\nLOGGER = logging.getLogger(__name__)\n\n\nclass CfnParseError(ConstructorError):\n    \"\"\"\n    Error thrown when the template contains Cfn Error\n    \"\"\"\n\n    def __init__(self, filename, errors):\n        if isinstance(errors, cfnlint.rules.Match):\n            errors = [errors]\n\n        # Call the base class constructor with the parameters it needs\n        super().__init__(errors[0].message)\n\n        # Now for your custom code...\n        self.filename = filename\n        self.matches = errors\n\n\ndef build_match(filename, message, line_number, column_number, key):\n    return cfnlint.rules.Match(\n        line_number + 1,\n        column_number + 1,\n        line_number + 1,\n        column_number + 1 + len(key),\n        filename,\n        cfnlint.rules.ParseError(),\n        message=message,\n    )\n\n\nclass NodeConstructor(SafeConstructor):\n    \"\"\"\n    Node Constructors for loading different types in Yaml\n    \"\"\"\n\n    def __init__(self, filename):\n        # Call the base class constructor\n        super().__init__()\n\n        self.filename = filename\n\n    # To support lazy loading, the original constructors first yield\n    # an empty object, then fill them in when iterated. Due to\n    # laziness we omit this behaviour (and will only do \"deep\n    # construction\") by first exhausting iterators, then yielding\n    # copies.\n    def construct_yaml_map(self, node):\n        # Check for duplicate keys on the current level, this is not desirable\n        # because a dict does not support this. It overwrites it with the last\n        # occurance, which can give unexpected results\n        mapping = {}\n        self.flatten_mapping(node)\n        matches = []\n        for key_node, value_node in node.value:\n            key = self.construct_object(key_node, False)\n            value = self.construct_object(value_node, False)\n\n            if key is None:\n                raise CfnParseError(\n                    self.filename,\n                    [\n                        build_match(\n                            filename=self.filename,\n                            message=f\"Null key {key_node.value!r} not supported (line {key_node.start_mark.line + 1})\",\n                            line_number=key_node.start_mark.line,\n                            column_number=key_node.start_mark.column,\n                            key=key_node.value,\n                        ),\n                    ],\n                )\n            for key_dup in mapping:\n                if key_dup == key:\n                    if not matches:\n                        matches.extend(\n                            [\n                                build_match(\n                                    filename=self.filename,\n                                    message=f'Duplicate found \"{key}\" (line {key_dup.start_mark.line + 1})',\n                                    line_number=key_dup.start_mark.line,\n                                    column_number=key_dup.start_mark.column,\n                                    key=key,\n                                ),\n                                build_match(\n                                    filename=self.filename,\n                                    message=f'Duplicate found \"{key}\" (line {key_node.start_mark.line + 1})',\n                                    line_number=key_node.start_mark.line,\n                                    column_number=key_node.start_mark.column,\n                                    key=key,\n                                ),\n                            ],\n                        )\n                    else:\n                        matches.append(\n                            build_match(\n                                filename=self.filename,\n                                message=f'Duplicate found \"{key}\" (line {key_node.start_mark.line + 1})',\n                                line_number=key_node.start_mark.line,\n                                column_number=key_node.start_mark.column,\n                                key=key,\n                            ),\n                        )\n            try:\n                mapping[key] = value\n            except Exception as exc:\n                raise CfnParseError(\n                    self.filename,\n                    [\n                        build_match(\n                            filename=self.filename,\n                            message=f'Unhashable type \"{key}\" (line {key.start_mark.line + 1})',\n                            line_number=key.start_mark.line,\n                            column_number=key.start_mark.column,\n                            key=key,\n                        ),\n                    ],\n                ) from exc\n\n        if matches:\n            raise CfnParseError(\n                self.filename,\n                matches,\n            )\n\n        (obj,) = SafeConstructor.construct_yaml_map(self, node)\n\n        if len(mapping) == 1:\n            if \"Fn::Sub\" in mapping:\n                return sub_node(obj, node.start_mark, node.end_mark)\n\n        return dict_node(obj, node.start_mark, node.end_mark)\n\n    def construct_yaml_str(self, node):\n        obj = SafeConstructor.construct_yaml_str(self, node)\n        assert isinstance(obj, (str))\n        return str_node(obj, node.start_mark, node.end_mark)\n\n    def construct_yaml_seq(self, node):\n        (obj,) = SafeConstructor.construct_yaml_seq(self, node)\n        assert isinstance(obj, list)\n        return list_node(obj, node.start_mark, node.end_mark)\n\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:map\", NodeConstructor.construct_yaml_map\n)\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:str\", NodeConstructor.construct_yaml_str\n)\n\nNodeConstructor.add_constructor(  # type: ignore\n    \"tag:yaml.org,2002:seq\", NodeConstructor.construct_yaml_seq\n)\n\n\n# pylint: disable=too-many-ancestors\nclass MarkedLoader(Reader, Scanner, Parser, Composer, NodeConstructor, Resolver):\n    \"\"\"\n    Class for marked loading YAML\n    \"\"\"\n\n    # pylint: disable=non-parent-init-called,super-init-not-called\n\n    def __init__(self, stream, filename):\n        Reader.__init__(self, stream)\n        Scanner.__init__(self)\n        if cyaml:\n            Parser.__init__(self, stream)\n        else:\n            Parser.__init__(self)\n        Composer.__init__(self)\n        SafeConstructor.__init__(self)\n        Resolver.__init__(self)\n        NodeConstructor.__init__(self, filename)\n\n    def construct_getatt(self, node):\n        \"\"\"\n        Reconstruct !GetAtt into a list\n        \"\"\"\n\n        if isinstance(node.value, (str)):\n            return list_node(node.value.split(\".\", 1), node.start_mark, node.end_mark)\n        if isinstance(node.value, list):\n            return [self.construct_object(child, deep=False) for child in node.value]\n\n        raise ValueError(f\"Unexpected node type: {type(node.value)}\")\n\n\ndef multi_constructor(loader, tag_suffix, node):\n    \"\"\"\n    Deal with !Ref style function format\n    \"\"\"\n\n    if tag_suffix not in UNCONVERTED_SUFFIXES:\n        tag_suffix = f\"{FN_PREFIX}{tag_suffix}\"\n\n    constructor = None\n    if tag_suffix == \"Fn::GetAtt\":\n        constructor = loader.construct_getatt\n    elif isinstance(node, ScalarNode):\n        constructor = loader.construct_scalar\n    elif isinstance(node, SequenceNode):\n        constructor = loader.construct_sequence\n    elif isinstance(node, MappingNode):\n        constructor = loader.construct_mapping\n    else:\n        raise f\"Bad tag: !{tag_suffix}\"\n\n    if tag_suffix == \"Fn::Sub\":\n        return sub_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)\n\n    return dict_node({tag_suffix: constructor(node)}, node.start_mark, node.end_mark)\n\n\ndef loads(yaml_string, fname=None):\n    \"\"\"\n    Load the given YAML string\n    \"\"\"\n    loader = MarkedLoader(yaml_string, fname)\n    loader.add_multi_constructor(\"!\", multi_constructor)\n    template = loader.get_single_data()\n    # Convert an empty file to an empty dict\n    if template is None:\n        template = {}\n\n    return template\n\n\ndef load(filename):\n    \"\"\"\n    Load the given YAML file\n    \"\"\"\n\n    content = \"\"\n\n    if not sys.stdin.isatty():\n        filename = \"-\" if filename is None else filename\n        if sys.version_info.major <= 3 and sys.version_info.minor <= 9:\n            for line in fileinput.input(files=filename):\n                content = content + line\n        else:\n            for line in fileinput.input(  # pylint: disable=unexpected-keyword-arg\n                files=filename, encoding=\"utf-8\"\n            ):\n                content = content + line\n    else:\n        with open(filename, encoding=\"utf-8\") as fp:\n            content = fp.read()\n\n    return loads(content, filename)\n", "path": "src/cfnlint/decode/cfn_yaml.py"}]}