Rust: Auto-generate CfgNodes.qll

This commit is contained in:
Tom Hvitved
2024-11-04 08:45:54 +01:00
parent 1c2fdc29a3
commit c8736e8a3d
18 changed files with 4139 additions and 56 deletions

View File

@@ -64,6 +64,8 @@ def _parse_args() -> argparse.Namespace:
help="registry file containing information about checked-in generated code. A .gitattributes"
"file is generated besides it to mark those files with linguist-generated=true. Must"
"be in a directory containing all generated code."),
p.add_argument("--ql-cfg-output",
help="output directory for QL CFG layer (optional)."),
]
p.add_argument("--script-name",
help="script name to put in header comments of generated files. By default, the path of this "

View File

@@ -160,6 +160,8 @@ def get_ql_class(cls: schema.Class, lookup: typing.Dict[str, schema.Class]) -> q
prop = get_ql_property(cls, p, lookup, prev_child)
if prop.is_child:
prev_child = prop.singular
if prop.type in lookup and lookup[prop.type].cfg:
prop.cfg = True
properties.append(prop)
return ql.Class(
name=cls.name,
@@ -171,6 +173,16 @@ def get_ql_class(cls: schema.Class, lookup: typing.Dict[str, schema.Class]) -> q
doc=cls.doc,
hideable="ql_hideable" in cls.pragmas,
internal="ql_internal" in cls.pragmas,
cfg=cls.cfg,
)
def get_ql_cfg_class(cls: schema.Class, lookup: typing.Dict[str, ql.Class]) -> ql.CfgClass:
return ql.CfgClass(
name=cls.name,
bases=[base for base in cls.bases if lookup[base.base].cfg],
properties=cls.properties,
doc=cls.doc
)
@@ -361,6 +373,7 @@ def generate(opts, renderer):
input = opts.schema
out = opts.ql_output
stub_out = opts.ql_stub_output
cfg_out = opts.ql_cfg_output
test_out = opts.ql_test_output
missing_test_source_filename = "MISSING_SOURCE.txt"
include_file = stub_out.with_suffix(".qll")
@@ -385,6 +398,7 @@ def generate(opts, renderer):
imports = {}
imports_impl = {}
classes_used_by = {}
cfg_classes = []
generated_import_prefix = get_import(out, opts.root_dir)
registry = opts.generated_registry or pathlib.Path(
os.path.commonpath((out, stub_out, test_out)), ".generated.list")
@@ -402,6 +416,8 @@ def generate(opts, renderer):
imports[c.name] = path
path_impl = get_import(stub_out / c.dir / "internal" / c.name, opts.root_dir)
imports_impl[c.name + "Impl"] = path_impl + "Impl"
if c.cfg:
cfg_classes.append(get_ql_cfg_class(c, classes))
for c in classes.values():
qll = out / c.path.with_suffix(".qll")
@@ -411,6 +427,14 @@ def generate(opts, renderer):
c.import_prefix = generated_import_prefix
renderer.render(c, qll)
if cfg_out:
cfg_classes_val = ql.CfgClasses(
include_file_import=get_import(include_file, opts.root_dir),
classes=cfg_classes
)
cfg_qll = cfg_out / "CfgNodes.qll"
renderer.render(cfg_classes_val, cfg_qll)
for c in data.classes.values():
path = _get_path(c)
path_impl = _get_path_impl(c)

View File

@@ -45,6 +45,7 @@ class Property:
synth: bool = False
type_is_hideable: bool = False
internal: bool = False
cfg: bool = False
def __post_init__(self):
if self.tableparams:
@@ -110,6 +111,7 @@ class Class:
internal: bool = False
doc: List[str] = field(default_factory=list)
hideable: bool = False
cfg: bool = False
def __post_init__(self):
def get_bases(bases): return [Base(str(b), str(prev)) for b, prev in zip(bases, itertools.chain([""], bases))]
@@ -333,3 +335,18 @@ class Synth:
cls: "Synth.FinalClass"
import_prefix: str
@dataclass
class CfgClass:
name: str
bases: List[Base] = field(default_factory=list)
properties: List[Property] = field(default_factory=list)
doc: List[str] = field(default_factory=list)
@dataclass
class CfgClasses:
template: ClassVar = 'ql_cfg_nodes'
include_file_import: Optional[str] = None
classes: List[CfgClass] = field(default_factory=list)

View File

@@ -94,6 +94,7 @@ class Class:
properties: List[Property] = field(default_factory=list)
pragmas: List[str] | Dict[str, object] = field(default_factory=dict)
doc: List[str] = field(default_factory=list)
cfg: bool = False
def __post_init__(self):
if not isinstance(self.pragmas, dict):

View File

@@ -279,7 +279,7 @@ _ = _PropertyAnnotation()
drop = object()
def annotate(annotated_cls: type, add_bases: _Iterable[type] | None = None, replace_bases: _Dict[type, type] | None = None) -> _Callable[[type], _PropertyAnnotation]:
def annotate(annotated_cls: type, add_bases: _Iterable[type] | None = None, replace_bases: _Dict[type, type] | None = None, cfg: bool = False) -> _Callable[[type], _PropertyAnnotation]:
"""
Add or modify schema annotations after a class has been defined previously.
@@ -298,6 +298,7 @@ def annotate(annotated_cls: type, add_bases: _Iterable[type] | None = None, repl
annotated_cls.__bases__ = tuple(replace_bases.get(b, b) for b in annotated_cls.__bases__)
if add_bases:
annotated_cls.__bases__ += tuple(add_bases)
annotated_cls.__cfg__ = cfg
for a in dir(cls):
if a.startswith(_schema.inheritable_pragma_prefix):
setattr(annotated_cls, a, getattr(cls, a))

View File

@@ -53,6 +53,7 @@ def _get_class(cls: type) -> schema.Class:
bases=[b.__name__ for b in cls.__bases__ if b is not object],
derived=derived,
pragmas=pragmas,
cfg=cls.__cfg__ if hasattr(cls, "__cfg__") else False,
# in the following we don't use `getattr` to avoid inheriting
properties=[
a | _PropertyNamer(n)

View File

@@ -0,0 +1,199 @@
// generated by {{generator}}, do not edit
/**
* This module provides generated wrappers around the `CfgNode` type.
*
* INTERNAL: Do not import directly.
*/
private import codeql.util.Location
private import codeql.util.Unit
private import {{include_file_import}}
/** Provides the input to `MakeCfgNodes` */
signature module InputSig<LocationSig Loc> {
class CfgNode {
AstNode getAstNode();
string toString();
Loc getLocation();
}
AstNode getDesugared(AstNode n);
}
/**
* Given a `CfgNode` implementation, provides the module `Nodes` that
* contains wrappers around `CfgNode` for relevant classes.
*/
module MakeCfgNodes<LocationSig Loc, InputSig<Loc> Input> {
private import Input
final private class AstNodeFinal = AstNode;
final private class CfgNodeFinal = CfgNode;
/**
* INTERNAL: Do not expose.
*/
abstract class ParentAstNode extends AstNodeFinal {
/**
* Holds if `child` is a (possibly nested) child of this AST node
* for which we would like to find a matching CFG child.
*/
abstract predicate relevantChild(AstNode child);
}
/**
* INTERNAL: Do not expose.
*/
abstract class ChildMapping extends Unit {
/**
* Holds if `child` is a (possibly nested) child of AST node `parent`
* for which we would like to find a matching CFG child.
*/
final predicate relevantChild(AstNode parent, AstNode child) {
parent.(ParentAstNode).relevantChild(child)
}
/**
* Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn`
* is a control-flow node for this AST node, and `cfnChild` is a control-flow
* node for `child`.
*
* This predicate should be implemented at the place where `MakeCfgNodes` is
* invoked. Ideally, `MakeCfgNodes` should be a higher-order parameterized
* module, but since that is currently not supported, we achieve the "callback"
* effect using this `abstract` class instead.
*/
cached
abstract predicate hasCfgChild(AstNode parent, AstNode child, CfgNode cfn, CfgNode cfnChild);
}
/** Provides sub classes of `CfgNode`. */
module Nodes {
{{#classes}}
private final class Parent{{name}} extends ParentAstNode, {{name}} {
override predicate relevantChild(AstNode child) {
none()
{{#properties}}
{{#cfg}}
or
child = this.{{getter}}({{#is_indexed}}_{{/is_indexed}})
{{/cfg}}
{{/properties}}
}
}
/**
{{#doc}}
* {{.}}
{{/doc}}
*/
final class {{name}}CfgNode extends CfgNodeFinal{{#bases}}, {{.}}CfgNode{{/bases}} {
private {{name}} node;
{{name}}CfgNode() {
node = this.getAstNode()
}
/** Gets the underlying `{{name}}`. */
{{name}} get{{name}}() { result = node }
{{#properties}}
/**
* {{>ql_property_doc}} *
{{#description}}
* {{.}}
{{/description}}
{{#internal}}
* INTERNAL: Do not use.
{{/internal}}
*/
{{type}}{{#cfg}}CfgNode{{/cfg}} {{getter}}({{#is_indexed}}int index{{/is_indexed}}) {
{{#cfg}}
any(ChildMapping mapping).hasCfgChild(node, node.{{getter}}({{#is_indexed}}index{{/is_indexed}}), this, result)
{{/cfg}}
{{^cfg}}
{{^is_predicate}}result = {{/is_predicate}}node.{{getter}}({{#is_indexed}}index{{/is_indexed}})
{{/cfg}}
}
{{#is_optional}}
/**
* Holds if `{{getter}}({{#is_repeated}}index{{/is_repeated}})` exists.
{{#internal}}
* INTERNAL: Do not use.
{{/internal}}
*/
predicate has{{singular}}({{#is_repeated}}int index{{/is_repeated}}) {
exists(this.{{getter}}({{#is_repeated}}index{{/is_repeated}}))
}
{{/is_optional}}
{{#is_indexed}}
/**
* Gets any of the {{doc_plural}}.
{{#internal}}
* INTERNAL: Do not use.
{{/internal}}
*/
{{type}}{{#cfg}}CfgNode{{/cfg}} {{indefinite_getter}}() {
result = this.{{getter}}(_)
}
{{^is_optional}}
/**
* Gets the number of {{doc_plural}}.
{{#internal}}
* INTERNAL: Do not use.
{{/internal}}
*/
int getNumberOf{{plural}}() {
result = count(int i | exists(this.{{getter}}(i)))
}
{{/is_optional}}
{{/is_indexed}}
{{#is_unordered}}
/**
* Gets the number of {{doc_plural}}.
{{#internal}}
* INTERNAL: Do not use.
{{/internal}}
*/
int getNumberOf{{plural}}() {
result = count(this.{{getter}}())
}
{{/is_unordered}}
{{/properties}}
}
{{/classes}}
}
module Consistency {
private predicate hasCfgNode(AstNode astNode) {
astNode = any(CfgNode cfgNode).getAstNode()
}
query predicate missingCfgChild(CfgNode parent, string pred, int i, AstNode child) {
none()
{{#classes}}
{{#properties}}
{{#cfg}}
or
pred = "{{getter}}" and
parent = any(Nodes::{{name}}CfgNode cfgNode, {{name}} astNode |
astNode = cfgNode.get{{name}}() and
child = getDesugared(astNode.{{getter}}({{#is_indexed}}i{{/is_indexed}}))
{{^is_indexed}}and i = -1{{/is_indexed}} and
hasCfgNode(child) and
not child = cfgNode.{{getter}}({{#is_indexed}}i{{/is_indexed}}).getAstNode()
|
cfgNode
)
{{/cfg}}
{{/properties}}
{{/classes}}
}
}
}

View File

@@ -4,6 +4,7 @@
--dbscheme=ql/lib/rust.dbscheme
--ql-output=ql/lib/codeql/rust/elements/internal/generated
--ql-stub-output=ql/lib/codeql/rust/elements
--ql-cfg-output=ql/lib/codeql/rust/controlflow/internal/generated
--ql-test-output=ql/test/extractor-tests/generated
--rust-output=extractor/src/generated
--script-name=codegen

View File

@@ -1,3 +1,4 @@
lib/codeql/rust/controlflow/internal/generated/CfgNodes.qll 55c20303ad50f17ddea728eb840304e0a634821adb7b13471a9ce55b71ed3886 13509512ffb59a9634ed2dc3c4969af26d5ba2d1502d98dc0c107a99392ce892
lib/codeql/rust/elements/Abi.qll 4c973d28b6d628f5959d1f1cc793704572fd0acaae9a97dfce82ff9d73f73476 250f68350180af080f904cd34cb2af481c5c688dc93edf7365fd0ae99855e893
lib/codeql/rust/elements/ArgList.qll 661f5100f5d3ef8351452d9058b663a2a5c720eea8cf11bedd628969741486a2 28e424aac01a90fb58cd6f9f83c7e4cf379eea39e636bc0ba07efc818be71c71
lib/codeql/rust/elements/ArrayExpr.qll a3e6e122632f4011644ec31b37f88b32fe3f2b7e388e7e878a6883309937049f 12ccb5873d95c433da5606fd371d182ef2f71b78d0c53c2d6dec10fa45852bdc

1
rust/ql/.gitattributes generated vendored
View File

@@ -1,5 +1,6 @@
/.generated.list linguist-generated
/.gitattributes linguist-generated
/lib/codeql/rust/controlflow/internal/generated/CfgNodes.qll linguist-generated
/lib/codeql/rust/elements/Abi.qll linguist-generated
/lib/codeql/rust/elements/ArgList.qll linguist-generated
/lib/codeql/rust/elements/ArrayExpr.qll linguist-generated

View File

@@ -5,7 +5,9 @@
private import rust
private import ControlFlowGraph
private import internal.ControlFlowGraphImpl
private import internal.ControlFlowGraphImpl as CfgImpl
private import internal.CfgNodes
import Nodes
/** A CFG node that corresponds to an element in the AST. */
class AstCfgNode extends CfgNode {
@@ -57,4 +59,4 @@ class MethodCallExprCfgNode extends ExprCfgNode {
MethodCallExpr getMethodCallExpr() { result = node }
}
final class ExitCfgNode = ExitNode;
final class ExitCfgNode = CfgImpl::ExitNode;

View File

@@ -8,6 +8,8 @@ import Consistency
private import codeql.rust.controlflow.ControlFlowGraph
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl as CfgImpl
private import codeql.rust.controlflow.internal.Completion
private import codeql.rust.controlflow.internal.CfgNodes::Consistency as CfgNodes
private import codeql.rust.Diagnostics
private predicate nonPostOrderExpr(Expr e) {
not e instanceof LetExpr and
@@ -54,6 +56,24 @@ query predicate deadEnd(CfgImpl::Node node) {
not letElsePanic(node.getAstNode())
}
pragma[nomagic]
private predicate successfullyExtractedFile(File f) {
not exists(ExtractionWarning ee | ee.getLocation().getFile() = f)
}
query predicate missingCfgChild(CfgNode parent, string pred, int i, AstNode child) {
CfgNodes::missingCfgChild(parent, pred, i, child) and
successfullyExtractedFile(child.getLocation().getFile()) and
not exists(AstNode last, CfgImpl::Completion c | CfgImpl::last(child, last, c) |
// In for example `if (a && true) ...` there is no RHS CFG node going into the
// `[false] a && true` operation
strictcount(ConditionalSuccessor cs | exists(last.getACfgNode().getASuccessor(cs)) | cs) = 1
or
// In for example `x && return`, there is no RHS CFG node going into the `&&` operation
not c instanceof CfgImpl::NormalCompletion
)
}
/**
* Gets counts of control flow graph inconsistencies of each type.
*/
@@ -99,4 +119,7 @@ int getCfgInconsistencyCounts(string type) {
or
type = "Non-PostOrderTree Expr node" and
result = count(Expr e | nonPostOrderExpr(e) | e)
or
type = "Missing CFG child" and
result = count(CfgNode parent, string pred, int child | missingCfgChild(parent, pred, child, _))
}

View File

@@ -0,0 +1,116 @@
private import rust
private import codeql.rust.controlflow.internal.generated.CfgNodes
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl as CfgImpl
private import codeql.rust.controlflow.ControlFlowGraph
private import codeql.rust.controlflow.BasicBlocks
private import codeql.rust.controlflow.CfgNodes
private import codeql.rust.internal.CachedStages
private predicate isPostOrder(AstNode n) {
n instanceof Expr and
not n instanceof LetExpr
or
n instanceof OrPat
or
n instanceof IdentPat
or
n instanceof LiteralPat
or
n instanceof Param
}
private module CfgNodesInput implements InputSig<Location> {
private import codeql.rust.controlflow.ControlFlowGraph as Cfg
class CfgNode = AstCfgNode;
private AstNode desugar(AstNode n) {
result = n.(ParenPat).getPat()
or
result = n.(ParenExpr).getExpr()
}
AstNode getDesugared(AstNode n) {
result = getDesugared(desugar(n))
or
not exists(desugar(n)) and
result = n
}
}
import MakeCfgNodes<Location, CfgNodesInput>
private class ChildMappingImpl extends ChildMapping {
/** Gets a CFG node for `child`, where `child` is a relevant child node of `parent`. */
private CfgNode getRelevantChildCfgNode(AstNode parent, AstNode child) {
this.relevantChild(parent, child) and
result = CfgNodesInput::getDesugared(child).getACfgNode()
}
pragma[nomagic]
private BasicBlock getARelevantBasicBlock(AstNode parent) {
result.getANode().getAstNode() = parent or
result.getANode() = this.getRelevantChildCfgNode(parent, _)
}
/**
* Holds if CFG node `cfnChild` can reach basic block `bb`, without going
* through an intermediate block that contains a CFG node for `parent` or
* any other relevant child of `parent`.
*/
pragma[nomagic]
predicate childNodeReachesBasicBlock(
AstNode parent, AstNode child, CfgNode cfnChild, BasicBlock bb
) {
exists(BasicBlock bb0 |
cfnChild = this.getRelevantChildCfgNode(parent, child) and
bb0.getANode() = cfnChild
|
bb = bb0
or
not bb0.getANode().getAstNode() = parent and
if isPostOrder(parent) then bb = bb0.getASuccessor() else bb = bb0.getAPredecessor()
)
or
exists(BasicBlock mid |
this.childNodeReachesBasicBlock(parent, child, cfnChild, mid) and
not mid = this.getARelevantBasicBlock(parent) and
if isPostOrder(parent) then bb = mid.getASuccessor() else bb = mid.getAPredecessor()
)
}
/**
* Holds if CFG node `cfnChild` can reach CFG node `cfnParent`, without going
* through an intermediate block that contains a CFG node for `parent`.
*/
pragma[nomagic]
predicate childNodeReachesParentNode(
AstNode parent, CfgNode cfnParent, AstNode child, CfgNode cfnChild
) {
// `cfnChild` can reach `cfnParent` directly
exists(BasicBlock bb |
this.childNodeReachesBasicBlock(parent, child, cfnChild, bb) and
cfnParent.getAstNode() = parent
|
cfnParent = bb.getANode()
or
if isPostOrder(parent)
then cfnParent = bb.getASuccessor().getANode()
else cfnParent = bb.getAPredecessor().getANode()
)
or
// `cfnChild` can reach `cfnParent` by going via another relevant child
exists(CfgNode cfnOtherChild |
this.childNodeReachesParentNode(parent, cfnParent, _, cfnOtherChild) and
exists(BasicBlock bb |
this.childNodeReachesBasicBlock(parent, child, cfnChild, bb) and
bb.getANode() = cfnOtherChild
)
)
}
override predicate hasCfgChild(AstNode parent, AstNode child, AstCfgNode cfn, AstCfgNode cfnChild) {
Stages::CfgStage::ref() and
this.childNodeReachesParentNode(parent, cfn, child, cfnChild)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ private import codeql.rust.controlflow.ControlFlowGraph
module Impl {
private import rust
private import codeql.rust.elements.internal.generated.ParentChild
private import codeql.rust.controlflow.ControlFlowGraph
/**
* Gets the immediate parent of a non-`AstNode` element `e`.
@@ -62,5 +63,37 @@ module Impl {
or
this.getParentNode().isInMacroExpansion()
}
/**
* Gets a control-flow node for this AST node, if any.
*
* Note that because of _control-flow splitting_, one `AstNode` node may correspond
* to multiple `CfgNode`s. Example:
*
* ```rust
* if a && b {
* // ...
* }
* ```
*
* The CFG for the condition above looks like
*
* ```mermaid
* flowchart TD
* 1["a"]
* 2["b"]
* 3["[false] a && b"]
* 4["[true] a && b"]
*
* 1 -- false --> 3
* 1 -- true --> 2
* 2 -- false --> 3
* 2 -- true --> 4
* ```
*
* That is, the AST node for `a && b` corresponds to _two_ CFG nodes (it is
* split into two).
*/
CfgNode getACfgNode() { this = result.getAstNode() }
}
}

View File

@@ -71,6 +71,7 @@ module Stages {
private import codeql.rust.controlflow.internal.Splitting
private import codeql.rust.controlflow.internal.SuccessorType
private import codeql.rust.controlflow.internal.ControlFlowGraphImpl
private import codeql.rust.controlflow.CfgNodes
/**
* Always holds.
@@ -93,6 +94,8 @@ module Stages {
exists(TNormalSuccessor())
or
exists(AstCfgNode n)
or
exists(CallExprCfgNode n | exists(n.getExpr()))
}
}
}

View File

@@ -1,5 +1,6 @@
| CFG scope lacks initial AST node | 0 |
| Dead end | 0 |
| Missing CFG child | 0 |
| Multiple successors of the same type | 0 |
| Multiple toStrings | 0 |
| Non-PostOrderTree Expr node | 0 |

View File

@@ -18,14 +18,14 @@ class _:
"""
@annotate(Expr)
@annotate(Expr, cfg = True)
class _:
"""
The base class for expressions.
"""
@annotate(Pat)
@annotate(Pat, cfg = True)
class _:
"""
The base class for patterns.
@@ -106,7 +106,7 @@ class PathExprBase(Expr):
"""
@annotate(PathExpr, replace_bases={Expr: PathExprBase})
@annotate(PathExpr, replace_bases={Expr: PathExprBase}, cfg = True)
class _:
"""
A path expression. For example:
@@ -119,7 +119,7 @@ class _:
"""
@annotate(IfExpr)
@annotate(IfExpr, cfg = True)
class _:
"""
An `if` expression. For example:
@@ -138,7 +138,7 @@ class _:
"""
@annotate(LetExpr)
@annotate(LetExpr, cfg = True)
@rust.doc_test_signature("(maybe_some: Option<String>) -> ()")
class _:
"""
@@ -151,7 +151,7 @@ class _:
"""
@annotate(BlockExpr)
@annotate(BlockExpr, cfg = True)
class _:
"""
A block expression. For example:
@@ -169,7 +169,7 @@ class _:
"""
@annotate(LoopExpr)
@annotate(LoopExpr, cfg = True)
class _:
"""
A loop expression. For example:
@@ -205,7 +205,7 @@ class CallExprBase(Expr):
attrs: list["Attr"] | child
@annotate(CallExpr, replace_bases={Expr: CallExprBase})
@annotate(CallExpr, replace_bases={Expr: CallExprBase}, cfg = True)
class _:
"""
A function call expression. For example:
@@ -220,7 +220,7 @@ class _:
attrs: drop
@annotate(MethodCallExpr, replace_bases={Expr: CallExprBase}, add_bases=(Resolvable,))
@annotate(MethodCallExpr, replace_bases={Expr: CallExprBase}, add_bases=(Resolvable,), cfg = True)
class _:
"""
A method call expression. For example:
@@ -253,7 +253,7 @@ class _:
"""
@annotate(MatchExpr)
@annotate(MatchExpr, cfg = True)
@rust.doc_test_signature("(x: i32) -> i32")
class _:
"""
@@ -273,7 +273,7 @@ class _:
"""
@annotate(ContinueExpr)
@annotate(ContinueExpr, cfg = True)
class _:
"""
A continue expression. For example:
@@ -294,7 +294,7 @@ class _:
"""
@annotate(BreakExpr)
@annotate(BreakExpr, cfg = True)
class _:
"""
A break expression. For example:
@@ -323,7 +323,7 @@ class _:
"""
@annotate(ReturnExpr)
@annotate(ReturnExpr, cfg = True)
@rust.doc_test_signature(None)
class _:
"""
@@ -341,7 +341,7 @@ class _:
"""
@annotate(BecomeExpr)
@annotate(BecomeExpr, cfg = True)
@rust.doc_test_signature(None)
class _:
"""
@@ -358,7 +358,7 @@ class _:
"""
@annotate(YieldExpr)
@annotate(YieldExpr, cfg = True)
class _:
"""
A `yield` expression. For example:
@@ -371,7 +371,7 @@ class _:
"""
@annotate(YeetExpr)
@annotate(YeetExpr, cfg = True)
class _:
"""
A `yeet` expression. For example:
@@ -393,7 +393,7 @@ class _:
"""
@annotate(RecordExpr)
@annotate(RecordExpr, cfg = True)
class _:
"""
A record expression. For example:
@@ -406,7 +406,7 @@ class _:
"""
@annotate(FieldExpr)
@annotate(FieldExpr, cfg = True)
class _:
"""
A field access expression. For example:
@@ -416,7 +416,7 @@ class _:
"""
@annotate(AwaitExpr)
@annotate(AwaitExpr, cfg = True)
class _:
"""
An `await` expression. For example:
@@ -439,7 +439,7 @@ class _:
"""
@annotate(RefExpr)
@annotate(RefExpr, cfg = True)
class _:
"""
A reference expression. For example:
@@ -452,7 +452,7 @@ class _:
"""
@annotate(PrefixExpr)
@annotate(PrefixExpr, cfg = True)
class _:
"""
A unary operation expression. For example:
@@ -464,7 +464,7 @@ class _:
"""
@annotate(BinaryExpr)
@annotate(BinaryExpr, cfg = True)
class _:
"""
A binary operation expression. For example:
@@ -478,7 +478,7 @@ class _:
"""
@annotate(RangeExpr)
@annotate(RangeExpr, cfg = True)
class _:
"""
A range expression. For example:
@@ -493,7 +493,7 @@ class _:
"""
@annotate(IndexExpr)
@annotate(IndexExpr, cfg = True)
class _:
"""
An index expression. For example:
@@ -520,7 +520,7 @@ class _:
"""
@annotate(TupleExpr)
@annotate(TupleExpr, cfg = True)
class _:
"""
A tuple expression. For example:
@@ -531,7 +531,7 @@ class _:
"""
@annotate(ArrayExpr)
@annotate(ArrayExpr, cfg = True)
class _:
"""
An array expression. For example:
@@ -542,7 +542,7 @@ class _:
"""
@annotate(LiteralExpr)
@annotate(LiteralExpr, cfg = True)
class _:
"""
A literal expression. For example:
@@ -559,7 +559,7 @@ class _:
"""
@annotate(UnderscoreExpr)
@annotate(UnderscoreExpr, cfg = True)
class _:
"""
An underscore expression. For example:
@@ -569,7 +569,7 @@ class _:
"""
@annotate(OffsetOfExpr)
@annotate(OffsetOfExpr, cfg = True)
class _:
"""
An `offset_of` expression. For example:
@@ -579,7 +579,7 @@ class _:
"""
@annotate(AsmExpr)
@annotate(AsmExpr, cfg = True)
class _:
"""
An inline assembly expression. For example:
@@ -591,7 +591,7 @@ class _:
"""
@annotate(LetStmt)
@annotate(LetStmt, cfg = True)
class _:
"""
A let statement. For example:
@@ -620,7 +620,7 @@ class _:
"""
@annotate(WildcardPat)
@annotate(WildcardPat, cfg = True)
class _:
"""
A wildcard pattern. For example:
@@ -630,7 +630,7 @@ class _:
"""
@annotate(TuplePat)
@annotate(TuplePat, cfg = True)
class _:
"""
A tuple pattern. For example:
@@ -641,7 +641,7 @@ class _:
"""
@annotate(OrPat)
@annotate(OrPat, cfg = True)
class _:
"""
An or pattern. For example:
@@ -663,7 +663,7 @@ class _:
"""
@annotate(RecordPat)
@annotate(RecordPat, cfg = True)
class _:
"""
A record pattern. For example:
@@ -676,7 +676,7 @@ class _:
"""
@annotate(RangePat)
@annotate(RangePat, cfg = True)
class _:
"""
A range pattern. For example:
@@ -690,7 +690,7 @@ class _:
"""
@annotate(SlicePat)
@annotate(SlicePat, cfg = True)
class _:
"""
A slice pattern. For example:
@@ -704,7 +704,7 @@ class _:
"""
@annotate(PathPat)
@annotate(PathPat, cfg = True)
class _:
"""
A path pattern. For example:
@@ -717,7 +717,7 @@ class _:
"""
@annotate(LiteralPat)
@annotate(LiteralPat, cfg = True)
class _:
"""
A literal pattern. For example:
@@ -730,7 +730,7 @@ class _:
"""
@annotate(IdentPat)
@annotate(IdentPat, cfg = True)
class _:
"""
A binding pattern. For example:
@@ -749,7 +749,7 @@ class _:
"""
@annotate(TupleStructPat)
@annotate(TupleStructPat, cfg = True)
class _:
"""
A tuple struct pattern. For example:
@@ -763,7 +763,7 @@ class _:
"""
@annotate(RefPat)
@annotate(RefPat, cfg = True)
class _:
"""
A reference pattern. For example:
@@ -776,7 +776,7 @@ class _:
"""
@annotate(BoxPat)
@annotate(BoxPat, cfg = True)
class _:
"""
A box pattern. For example:
@@ -789,7 +789,7 @@ class _:
"""
@annotate(ConstBlockPat)
@annotate(ConstBlockPat, cfg = True)
class _:
"""
A const block pattern. For example:
@@ -990,7 +990,7 @@ class _:
"""
@annotate(ForExpr)
@annotate(ForExpr, cfg = True)
class _:
"""
A ForExpr. For example:
@@ -1020,7 +1020,7 @@ class _:
"""
@annotate(FormatArgsExpr)
@annotate(FormatArgsExpr, cfg = True)
class _:
"""
A FormatArgsExpr. For example:
@@ -1150,7 +1150,7 @@ class _:
"""
@annotate(MacroCall)
@annotate(MacroCall, cfg = True)
class _:
"""
A MacroCall. For example:
@@ -1171,7 +1171,7 @@ class _:
"""
@annotate(MacroExpr)
@annotate(MacroExpr, cfg = True)
class _:
"""
A MacroExpr. For example:
@@ -1194,7 +1194,7 @@ class _:
"""
@annotate(MacroPat)
@annotate(MacroPat, cfg = True)
class _:
"""
A MacroPat. For example:
@@ -1297,7 +1297,7 @@ class _:
"""
@annotate(Param)
@annotate(Param, cfg = True)
class _:
"""
A Param. For example:
@@ -1437,7 +1437,7 @@ class _:
"""
@annotate(RestPat)
@annotate(RestPat, cfg = True)
class _:
"""
A RestPat. For example:
@@ -1564,7 +1564,7 @@ class _:
"""
@annotate(TryExpr)
@annotate(TryExpr, cfg = True)
class _:
"""
A TryExpr. For example:
@@ -1744,7 +1744,7 @@ class _:
"""
@annotate(WhileExpr)
@annotate(WhileExpr, cfg = True)
class _:
"""
A WhileExpr. For example: