Add conditional result keyword to mojo parser/lexer
This allows developers to specify result<T, E> in the IDL. E.g.: Method() => result<bool, bool>. This does not prevent developers from using 'result' as an identifier in parameter lists. E.g.: Echo(int result) => (int result). We rely on conditional lexing and embedded actions to allows context sensitive tokenization for the 'result' string. Under the 'responsetype' lex state, the lexer will recognize 'result' as a keyword. In any other lex state, 'result' results in a NAME token. This is not wired up to anything atm. The RESULT token is simply swallowed and treated as an empty response atm. Change-Id: I0c955db6f7be12a5b87d9cdf77423766709d7b44 Bug: 40841428 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6289176 Reviewed-by: Alex Gough <ajgo@chromium.org> Commit-Queue: Fred Shih <ffred@chromium.org> Cr-Commit-Position: refs/heads/main@{#1423387}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
adafea8932
commit
925d9a55ec
mojo
golden
corpus
generated
public
tools
mojom
11
mojo/golden/corpus/results.test-mojom
Normal file
11
mojo/golden/corpus/results.test-mojom
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
module golden;
|
||||
|
||||
struct ResultTestError {
|
||||
};
|
||||
|
||||
interface ResultInterface {
|
||||
Method(bool a) => result<bool, ResultTestError>;
|
||||
};
|
@ -0,0 +1,7 @@
|
||||
// results.test-mojom-converters.ts is auto generated by mojom_bindings_generator.py, do not edit
|
||||
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import {mojo} from '//resources/mojo/mojo/public/js/bindings.js';
|
||||
|
@ -0,0 +1,203 @@
|
||||
// results.test-mojom-webui.ts is auto generated by mojom_bindings_generator.py, do not edit
|
||||
|
||||
// Copyright 2020 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {mojo} from '//resources/mojo/mojo/public/js/bindings.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export class ResultInterfacePendingReceiver implements
|
||||
mojo.internal.interfaceSupport.PendingReceiver {
|
||||
|
||||
handle: mojo.internal.interfaceSupport.Endpoint;
|
||||
constructor(handle: MojoHandle|mojo.internal.interfaceSupport.Endpoint) {
|
||||
this.handle = mojo.internal.interfaceSupport.getEndpointForReceiver(handle);
|
||||
}
|
||||
|
||||
bindInBrowser(scope: string = 'context') {
|
||||
mojo.internal.interfaceSupport.bind(
|
||||
this.handle,
|
||||
'golden.ResultInterface',
|
||||
scope);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResultInterfaceInterface {
|
||||
method(a: boolean): void;
|
||||
}
|
||||
|
||||
export class ResultInterfaceRemote implements ResultInterfaceInterface {
|
||||
private proxy: mojo.internal.interfaceSupport.InterfaceRemoteBase<ResultInterfacePendingReceiver>;
|
||||
$: mojo.internal.interfaceSupport.InterfaceRemoteBaseWrapper<ResultInterfacePendingReceiver>;
|
||||
onConnectionError: mojo.internal.interfaceSupport.ConnectionErrorEventRouter;
|
||||
|
||||
constructor(
|
||||
handle?: MojoHandle|mojo.internal.interfaceSupport.Endpoint) {
|
||||
this.proxy =
|
||||
new mojo.internal.interfaceSupport.InterfaceRemoteBase(
|
||||
ResultInterfacePendingReceiver, handle);
|
||||
|
||||
this.$ = new mojo.internal.interfaceSupport.InterfaceRemoteBaseWrapper(this.proxy);
|
||||
|
||||
this.onConnectionError = this.proxy.getConnectionErrorEventRouter();
|
||||
}
|
||||
|
||||
method(
|
||||
a: boolean): void {
|
||||
this.proxy.sendMessage(
|
||||
0,
|
||||
ResultInterface_Method_ParamsSpec.$,
|
||||
null,
|
||||
[
|
||||
a
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An object which receives request messages for the ResultInterface
|
||||
* mojom interface. Must be constructed over an object which implements that
|
||||
* interface.
|
||||
*/
|
||||
export class ResultInterfaceReceiver {
|
||||
private helper_internal_: mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal<ResultInterfaceRemote>;
|
||||
|
||||
$: mojo.internal.interfaceSupport.InterfaceReceiverHelper<ResultInterfaceRemote>;
|
||||
|
||||
onConnectionError: mojo.internal.interfaceSupport.ConnectionErrorEventRouter;
|
||||
|
||||
|
||||
constructor(impl: ResultInterfaceInterface) {
|
||||
|
||||
this.helper_internal_ = new mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal(
|
||||
ResultInterfaceRemote);
|
||||
|
||||
this.$ = new mojo.internal.interfaceSupport.InterfaceReceiverHelper(this.helper_internal_);
|
||||
|
||||
|
||||
this.helper_internal_.registerHandler(
|
||||
0,
|
||||
ResultInterface_Method_ParamsSpec.$,
|
||||
null,
|
||||
impl.method.bind(impl));
|
||||
this.onConnectionError = this.helper_internal_.getConnectionErrorEventRouter();
|
||||
}
|
||||
}
|
||||
|
||||
export class ResultInterface {
|
||||
static get $interfaceName(): string {
|
||||
return "golden.ResultInterface";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a remote for this interface which sends messages to the browser.
|
||||
* The browser must have an interface request binder registered for this
|
||||
* interface and accessible to the calling document's frame.
|
||||
*/
|
||||
static getRemote(): ResultInterfaceRemote {
|
||||
let remote = new ResultInterfaceRemote;
|
||||
remote.$.bindNewPipeAndPassReceiver().bindInBrowser();
|
||||
return remote;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object which receives request messages for the ResultInterface
|
||||
* mojom interface and dispatches them as callbacks. One callback receiver exists
|
||||
* on this object for each message defined in the mojom interface, and each
|
||||
* receiver can have any number of listeners added to it.
|
||||
*/
|
||||
export class ResultInterfaceCallbackRouter {
|
||||
private helper_internal_: mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal<ResultInterfaceRemote>;
|
||||
$: mojo.internal.interfaceSupport.InterfaceReceiverHelper<ResultInterfaceRemote>;
|
||||
router_: mojo.internal.interfaceSupport.CallbackRouter;
|
||||
|
||||
method: mojo.internal.interfaceSupport.InterfaceCallbackReceiver;
|
||||
onConnectionError: mojo.internal.interfaceSupport.ConnectionErrorEventRouter;
|
||||
|
||||
constructor() {
|
||||
this.helper_internal_ = new mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal(
|
||||
ResultInterfaceRemote);
|
||||
|
||||
this.$ = new mojo.internal.interfaceSupport.InterfaceReceiverHelper(this.helper_internal_);
|
||||
|
||||
this.router_ = new mojo.internal.interfaceSupport.CallbackRouter;
|
||||
|
||||
this.method =
|
||||
new mojo.internal.interfaceSupport.InterfaceCallbackReceiver(
|
||||
this.router_);
|
||||
|
||||
this.helper_internal_.registerHandler(
|
||||
0,
|
||||
ResultInterface_Method_ParamsSpec.$,
|
||||
null,
|
||||
this.method.createReceiverHandler(false /* expectsResponse */));
|
||||
this.onConnectionError = this.helper_internal_.getConnectionErrorEventRouter();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id An ID returned by a prior call to addListener.
|
||||
* @return True iff the identified listener was found and removed.
|
||||
*/
|
||||
removeListener(id: number): boolean {
|
||||
return this.router_.removeListener(id);
|
||||
}
|
||||
}
|
||||
|
||||
export const ResultTestErrorSpec: { $: mojo.internal.MojomType } =
|
||||
{ $: {} as unknown as mojo.internal.MojomType };
|
||||
|
||||
export const ResultInterface_Method_ParamsSpec: { $: mojo.internal.MojomType } =
|
||||
{ $: {} as unknown as mojo.internal.MojomType };
|
||||
|
||||
|
||||
|
||||
|
||||
export interface ResultTestErrorMojoType {
|
||||
}
|
||||
|
||||
|
||||
export type ResultTestError = ResultTestErrorMojoType;
|
||||
mojo.internal.Struct<ResultTestErrorMojoType>(
|
||||
ResultTestErrorSpec.$,
|
||||
'ResultTestError',
|
||||
[
|
||||
],
|
||||
[[0, 8],]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export interface ResultInterface_Method_ParamsMojoType {
|
||||
a: boolean;
|
||||
}
|
||||
|
||||
|
||||
export type ResultInterface_Method_Params = ResultInterface_Method_ParamsMojoType;
|
||||
mojo.internal.Struct<ResultInterface_Method_ParamsMojoType>(
|
||||
ResultInterface_Method_ParamsSpec.$,
|
||||
'ResultInterface_Method_Params',
|
||||
[
|
||||
mojo.internal.StructField<ResultInterface_Method_ParamsMojoType, boolean>(
|
||||
'a', 0,
|
||||
0,
|
||||
mojo.internal.Bool,
|
||||
false,
|
||||
false /* nullable */,
|
||||
0,
|
||||
undefined,
|
||||
undefined,
|
||||
),
|
||||
],
|
||||
[[0, 16],]);
|
||||
|
||||
|
||||
|
@ -103,9 +103,21 @@ class Lexer:
|
||||
'COMMA',
|
||||
'PIPE', # |
|
||||
'AMPERSAND', # &
|
||||
'DOT' # , .
|
||||
'DOT', # , .
|
||||
|
||||
# Conditional keywords
|
||||
'RESULT',
|
||||
)
|
||||
|
||||
states = [
|
||||
# Lex state to parse method response type. This is because we use
|
||||
# 'result' as a keyword when declaring the return type of a method.
|
||||
# E.g.: FooMethod() => result<T, E>
|
||||
# This state is needed to disambiguate the keyword from an identifier in
|
||||
# the context of a response return type.
|
||||
('responsetype', 'inclusive'),
|
||||
]
|
||||
|
||||
##
|
||||
## Regexes for use in tokens
|
||||
##
|
||||
@ -197,6 +209,9 @@ class Lexer:
|
||||
|
||||
t_STRING_LITERAL = string_literal
|
||||
|
||||
# Conditional keywords
|
||||
t_responsetype_RESULT = r'result'
|
||||
|
||||
# The following floating and integer constants are defined as
|
||||
# functions to impose a strict order (otherwise, decimal
|
||||
# is placed before the others because its regex is longer,
|
||||
|
@ -159,9 +159,17 @@ class LexerTest(unittest.TestCase):
|
||||
_MakeLexToken("COMMA", ","))
|
||||
self.assertEqual(self._SingleTokenForInput("."), _MakeLexToken("DOT", "."))
|
||||
|
||||
def _TokensForInput(self, input_string):
|
||||
def testConditionalTokens(self):
|
||||
self.assertEqual(self._SingleTokenForInput("result"),
|
||||
_MakeLexToken("NAME", "result"))
|
||||
self.assertEqual(self._SingleTokenForInput("result", "responsetype"),
|
||||
_MakeLexToken("RESULT", "result"))
|
||||
|
||||
def _TokensForInput(self, input_string, state=None):
|
||||
"""Gets a list of tokens for the given input string."""
|
||||
lexer = self._zygote_lexer.clone()
|
||||
if state:
|
||||
lexer.begin(state)
|
||||
lexer.input(input_string)
|
||||
rv = []
|
||||
while True:
|
||||
@ -170,10 +178,10 @@ class LexerTest(unittest.TestCase):
|
||||
return rv
|
||||
rv.append(tok)
|
||||
|
||||
def _SingleTokenForInput(self, input_string):
|
||||
def _SingleTokenForInput(self, input_string, state=None):
|
||||
"""Gets the single token for the given input string. (Raises an exception if
|
||||
the input string does not result in exactly one token.)"""
|
||||
toks = self._TokensForInput(input_string)
|
||||
toks = self._TokensForInput(input_string, state)
|
||||
assert len(toks) == 1
|
||||
return toks[0]
|
||||
|
||||
|
@ -303,7 +303,30 @@ class Parser:
|
||||
p[0] = None
|
||||
|
||||
def p_response_2(self, p):
|
||||
"""response : RESPONSE LPAREN parameter_list RPAREN"""
|
||||
"""response : response_push_state RESPONSE response_type"""
|
||||
p[0] = p[3]
|
||||
|
||||
# Embedded action to disambiguate 'result' keyword from an name. A push
|
||||
# embedded action should be followed by a pop action before the response
|
||||
# type is reduced. Otherwise, 'result' will not be allowed as an identifier
|
||||
# name.
|
||||
def p_response_push_state(self, p):
|
||||
"""response_push_state :"""
|
||||
p.lexer.push_state('responsetype')
|
||||
p[0] = None
|
||||
|
||||
def p_response_pop_state(self, p):
|
||||
"""response_pop_state :"""
|
||||
p.lexer.pop_state()
|
||||
p[0] = None
|
||||
|
||||
def p_response_type_1(self, p):
|
||||
"""response_type : RESULT response_pop_state LANGLE typename COMMA typename RANGLE"""
|
||||
# TODO(crbug.com/40841428): implement result type.
|
||||
p[0] = None
|
||||
|
||||
def p_response_type_2(self, p):
|
||||
"""response_type : LPAREN response_pop_state parameter_list RPAREN"""
|
||||
p[0] = p[3]
|
||||
|
||||
def p_method(self, p):
|
||||
|
@ -920,6 +920,32 @@ class ParserTest(unittest.TestCase):
|
||||
])
|
||||
self.assertEqual(parser.Parse(source3, "my_file.mojom"), expected3)
|
||||
|
||||
source4 = """
|
||||
interface MyInterface {
|
||||
MyMethod(string a) => result<bool, bool>;
|
||||
};
|
||||
"""
|
||||
expected4 = ast.Mojom(
|
||||
None,
|
||||
ast.ImportList(),
|
||||
[
|
||||
ast.Interface(
|
||||
ast.Name('MyInterface'),
|
||||
None,
|
||||
ast.InterfaceBody(
|
||||
ast.Method(
|
||||
ast.Name('MyMethod'),
|
||||
None,
|
||||
None,
|
||||
ast.ParameterList(
|
||||
ast.Parameter(
|
||||
ast.Name('a'), None, None,
|
||||
ast.Typename(ast.Identifier('string')))),
|
||||
# TODO(crbug.com/40841428): put in an actual return.
|
||||
None)))
|
||||
])
|
||||
self.assertEqual(parser.Parse(source4, "my_file.mojom"), expected4)
|
||||
|
||||
def testInvalidMethods(self):
|
||||
"""Tests that invalid method declarations are correctly detected."""
|
||||
|
||||
|
Reference in New Issue
Block a user