Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C, keyword changes #9356

Merged
merged 1 commit into from Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES
Expand Up @@ -13,6 +13,10 @@ Deprecated
Features added
--------------

* C, add C23 keywords ``_Decimal32``, ``_Decimal64``, and ``_Decimal128``.
* #9354: C, add :confval:`c_extra_keywords` to allow user-defined keywords
during parsing.

Bugs fixed
----------

Expand All @@ -21,6 +25,8 @@ Bugs fixed
* #9313: LaTeX: complex table with merged cells broken since 4.0
* #9305: LaTeX: backslash may cause Improper discretionary list pdf build error
with Japanese engines
* #9354: C, remove special macro names from the keyword list.
See also :confval:`c_extra_keywords`.

Testing
--------
Expand Down
8 changes: 8 additions & 0 deletions doc/usage/configuration.rst
Expand Up @@ -2670,6 +2670,14 @@ Options for the C domain

.. versionadded:: 3.0

.. confval:: c_extra_keywords

A list of identifiers to be recognized as keywords by the C parser.
It defaults to ``['alignas', 'alignof', 'bool', 'complex', 'imaginary',
'noreturn', 'static_assert', 'thread_local']``.

.. versionadded:: 4.0.3

.. confval:: c_allow_pre_v3

A boolean (default ``False``) controlling whether to parse and try to
Expand Down
26 changes: 22 additions & 4 deletions sphinx/domains/c.py
Expand Up @@ -54,10 +54,15 @@
'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long',
'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct',
'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
'_Alignas', 'alignas', '_Alignof', 'alignof', '_Atomic', '_Bool', 'bool',
'_Complex', 'complex', '_Generic', '_Imaginary', 'imaginary',
'_Noreturn', 'noreturn', '_Static_assert', 'static_assert',
'_Thread_local', 'thread_local',
'_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex',
'_Decimal32', '_Decimal64', '_Decimal128',
'_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local',
]
# These are only keyword'y when the corresponding headers are included.
# They are used as default value for c_extra_keywords.
_macroKeywords = [
'alignas', 'alignof', 'bool', 'complex', 'imaginary', 'noreturn', 'static_assert',
'thread_local',
]

# these are ordered by preceedence
Expand Down Expand Up @@ -2535,6 +2540,12 @@ def _parse_nested_name(self) -> ASTNestedName:
if identifier in _keywords:
self.fail("Expected identifier in nested name, "
"got keyword: %s" % identifier)
if self.matched_text in self.config.c_extra_keywords:
msg = "Expected identifier, got user-defined keyword: %s." \
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ "Currently c_extra_keywords is %s."
self.fail(msg % (self.matched_text,
str(self.config.c_extra_keywords)))
ident = ASTIdentifier(identifier)
names.append(ident)

Expand Down Expand Up @@ -2711,6 +2722,12 @@ def _parse_declarator_name_suffix(
if self.matched_text in _keywords:
self.fail("Expected identifier, "
"got keyword: %s" % self.matched_text)
if self.matched_text in self.config.c_extra_keywords:
msg = "Expected identifier, got user-defined keyword: %s." \
+ " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ "Currently c_extra_keywords is %s."
self.fail(msg % (self.matched_text,
str(self.config.c_extra_keywords)))
identifier = ASTIdentifier(self.matched_text)
declId = ASTNestedName([identifier], rooted=False)
else:
Expand Down Expand Up @@ -3877,6 +3894,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_domain(CDomain)
app.add_config_value("c_id_attributes", [], 'env')
app.add_config_value("c_paren_attributes", [], 'env')
app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
app.add_post_transform(AliasTransform)

app.add_config_value("c_allow_pre_v3", False, 'env')
Expand Down
25 changes: 18 additions & 7 deletions tests/test_domain_c.py
Expand Up @@ -15,16 +15,20 @@

from sphinx import addnodes
from sphinx.addnodes import desc
from sphinx.domains.c import DefinitionError, DefinitionParser, Symbol, _id_prefix, _max_id
from sphinx.domains.c import (DefinitionError, DefinitionParser, Symbol, _id_prefix,
_macroKeywords, _max_id)
from sphinx.ext.intersphinx import load_mappings, normalize_intersphinx_mapping
from sphinx.testing import restructuredtext
from sphinx.testing.util import assert_node


class Config:
c_id_attributes = ["id_attr", 'LIGHTGBM_C_EXPORT']
c_paren_attributes = ["paren_attr"]
c_extra_keywords = _macroKeywords


def parse(name, string):
class Config:
c_id_attributes = ["id_attr", 'LIGHTGBM_C_EXPORT']
c_paren_attributes = ["paren_attr"]
parser = DefinitionParser(string, location=None, config=Config())
parser.allowFallbackExpressionParsing = False
ast = parser.parse_declaration(name, name)
Expand Down Expand Up @@ -114,9 +118,6 @@ def check(name, input, idDict, output=None, key=None, asTextOutput=None):

def test_expressions():
def exprCheck(expr, output=None):
class Config:
c_id_attributes = ["id_attr"]
c_paren_attributes = ["paren_attr"]
parser = DefinitionParser(expr, location=None, config=Config())
parser.allowFallbackExpressionParsing = False
ast = parser.parse_expression()
Expand Down Expand Up @@ -522,6 +523,16 @@ def test_attributes():
check('function', 'LIGHTGBM_C_EXPORT int LGBM_BoosterFree(int handle)',
{1: 'LGBM_BoosterFree'})


def test_extra_keywords():
with pytest.raises(DefinitionError,
match='Expected identifier, got user-defined keyword: complex.'):
parse('function', 'void f(int complex)')
with pytest.raises(DefinitionError,
match='Expected identifier, got user-defined keyword: complex.'):
parse('function', 'void complex(void)')


# def test_print():
# # used for getting all the ids out for checking
# for a in ids:
Expand Down