Merge pull request #17822 from github/tausbn/python-more-parser-fixes

Python: A few more parser fixes
This commit is contained in:
Taus
2024-10-30 13:47:10 +01:00
committed by GitHub
15 changed files with 51603 additions and 53873 deletions

View File

@@ -97,9 +97,27 @@ class AstDumper(object):
class StdoutLogger(logging.Logger): class StdoutLogger(logging.Logger):
error_count = 0
def log(self, level, fmt, *args): def log(self, level, fmt, *args):
sys.stdout.write(fmt % args + "\n") sys.stdout.write(fmt % args + "\n")
def info(self, fmt, *args):
self.log(logging.INFO, fmt, *args)
def warn(self, fmt, *args):
self.log(logging.WARN, fmt, *args)
self.error_count += 1
def error(self, fmt, *args):
self.log(logging.ERROR, fmt, *args)
self.error_count += 1
def had_errors(self):
return self.error_count > 0
def reset_error_count(self):
self.error_count = 0
def old_parser(inputfile, logger): def old_parser(inputfile, logger):
mod = PythonSourceModule(None, inputfile, logger) mod = PythonSourceModule(None, inputfile, logger)
logger.close() logger.close()

View File

@@ -440,7 +440,7 @@ def concatenate_stringparts(stringparts, logger):
try: try:
return "".join(decode_str(stringpart.s) for stringpart in stringparts) return "".join(decode_str(stringpart.s) for stringpart in stringparts)
except Exception as ex: except Exception as ex:
logger.error("Unable to concatenate string %s getting error %s", stringparts, ex) logger.error("Unable to concatenate string {} getting error {}".format(stringparts, ex))
return stringparts[0].s return stringparts[0].s

View File

@@ -14,4 +14,6 @@ x, y = z, w = 3, 4
s, *t = u s, *t = u
[v, *w] = x
o,p, = q,r, o,p, = q,r,

View File

@@ -55,3 +55,13 @@
t = tuple(x for y in z) t = tuple(x for y in z)
[( t, ) for v in w] [( t, ) for v in w]
[# comment
a for b in c # comment
# comment
] # comment
[# comment
d for e in f if g # comment
# comment
] # comment

View File

@@ -1,4 +1,4 @@
Module: [1, 0] - [22, 0] Module: [1, 0] - [27, 0]
body: [ body: [
Try: [1, 0] - [1, 4] Try: [1, 0] - [1, 4]
body: [ body: [
@@ -133,4 +133,24 @@ Module: [1, 0] - [22, 0]
variable: Variable('v', None) variable: Variable('v', None)
ctx: Load ctx: Load
] ]
Try: [23, 0] - [23, 4]
body: [
Pass: [24, 4] - [24, 8]
]
orelse: []
handlers: [
ExceptGroupStmt: [25, 0] - [26, 8]
type:
Name: [25, 8] - [25, 11]
variable: Variable('foo', None)
ctx: Load
name:
Name: [25, 15] - [25, 16]
variable: Variable('e', None)
ctx: Store
body: [
Pass: [26, 4] - [26, 8]
]
]
finalbody: []
] ]

View File

@@ -19,3 +19,8 @@ else:
finally: finally:
u u
v v
try:
pass
except *foo as e:
pass

View File

@@ -0,0 +1,41 @@
Module: [1, 0] - [3, 0]
body: [
Assign: [1, 0] - [1, 42]
targets: [
Name: [1, 4] - [1, 26]
variable: Variable('tuple_typed_list_splat', None)
ctx: Store
]
value:
FunctionExpr: [1, 0] - [1, 42]
name: 'tuple_typed_list_splat'
args:
arguments
defaults: []
kw_defaults: []
annotations: []
varargannotation:
Starred: [1, 35] - [1, 40]
value:
Name: [1, 36] - [1, 40]
variable: Variable('ARGS', None)
ctx: Load
ctx: Load
kwargannotation: None
kw_annotations: []
returns: None
inner_scope:
Function: [1, 0] - [1, 42]
name: 'tuple_typed_list_splat'
type_parameters: []
args: []
vararg:
Name: [1, 28] - [1, 32]
variable: Variable('args', None)
ctx: Param
kwonlyargs: []
kwarg: None
body: [
Pass: [2, 4] - [2, 8]
]
]

View File

@@ -0,0 +1,2 @@
def tuple_typed_list_splat(*args : *ARGS):
pass

View File

@@ -49,6 +49,8 @@ class ParserTest(unittest.TestCase):
diff = e.output diff = e.output
if diff: if diff:
pytest.fail(diff.decode("utf-8")) pytest.fail(diff.decode("utf-8"))
self.check_for_stdout_errors(logger)
self.assertEqual(self.capsys.readouterr().err, "") self.assertEqual(self.capsys.readouterr().err, "")
os.remove(oldfile) os.remove(oldfile)
os.remove(newfile) os.remove(newfile)
@@ -84,9 +86,15 @@ class ParserTest(unittest.TestCase):
diff = e.output diff = e.output
if diff: if diff:
pytest.fail(diff.decode("utf-8")) pytest.fail(diff.decode("utf-8"))
self.check_for_stdout_errors(logger)
self.assertEqual(self.capsys.readouterr().err, "") self.assertEqual(self.capsys.readouterr().err, "")
os.remove(actual) os.remove(actual)
def check_for_stdout_errors(self, logger):
if logger.had_errors():
logger.reset_error_count()
pytest.fail("Errors/warnings were logged to stdout during testing.")
def setup_tests(): def setup_tests():
test_folder = os.path.join(os.path.dirname(__file__), "parser") test_folder = os.path.join(os.path.dirname(__file__), "parser")

View File

@@ -25,6 +25,9 @@
[ (expression_list) (tuple) (tuple_pattern) (pattern_list) ] @tuple [ (expression_list) (tuple) (tuple_pattern) (pattern_list) ] @tuple
{ let @tuple.node = (ast-node @tuple "Tuple") } { let @tuple.node = (ast-node @tuple "Tuple") }
(list_pattern) @list
{ let @list.node = (ast-node @list "List") }
(call) @call { let @call.node = (ast-node @call "Call") } (call) @call { let @call.node = (ast-node @call "Call") }
(for_statement) @for (for_statement) @for
@@ -1059,30 +1062,38 @@
let @genexpr.result = tuple let @genexpr.result = tuple
} }
; For the final `if` clause, we need to hook it up with the `yield` expression and with its associated `for` clause. ; For the final clause, we need to hook it up with the rest of the expression.
; If it's an `if` clause, we need to hook it up with the `yield` expression and with its associated
; `for` clause.
; If it's a `for` clause, we only need to create and hook it up with the `yield` expression.
;
; It would be tempting to use anchors here, but they just don't work. In particular, an anchor of
; the form `. (comment)* . )` (which would be needed in order to handle the case where there are
; comments after the last clause) cause the `tree-sitter` query engine to match _all_ clauses, not
; just the last one.
; Instead, we gather up all clauses in a list (these will be in the order they appear in the source
; code), and extract the last element using a custom Rust function.
[ [
(generator_expression (generator_expression
body: (_) @body body: (_) @body
(if_clause) @last [(if_clause) (for_in_clause)]+ @last_candidates
.
) @genexpr ) @genexpr
(list_comprehension (list_comprehension
body: (_) @body body: (_) @body
(if_clause) @last [(if_clause) (for_in_clause)]+ @last_candidates
.
) @genexpr ) @genexpr
(set_comprehension (set_comprehension
body: (_) @body body: (_) @body
(if_clause) @last [(if_clause) (for_in_clause)]+ @last_candidates
.
) @genexpr ) @genexpr
(dictionary_comprehension (dictionary_comprehension
body: (_) @body body: (_) @body
(if_clause) @last [(if_clause) (for_in_clause)]+ @last_candidates
.
) @genexpr ) @genexpr
] ]
{ {
let last = (get-last-element @last_candidates)
let expr = (ast-node @body "Expr") let expr = (ast-node @body "Expr")
let yield = (ast-node @body "Yield") let yield = (ast-node @body "Yield")
@@ -1093,50 +1104,19 @@
attr (yield) value = @genexpr.result attr (yield) value = @genexpr.result
attr (@body.node) ctx = "load" attr (@body.node) ctx = "load"
edge @last.first_if -> expr
attr (@last.first_if -> expr) body = 0
; Hook up this `if` clause with its `for` clause if (instance-of last "if_clause") {
edge @last.for -> @last.node edge last.first_if -> expr
attr (@last.for -> @last.node) body = 0 attr (last.first_if -> expr) body = 0
}
; If the last clause is a `for`, we only have to create and hook up the `yield` expression. ; Hook up this `if` clause with its `for` clause
[ edge last.for -> last.node
(generator_expression attr (last.for -> last.node) body = 0
body: (_) @body } else {
(for_in_clause) @last ; If the last clause is a `for`, we only have to create and hook up the `yield` expression.
. edge last.node -> expr
) @genexpr attr (last.node -> expr) body = 0
(list_comprehension }
body: (_) @body
(for_in_clause) @last
.
) @genexpr
(set_comprehension
body: (_) @body
(for_in_clause) @last
.
) @genexpr
(dictionary_comprehension
body: (_) @body
(for_in_clause) @last
.
) @genexpr
]
{
let expr = (ast-node @body "Expr")
let yield = (ast-node @body "Yield")
let @genexpr.expr = expr
let @genexpr.yield = yield
attr (expr) value = yield
attr (yield) value = @genexpr.result
attr (@body.node) ctx = "load"
edge @last.node -> expr
attr (@last.node -> expr) body = 0
} }
; For whatever reason, we do not consider parentheses around the yielded expression if they are present, so ; For whatever reason, we do not consider parentheses around the yielded expression if they are present, so
@@ -3180,11 +3160,11 @@
(typed_parameter (typed_parameter
(identifier) @name (identifier) @name
. .
type: (type (expression) @type) type: (type (_) @type)
) )
(typed_default_parameter (typed_default_parameter
name: (_) @name name: (_) @name
type: (type (expression) @type) type: (type (_) @type)
value: (_) @value value: (_) @value
) )
] @param ] @param
@@ -3239,7 +3219,7 @@
(list_splat_pattern vararg: (_) @name) @starred (list_splat_pattern vararg: (_) @name) @starred
(typed_parameter (typed_parameter
(list_splat_pattern vararg: (_) @name) @starred (list_splat_pattern vararg: (_) @name) @starred
type: (type (expression) @type) type: (type (_) @type)
) )
] ]
) @params ) @params
@@ -3256,7 +3236,7 @@
; Return type ; Return type
(function_definition (function_definition
return_type: (type (expression) @type) return_type: (type (_) @type)
) @funcdef ) @funcdef
{ {
attr (@funcdef.funcexpr) returns = @type.node attr (@funcdef.funcexpr) returns = @type.node
@@ -3270,7 +3250,7 @@
(dictionary_splat_pattern kwarg: (identifier) @name) (dictionary_splat_pattern kwarg: (identifier) @name)
(typed_parameter (typed_parameter
(dictionary_splat_pattern kwarg: (identifier) @name) (dictionary_splat_pattern kwarg: (identifier) @name)
type: (type (expression) @type) type: (type (_) @type)
) )
] ]
) @params ) @params
@@ -3447,6 +3427,9 @@
; Left hand side of an assignment such as `foo, bar = ...` ; Left hand side of an assignment such as `foo, bar = ...`
(pattern_list element: (_) @elt) @parent (pattern_list element: (_) @elt) @parent
; Left hand side of an assignment such as `[foo, bar] = ...`
(list_pattern element: (_) @elt) @parent
; An unadorned tuple (such as in `x = y, z`) ; An unadorned tuple (such as in `x = y, z`)
(expression_list element: (_) @elt) @parent (expression_list element: (_) @elt) @parent
@@ -3483,6 +3466,7 @@
(tuple element: (_) @elt) (tuple element: (_) @elt)
(tuple_pattern element: (_) @elt) (tuple_pattern element: (_) @elt)
(pattern_list element: (_) @elt) (pattern_list element: (_) @elt)
(list_pattern element: (_) @elt)
(expression_list element: (_) @elt) (expression_list element: (_) @elt)
(parenthesized_expression inner: (_) @elt) (parenthesized_expression inner: (_) @elt)
(set element: (_) @elt) (set element: (_) @elt)

View File

@@ -463,6 +463,22 @@ pub mod extra_functions {
Ok(Value::Integer(left % right)) Ok(Value::Integer(left % right))
} }
} }
pub struct GetLastElement;
impl Function for GetLastElement {
fn call(
&self,
_graph: &mut Graph,
_source: &str,
parameters: &mut dyn Parameters,
) -> Result<Value, ExecutionError> {
let list = parameters.param()?.into_list()?;
parameters.finish()?;
let last = list.last().unwrap_or(&Value::Null).clone();
Ok(last)
}
}
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@@ -562,6 +578,12 @@ fn main() -> Result<()> {
); );
functions.add(Identifier::from("mod"), extra_functions::Modulo); functions.add(Identifier::from("mod"), extra_functions::Modulo);
functions.add(
Identifier::from("get-last-element"),
extra_functions::GetLastElement,
);
let globals = Variables::new(); let globals = Variables::new();
let mut config = ExecutionConfig::new(&mut functions, &globals).lazy(false); let mut config = ExecutionConfig::new(&mut functions, &globals).lazy(false);
let graph = file let graph = file

View File

@@ -309,7 +309,8 @@ module.exports = grammar({
), ),
except_group_clause: $ => seq( except_group_clause: $ => seq(
'except*', 'except',
'*',
seq( seq(
field('type', $.expression), field('type', $.expression),
optional(seq( optional(seq(
@@ -963,7 +964,7 @@ module.exports = grammar({
field('type', $.type) field('type', $.type)
)), )),
type: $ => $.expression, type: $ => choice($.list_splat, $.expression),
keyword_argument: $ => seq( keyword_argument: $ => seq(
field('name', choice($.identifier, $.keyword_identifier)), field('name', choice($.identifier, $.keyword_identifier)),

View File

@@ -1169,7 +1169,11 @@
"members": [ "members": [
{ {
"type": "STRING", "type": "STRING",
"value": "except*" "value": "except"
},
{
"type": "STRING",
"value": "*"
}, },
{ {
"type": "SEQ", "type": "SEQ",
@@ -5289,8 +5293,17 @@
} }
}, },
"type": { "type": {
"type": "SYMBOL", "type": "CHOICE",
"name": "expression" "members": [
{
"type": "SYMBOL",
"name": "list_splat"
},
{
"type": "SYMBOL",
"name": "expression"
}
]
}, },
"keyword_argument": { "keyword_argument": {
"type": "SEQ", "type": "SEQ",

View File

@@ -3347,6 +3347,10 @@
{ {
"type": "expression", "type": "expression",
"named": true "named": true
},
{
"type": "list_splat",
"named": true
} }
] ]
} }
@@ -3960,10 +3964,6 @@
"type": "except", "type": "except",
"named": false "named": false
}, },
{
"type": "except*",
"named": false
},
{ {
"type": "exec", "type": "exec",
"named": false "named": false

File diff suppressed because it is too large Load Diff