From 33a7434e95ef3a5bec6f547f3b556e182bb78df2 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 17 Feb 2025 00:36:16 +0100 Subject: [PATCH 01/11] feat: add support for c++ concepts --- .../printers/default/__init__.py | 1 + .../printers/default/classprinter.py | 2 + .../printers/default/conceptprinter.py | 25 ++++ .../printers/default/defaultprinter.py | 4 +- .../printers/default/functionprinter.py | 2 + .../default/templateparameterprinter.py | 13 +- src/devana/syntax_abstraction/conceptinfo.py | 136 ++++++++++++++++++ .../syntax_abstraction/namespaceinfo.py | 3 +- .../organizers/sourcefile.py | 3 +- src/devana/syntax_abstraction/templateinfo.py | 78 ++++++++-- src/devana/syntax_abstraction/using.py | 4 +- 11 files changed, 252 insertions(+), 19 deletions(-) create mode 100644 src/devana/code_generation/printers/default/conceptprinter.py create mode 100644 src/devana/syntax_abstraction/conceptinfo.py diff --git a/src/devana/code_generation/printers/default/__init__.py b/src/devana/code_generation/printers/default/__init__.py index 8b52268..0649d16 100644 --- a/src/devana/code_generation/printers/default/__init__.py +++ b/src/devana/code_generation/printers/default/__init__.py @@ -23,3 +23,4 @@ from .functionprinter import FunctionPrinter from .functiontypeprinter import FunctionTypePrinter from .attributeprinter import AttributePrinter, AttributeDeclarationPrinter +from .conceptprinter import ConceptPrinter diff --git a/src/devana/code_generation/printers/default/classprinter.py b/src/devana/code_generation/printers/default/classprinter.py index 2baa5de..e7e364d 100644 --- a/src/devana/code_generation/printers/default/classprinter.py +++ b/src/devana/code_generation/printers/default/classprinter.py @@ -181,6 +181,8 @@ def print(self, source: ClassInfo, config: Optional[PrinterConfiguration] = None parameters.append(self.printer_dispatcher.print(p, config, source)) parameters = ','.join(parameters) template_prefix = f"template<{parameters}>" + if source.template.requires: + template_prefix += f" requires {source.template.requires}" specialisation_values = [] diff --git a/src/devana/code_generation/printers/default/conceptprinter.py b/src/devana/code_generation/printers/default/conceptprinter.py new file mode 100644 index 0000000..15b6169 --- /dev/null +++ b/src/devana/code_generation/printers/default/conceptprinter.py @@ -0,0 +1,25 @@ +from typing import Optional +from devana.code_generation.printers.icodeprinter import ICodePrinter +from devana.syntax_abstraction.conceptinfo import ConceptInfo +from devana.code_generation.printers.dispatcherinjectable import DispatcherInjectable +from devana.code_generation.printers.configuration import PrinterConfiguration +from devana.code_generation.printers.formatter import Formatter + + +class ConceptPrinter(ICodePrinter, DispatcherInjectable): + """Printer for concept declaration.""" + + def print(self, source: ConceptInfo, config: Optional[PrinterConfiguration] = None, + context: Optional = None) -> str: + if config is None: + config = PrinterConfiguration() + formatter = Formatter(config) + parameters = [] + for p in source.template.parameters: + parameters.append(self.printer_dispatcher.print(p, config, source)) + parameters = ', '.join(parameters) + template_prefix = f"template<{parameters}>" + formatter.print_line(template_prefix) + + formatter.print_line(f"concept {source.name} = {source.body};") + return formatter.text diff --git a/src/devana/code_generation/printers/default/defaultprinter.py b/src/devana/code_generation/printers/default/defaultprinter.py index 7ea58ac..91f236f 100644 --- a/src/devana/code_generation/printers/default/defaultprinter.py +++ b/src/devana/code_generation/printers/default/defaultprinter.py @@ -14,6 +14,8 @@ from devana.code_generation.printers.default.commentprinter import CommentPrinter from devana.code_generation.printers.default.functiontypeprinter import FunctionTypePrinter from devana.code_generation.printers.default.stubtypeprinter import StubTypePrinter +from devana.code_generation.printers.default.conceptprinter import ConceptPrinter +from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.syntax_abstraction.classinfo import ClassInfo from devana.syntax_abstraction.templateinfo import GenericTypeParameter from devana.syntax_abstraction.typedefinfo import TypedefInfo @@ -72,5 +74,5 @@ def create_default_printer() -> CodePrinter: printer.register(StubTypePrinter, StubType) printer.register(AttributePrinter, Attribute) printer.register(AttributeDeclarationPrinter, AttributeDeclaration) - + printer.register(ConceptPrinter, ConceptInfo) return printer diff --git a/src/devana/code_generation/printers/default/functionprinter.py b/src/devana/code_generation/printers/default/functionprinter.py index 0f71693..99dd176 100644 --- a/src/devana/code_generation/printers/default/functionprinter.py +++ b/src/devana/code_generation/printers/default/functionprinter.py @@ -41,6 +41,8 @@ def print(self, source: FunctionInfo, config: Optional[PrinterConfiguration] = N parameters.append(self.printer_dispatcher.print(p, config, source)) parameters = ','.join(parameters) template_prefix = f"template<{parameters}>" + if source.template.requires: + template_prefix += f" requires {source.template.requires}" specialisation_values = [] diff --git a/src/devana/code_generation/printers/default/templateparameterprinter.py b/src/devana/code_generation/printers/default/templateparameterprinter.py index 44c3d09..b07adbe 100644 --- a/src/devana/code_generation/printers/default/templateparameterprinter.py +++ b/src/devana/code_generation/printers/default/templateparameterprinter.py @@ -1,14 +1,25 @@ from devana.code_generation.printers.icodeprinter import ICodePrinter from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.code_generation.printers.dispatcherinjectable import DispatcherInjectable +from devana.code_generation.printers.default.typeexpressionprinter import TypeExpressionPrinter class TemplateParameterPrinter(ICodePrinter, DispatcherInjectable): """Printer for template parameter.""" def print(self, source: TemplateInfo.TemplateParameter, _1=None, _2=None) -> str: + if isinstance(source.specifier, str): + text = f"{source.specifier} {source.name}" + else: + if len(source.specifier.parameters) == 0: + text = f"{source.specifier.name} {source.name}" + else: + parameters = [] + for p in source.specifier.parameters: + parameters.append(self.printer_dispatcher.print(p)) + parameters = ', '.join(parameters) + text = f"{source.specifier.name}<{parameters}> {source.name}" - text = f"{source.specifier} {source.name}" if source.is_variadic: return f"{text}..." if source.default_value: diff --git a/src/devana/syntax_abstraction/conceptinfo.py b/src/devana/syntax_abstraction/conceptinfo.py new file mode 100644 index 0000000..3bff14a --- /dev/null +++ b/src/devana/syntax_abstraction/conceptinfo.py @@ -0,0 +1,136 @@ +from devana.syntax_abstraction.organizers.codecontainer import CodeContainer +from devana.syntax_abstraction.typeexpression import TypeExpression +from devana.syntax_abstraction.templateinfo import TemplateInfo +from devana.syntax_abstraction.codepiece import CodePiece +from devana.syntax_abstraction.organizers.lexicon import Lexicon +from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.utility.lazy import LazyNotInit, lazy_invoke +from devana.utility.init_params import init_params +from devana.utility.errors import ParserError + +from clang.cindex import CursorKind, Cursor +from typing import Optional, List + + +class ConceptInfo(CodeContainer): + + def __init__(self, cursor: Optional[Cursor] = None, parent: Optional[CodeContainer] = None): + super().__init__(cursor, parent) + self._cursor = cursor + self._parent = parent + if cursor is None: + self._name = "DefaultConcept" + self._body = "true" + self._template = TemplateInfo.from_params(parameters=[ + TemplateInfo.TemplateParameter.create_default() + ]) + self._parameters = [] + else: + if not self.is_cursor_valid(cursor): + raise ParserError(f"It is not a valid type cursor: {cursor.kind}.") + self._name = LazyNotInit + self._body = LazyNotInit + self._template = LazyNotInit + self._parameters = LazyNotInit + self._lexicon = Lexicon.create(self) + + def __repr__(self): + return f"{type(self).__name__}:{self.name} ({super().__repr__()})" + + @classmethod + def create_default(cls, parent: Optional = None) -> "ConceptInfo": + return cls(None, parent) + + @classmethod + def from_cursor(cls, cursor: Cursor, parent: Optional = None) -> Optional["ConceptInfo"]: + if cls.is_cursor_valid(cursor): + return cls(cursor, parent) + return None + + @classmethod + @init_params(skip={"parent"}) + def from_params( + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + body: Optional[str] = None, + template: Optional[TemplateInfo] = None, + parameters: Optional[List["TypeExpression"]] = None, + lexicon: Optional[Lexicon] = None + ) -> "ConceptInfo": + return cls(None, parent) + + @staticmethod + def is_cursor_valid(cursor: Cursor) -> bool: + return cursor.kind == CursorKind.CONCEPT_DECL + + @property + def parent(self) -> CodeContainer: + """Structural parent element like file, namespace or class.""" + return self._parent + + @property + @lazy_invoke + def name(self) -> str: + self._name = self._cursor.spelling + return self._name + + @name.setter + def name(self, value) -> None: + self._name = value + + @property + @lazy_invoke + def template(self) -> TemplateInfo: + self._template = TemplateInfo.from_cursor(self._cursor) + return self._template + + @template.setter + def template(self, value: TemplateInfo) -> None: + self._template = value + + @property + @lazy_invoke + def body(self) -> str: + self._body = "" + for child in self._cursor.get_children(): + if child.kind != CursorKind.TEMPLATE_TYPE_PARAMETER: + self._body = CodePiece(child).text + break + return self._body + + @body.setter + def body(self, value: str) -> None: + self._body = value + + @property + @lazy_invoke + def parameters(self) -> List["TypeExpression"]: + """Retrieves the concept parameters '<...>'.""" + if not isinstance(self.parent, TemplateInfo.TemplateParameter): + return [] + # Probably without a cursor from the parent it will not be possible to extract it. + # I get a mental breakdown every time I see the number -1, when i want to extract parameters in the normal way. + # Fuck it for now. + return [] + + @parameters.setter + def parameters(self, value: List["TypeExpression"]) -> None: + self._parameters = value + + @property + def is_specifier(self) -> bool: + """Determines whether this ConceptInfo instance is acting as a template specifier.""" + return isinstance(self.parent, TemplateInfo.TemplateParameter) + + @property + def lexicon(self): + return self._lexicon + + @lexicon.setter + def lexicon(self, value): + self._lexicon = value + + @property + def _content_types(self) -> List: + return [ConceptInfo] diff --git a/src/devana/syntax_abstraction/namespaceinfo.py b/src/devana/syntax_abstraction/namespaceinfo.py index 59c1124..7a0158a 100644 --- a/src/devana/syntax_abstraction/namespaceinfo.py +++ b/src/devana/syntax_abstraction/namespaceinfo.py @@ -99,8 +99,9 @@ def _content_types(self) -> List: from devana.syntax_abstraction.variable import GlobalVariable from devana.syntax_abstraction.unioninfo import UnionInfo from devana.syntax_abstraction.externc import ExternC + from devana.syntax_abstraction.conceptinfo import ConceptInfo types = [FunctionInfo, NamespaceInfo, UsingNamespace, ClassInfo, EnumInfo, TypedefInfo, MethodInfo, UnionInfo, - GlobalVariable, ExternC, Using] + GlobalVariable, ExternC, Using, ConceptInfo] return types def __repr__(self): diff --git a/src/devana/syntax_abstraction/organizers/sourcefile.py b/src/devana/syntax_abstraction/organizers/sourcefile.py index 83733bb..8457b8e 100644 --- a/src/devana/syntax_abstraction/organizers/sourcefile.py +++ b/src/devana/syntax_abstraction/organizers/sourcefile.py @@ -392,8 +392,9 @@ def _content_types(self) -> List: from devana.syntax_abstraction.variable import GlobalVariable from devana.syntax_abstraction.externc import ExternC from devana.syntax_abstraction.using import Using + from devana.syntax_abstraction.conceptinfo import ConceptInfo types = [ClassInfo, UnionInfo, FunctionInfo, EnumInfo, TypedefInfo, NamespaceInfo, UsingNamespace, - MethodInfo, GlobalVariable, ExternC, Using] + MethodInfo, GlobalVariable, ExternC, Using, ConceptInfo] return types def _create_content(self) -> List[Any]: diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 5531de9..9f48ed4 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -1,6 +1,7 @@ import re +from ctypes import c_uint from pathlib import Path -from typing import Optional, List, Union, Tuple, Any +from typing import Optional, List, Union, Tuple, Any, TYPE_CHECKING, Iterable from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression, TypeModification @@ -13,6 +14,10 @@ from devana.syntax_abstraction.syntax import ISyntaxElement from devana.configuration import Configuration +if TYPE_CHECKING: + from devana.syntax_abstraction.conceptinfo import ConceptInfo + + class GenericTypeParameter(ISyntaxElement): """An unresolved generic template parameter, known idiomatically in C++ as T.""" @@ -31,13 +36,13 @@ def name(self, value): @staticmethod def from_cursor(type_c, cursor: cindex.Type, parent: Optional = None) -> Optional["GenericTypeParameter"]: if type_c.kind == cindex.TypeKind.UNEXPOSED: - if type_c.get_num_template_arguments() > 0: - return None if hasattr(cursor, 'get_children'): for c in cursor.get_children(): if c.kind == cindex.CursorKind.TYPE_REF: return GenericTypeParameter(c.type.spelling, parent) elif c.kind == cindex.CursorKind.TEMPLATE_REF: + if getattr(c, "referenced", None) and c.referenced.kind == cindex.CursorKind.CONCEPT_DECL: + return GenericTypeParameter(type_c.spelling, parent) return None text = type_c.spelling if "::" in text: @@ -57,10 +62,11 @@ def __repr__(self): class TemplateInfo(IBasicCreatable, ICursorValidate, ISyntaxElement): """General template syntax information abut template definition.""" - class TemplateParameter(IBasicCreatable, ICursorValidate, ISyntaxElement): + class TemplateParameter(CodeContainer): """A description of the generic component for the type/function claim.""" def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): + super().__init__(cursor, parent) self._cursor = cursor self._parent = parent if cursor is None: @@ -86,7 +92,7 @@ def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParame def from_params( # pylint: disable=unused-argument cls, parent: Optional[ISyntaxElement] = None, - specifier: Optional[str] = None, + specifier: Optional[Union[str, "ConceptInfo"]] = None, name: Optional[str] = None, default_value: Optional[str] = None, is_variadic: Optional[bool] = None, @@ -106,15 +112,27 @@ def is_cursor_valid(cursor: cindex.Cursor) -> bool: @property @lazy_invoke - def specifier(self) -> str: - """Keyword before name.""" + def specifier(self) -> Union["ConceptInfo", str]: + """Keyword or ConceptInfo instance preceding the name.""" + from devana.syntax_abstraction.conceptinfo import ConceptInfo + + cursors = filter( + lambda c: c.kind == cindex.CursorKind.TEMPLATE_REF and c.referenced, + self._cursor.get_children() + ) + for cursor in cursors: + if maybe_concept := ConceptInfo.from_cursor(cursor=cursor.referenced, parent=self): + self._specifier = maybe_concept + return self._specifier + self._specifier = "class" if CodePiece(self._cursor).text.find("class ") != -1 else "typename" return self._specifier @specifier.setter - def specifier(self, value): - if value not in ("class", "typename"): - raise ValueError("Only class or typename specifier is allowed.") + def specifier(self, value: Union["ConceptInfo", str]): + from devana.syntax_abstraction.conceptinfo import ConceptInfo + if not isinstance(value, ConceptInfo) and value not in ("class", "typename"): + raise ValueError("Specifier must be class, typename, or an instance of ConceptInfo.") self._specifier = value @property @@ -172,6 +190,11 @@ def __eq__(self, other): return False return self.name == other.name and self.is_variadic == other.is_variadic + @property + def _content_types(self) -> List: + from devana.syntax_abstraction.conceptinfo import ConceptInfo + return [ConceptInfo] + def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): self._cursor = cursor self._parent = parent @@ -181,6 +204,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No self._parameters = [] self._is_empty = True self._is_variadic = False + self._requires = None else: if not self.is_cursor_valid(cursor): raise ParserError("Template parameter expect FUNCTION_TEMPLATE cursor kind.") @@ -189,6 +213,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No self._parameters = LazyNotInit self._is_empty = LazyNotInit self._is_variadic = LazyNotInit + self._requires = LazyNotInit self._lexicon = Lexicon.create(self) @classmethod @@ -205,6 +230,7 @@ def from_params( # pylint: disable=unused-argument parameters: Optional[List[TemplateParameter]] = None, is_empty: Optional[bool] = None, lexicon: Optional[Lexicon] = None, + requires: Optional[str] = None ) -> "TemplateInfo": return cls(None, parent) @@ -216,10 +242,15 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: - return (not (cursor.kind != cindex.CursorKind.FUNCTION_TEMPLATE) or not ( - cursor.kind != cindex.CursorKind.CLASS_TEMPLATE) or not ( - cursor.kind != cindex.CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION) or - re.search(r"template\s*<>", CodePiece(cursor).text)) + valid_cursors = ( + cindex.CursorKind.FUNCTION_TEMPLATE, + cindex.CursorKind.CLASS_TEMPLATE, + cindex.CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION, + cindex.CursorKind.CONCEPT_DECL + ) + return cursor.kind in valid_cursors or re.search( + r"template\s*<>", CodePiece(cursor).text + ) is not None @property @lazy_invoke @@ -428,3 +459,22 @@ def lexicon(self, value): @property def parent(self) -> ISyntaxElement: return self._parent + + @property + @lazy_invoke + def requires(self) -> Optional[str]: + """Extracts the constraint expression from the 'requires' clause.""" + + # Eventually I'd like to go for the [ConceptInfo | str] list here, + # but that's going to be a bit of work, because clang gives little information here, + # and when it does, it's in a snide way. + # Additionally, this property should be in classes/methods/functions etc. I originally assumed template == requires, which was a mistake. + self._requires = None + match = re.search(r"(?<=requires\s)([^\n{}]+)", CodePiece(self._cursor).text) + if match: + self._requires = match.group().strip() + return self._requires + + @requires.setter + def requires(self, value: Optional[str]) -> None: + self._requires = value diff --git a/src/devana/syntax_abstraction/using.py b/src/devana/syntax_abstraction/using.py index 202ed34..3beb960 100644 --- a/src/devana/syntax_abstraction/using.py +++ b/src/devana/syntax_abstraction/using.py @@ -53,7 +53,9 @@ def from_params( # pylint: disable=unused-argument @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: - return cursor.kind == cindex.CursorKind.TYPE_ALIAS_DECL + return cursor.kind in ( + cindex.CursorKind.TYPE_ALIAS_DECL, + ) @property @lazy_invoke From 882bfd309b87a0b2bb95a63b238e060b6aa3a723 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 17 Feb 2025 00:58:01 +0100 Subject: [PATCH 02/11] feat: add missing tests --- tests/code_generation/unit/test_concept.py | 197 ++++++++++++++++++ .../unit/test_instance_creations.py | 10 + .../unit/source_files/advanced_template.hpp | 7 +- tests/parsing/unit/source_files/concept.hpp | 53 +++++ .../unit/source_files/template_class.hpp | 19 +- .../unit/source_files/template_functions.hpp | 14 +- tests/parsing/unit/test_attributes.py | 3 +- tests/parsing/unit/test_class.py | 76 ++++++- tests/parsing/unit/test_concept.py | 137 ++++++++++++ tests/parsing/unit/test_function.py | 56 ++++- tests/parsing/unit/test_templates.py | 34 ++- tests/parsing/unit/test_using.py | 11 +- 12 files changed, 606 insertions(+), 11 deletions(-) create mode 100644 tests/code_generation/unit/test_concept.py create mode 100644 tests/parsing/unit/source_files/concept.hpp create mode 100644 tests/parsing/unit/test_concept.py diff --git a/tests/code_generation/unit/test_concept.py b/tests/code_generation/unit/test_concept.py new file mode 100644 index 0000000..65830c4 --- /dev/null +++ b/tests/code_generation/unit/test_concept.py @@ -0,0 +1,197 @@ +from devana.code_generation.printers.default.templateparameterprinter import TemplateParameterPrinter +from devana.code_generation.printers.default.conceptprinter import ConceptPrinter +from devana.code_generation.printers.default.classprinter import ClassPrinter, FieldPrinter +from devana.code_generation.printers.default.typeexpressionprinter import TypeExpressionPrinter +from devana.code_generation.printers.default.basictypeprinter import BasicTypePrinter +from devana.code_generation.printers.default.functionprinter import FunctionPrinter +from devana.syntax_abstraction.templateinfo import GenericTypeParameter +from devana.syntax_abstraction.conceptinfo import ConceptInfo +from devana.syntax_abstraction.functioninfo import FunctionInfo +from devana.syntax_abstraction.classinfo import ClassInfo, FieldInfo +from devana.syntax_abstraction.templateinfo import TemplateInfo +from devana.syntax_abstraction.typeexpression import TypeExpression, BasicType +from devana.code_generation.printers.codeprinter import CodePrinter +import unittest + + +class TestConceptAlone(unittest.TestCase): + + def setUp(self): + self.printer = CodePrinter() + self.printer.register(ConceptPrinter, ConceptInfo) + self.printer.register(TemplateParameterPrinter, TemplateInfo.TemplateParameter) + + def test_print_simple_concept(self): + concept = ConceptInfo.from_params( + name="TestConcept", + body="T{}" + ) + result = self.printer.print(concept) + self.assertEqual(result, "template\nconcept TestConcept = T{};\n") + + def test_print_complex_body_concept(self): + concept = ConceptInfo.from_params( + name="ComplexConcept", + body="true or false or && requires(A a) {\n a++;\n}", + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + name="A", specifier="class" + ) + ] + ) + ) + result = self.printer.print(concept) + self.assertEqual(result, """template +concept ComplexConcept = true or false or && requires(A a) { + a++; +}; +""") + def test_print_concept_template_params(self): + concept = ConceptInfo.from_params( + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + name="A", + specifier="class", + default_value="10" + ), + TemplateInfo.TemplateParameter.from_params( + name="Args", + specifier="typename", + is_variadic=True + ) + ] + ) + ) + result = self.printer.print(concept) + self.assertEqual(result, """template +concept DefaultConcept = true; +""") + def test_print_concept_skip_requires(self): + concept = ConceptInfo.create_default() + concept.template.requires = "true" + result = self.printer.print(concept) + self.assertEqual(result, "template\nconcept DefaultConcept = true;\n") + +class TestConceptClass(unittest.TestCase): + + def setUp(self): + self.printer = CodePrinter() + self.printer.register(ConceptPrinter, ConceptInfo) + self.printer.register(TemplateParameterPrinter, TemplateInfo.TemplateParameter) + self.printer.register(ClassPrinter, ClassInfo) + self.printer.register(TypeExpressionPrinter, TypeExpression) + self.printer.register(BasicTypePrinter, BasicType) + self.printer.register(FieldPrinter, FieldInfo) + self.printer.register(BasicTypePrinter, GenericTypeParameter) + + def test_print_simple_class_concept(self): + concept = ConceptInfo.from_params( + name="TestConcept" + ) + class_ = ClassInfo.from_params( + name="ClassConcept", + is_class=True, + is_declaration=True, + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + name="T", + specifier=concept + ) + ] + ) + ) + result = self.printer.print(class_) + self.assertEqual(result, """template +class ClassConcept; +""") + def test_print_class_requires(self): + class_ = ClassInfo.from_params( + name="ClassRequires", + template=TemplateInfo.from_params( + requires="true", + parameters=[ + TemplateInfo.TemplateParameter.create_default() + ] + ), + is_declaration=True + ) + result = self.printer.print(class_) + self.assertEqual(result, """template requires true +struct ClassRequires; +""") + + def test_print_complex_concept_class(self): + concept = ConceptInfo.from_params( + parameters=[TypeExpression.create_default()] + ) + class_ = ClassInfo.from_params( + name="ComplexConceptClass", + is_class=True, + content=[ + FieldInfo.from_params( + name="atr", + type=TypeExpression.from_params( + details=GenericTypeParameter("T") + ) + ) + ], + template=TemplateInfo.from_params( + requires="true", + parameters=[ + TemplateInfo.TemplateParameter.from_params( + name="T", + specifier=concept + ) + ] + ) + ) + result = self.printer.print(class_) + self.assertEqual(result, """template T> requires true +class ComplexConceptClass +{ + T atr; +}; +""") + +class TestConceptFunction(unittest.TestCase): + + def setUp(self): + self.printer = CodePrinter() + self.printer.register(ConceptPrinter, ConceptInfo) + self.printer.register(TemplateParameterPrinter, TemplateInfo.TemplateParameter) + self.printer.register(ClassPrinter, ClassInfo) + self.printer.register(TypeExpressionPrinter, TypeExpression) + self.printer.register(BasicTypePrinter, BasicType) + self.printer.register(FieldPrinter, FieldInfo) + self.printer.register(BasicTypePrinter, GenericTypeParameter) + self.printer.register(FunctionPrinter, FunctionInfo) + + def test_function_requires(self): + source = FunctionInfo.from_params( + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.create_default() + ], + requires="Concept" + ), + body=None + ) + result = self.printer.print(source) + self.assertEqual(result, "template requires Concept\nvoid foo();\n") + + def test_function_concept(self): + source = FunctionInfo.from_params( + template=TemplateInfo.from_params( + parameters=[TemplateInfo.TemplateParameter.from_params( + name="T", + specifier=ConceptInfo.from_params( + parameters=[TypeExpression.create_default()] + ) + )] + ) + ) + result = self.printer.print(source) + self.assertEqual(result, "template T>\nvoid foo();\n") \ No newline at end of file diff --git a/tests/code_generation/unit/test_instance_creations.py b/tests/code_generation/unit/test_instance_creations.py index 1139145..9ca0cb8 100644 --- a/tests/code_generation/unit/test_instance_creations.py +++ b/tests/code_generation/unit/test_instance_creations.py @@ -5,6 +5,7 @@ from devana.syntax_abstraction.functiontype import FunctionType from devana.syntax_abstraction.variable import GlobalVariable from devana.syntax_abstraction.typedefinfo import TypedefInfo +from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.syntax_abstraction.unioninfo import UnionInfo from devana.syntax_abstraction.enuminfo import EnumInfo from devana.syntax_abstraction.externc import ExternC @@ -269,6 +270,15 @@ def test_include_creation(self): self.assertEqual(include_info.value, "string") self.assertEqual(include_info.is_standard, True) + def test_concept_creation(self): + concept_info = ConceptInfo.from_params( + name="ConceptName", + body="false" + ) + self.assertEqual(concept_info.name, "ConceptName") + self.assertEqual(concept_info.body, "false") + self.assertIsNotNone(concept_info.template) + def test_init_params(self): class A: @classmethod diff --git a/tests/parsing/unit/source_files/advanced_template.hpp b/tests/parsing/unit/source_files/advanced_template.hpp index 99d7bf8..1f82abd 100644 --- a/tests/parsing/unit/source_files/advanced_template.hpp +++ b/tests/parsing/unit/source_files/advanced_template.hpp @@ -82,7 +82,7 @@ template void templateFunctionSpec_1(double b, T a) {} template<> -void templateFunctionSpec_1*>(double b, FieldHolderTemplate* a) {} +void templateFunctionSpec_1*>(double b, FieldHolderTemplate* a) {} struct TemplateFunctionSpecHolder { @@ -111,3 +111,8 @@ struct TestTemplateFields BaseArray data3; }; +template +concept TestConceptCase1 = A{} and B{}; + +template +concept TestConceptCase2 = false; \ No newline at end of file diff --git a/tests/parsing/unit/source_files/concept.hpp b/tests/parsing/unit/source_files/concept.hpp new file mode 100644 index 0000000..984f55f --- /dev/null +++ b/tests/parsing/unit/source_files/concept.hpp @@ -0,0 +1,53 @@ +template +concept ConceptCase1 = requires(T a) { + { --a }; + { a-- }; +}; + +template +concept ConceptCase2 = requires(T a, T b) { + { a + b }; +}; + +template +concept ConceptCase3 = requires(T a, T b) { + a = b; +} || requires(T a, T b) { + b = a; +}; + +template +concept ConceptCase4 = requires { + T{}; +}; + +template +concept ConceptCase5 = requires { + T(-1) < T(0); +}; + +template +concept ConceptCase6 = ConceptCase1 && requires(T t) { + *t; +}; + +template +concept ConceptCase7 = (T{} > 0); + +template +concept ConceptCase8 = ConceptCase7; + +template +concept ConceptCase9 = ConceptCase1 && ConceptCase2; + +template +concept ConceptCase10 = ConceptCase1 || ConceptCase2; + +template +concept ConceptCase11 = true; + +template +concept ConceptCase12 = T::value || true; + +template +concept ConceptCase13 = ConceptCase11; \ No newline at end of file diff --git a/tests/parsing/unit/source_files/template_class.hpp b/tests/parsing/unit/source_files/template_class.hpp index cfe52cf..e81f063 100644 --- a/tests/parsing/unit/source_files/template_class.hpp +++ b/tests/parsing/unit/source_files/template_class.hpp @@ -102,4 +102,21 @@ template struct multiple_pointer_struct { T** a; -} \ No newline at end of file +} + +template +concept TestConcept = true; + +template requires true +class ConceptClass { +public: + T a; + void process(T arg) requires TestConcept; +}; + +template requires false or true +struct ConceptStruct { + T abc = 10; + T foo() requires TestConcept; + T barFoo(T arg1, A arg2); +}; \ No newline at end of file diff --git a/tests/parsing/unit/source_files/template_functions.hpp b/tests/parsing/unit/source_files/template_functions.hpp index d0f0990..9b0ad1b 100644 --- a/tests/parsing/unit/source_files/template_functions.hpp +++ b/tests/parsing/unit/source_files/template_functions.hpp @@ -8,4 +8,16 @@ template const T complex_function(float a, T b, P& c, char d = '3'); template<> -const int* specialisation_function(float a, int* b, float& c, char d); \ No newline at end of file +const int* specialisation_function(float a, int* b, float& c, char d); + +template +concept AlwaysTrue = true; + +template +void requires_concept_function(T a) requires AlwaysTrue; + +template +int requires_bool_function(T a = 1) requires true or false; + +template +T basic_concept_function(); diff --git a/tests/parsing/unit/test_attributes.py b/tests/parsing/unit/test_attributes.py index 1cb9fcb..2529321 100644 --- a/tests/parsing/unit/test_attributes.py +++ b/tests/parsing/unit/test_attributes.py @@ -1,13 +1,12 @@ import unittest import os -from typing import List from devana.syntax_abstraction.organizers.sourcefile import SourceFile from devana.syntax_abstraction.attribute import Attribute from devana.syntax_abstraction.functioninfo import FunctionInfo from devana.syntax_abstraction.classinfo import ClassInfo, FieldInfo, MethodInfo from devana.syntax_abstraction.namespaceinfo import NamespaceInfo from devana.syntax_abstraction.enuminfo import EnumInfo - +from typing import List class TestAttributesParser(unittest.TestCase): diff --git a/tests/parsing/unit/test_class.py b/tests/parsing/unit/test_class.py index 6ea683f..141a151 100644 --- a/tests/parsing/unit/test_class.py +++ b/tests/parsing/unit/test_class.py @@ -432,7 +432,10 @@ class TestClassTemplate(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() - self.cursor = index.parse(os.path.dirname(__file__) + r"/source_files/template_class.hpp").cursor + self.cursor = index.parse( + os.path.dirname(__file__) + r"/source_files/template_class.hpp", + args=("-std=c++20",) + ).cursor def test_simple_template_class(self): node = find_by_name(self.cursor, "simple_template_struct_1") @@ -678,6 +681,7 @@ def test_struct_varidaic_template(self): self.assertEqual(result.template.parameters[2].specifier, "typename") self.assertEqual(result.template.parameters[2].default_value, None) self.assertTrue(result.template.parameters[2].is_variadic) + self.assertIsNone(result.template.requires) def test_multiple_pointer_type_template(self): node = find_by_name(self.cursor, "multiple_pointer_struct") @@ -694,7 +698,77 @@ def test_multiple_pointer_type_template(self): self.assertTrue(content.type.is_generic) self.assertEqual(content.type.modification.pointer_order, 2) self.assertEqual(content.type.details.name, "T") + self.assertIsNone(result.template.requires) + + def test_concept_class(self): + node = find_by_name(self.cursor, "ConceptClass") + result = ClassInfo.from_cursor(node) + self.assertTrue(result.is_class) + self.assertEqual(result.name, "ConceptClass") + self.assertEqual(result.template.requires, "true") + self.assertEqual(len(result.template.parameters), 2) + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier.name, "TestConcept") + self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.template.parameters[1].name, "B") + self.assertEqual(result.template.parameters[1].specifier, "class") + + field: FieldInfo = cast(FieldInfo, result.public[0]) + self.assertEqual(field.name, "a") + self.assertEqual(field.type.is_generic, True) + self.assertEqual(field.type.details.name, "T") + + # todo: fix this + # Currently there is no way to access the requires in this method + method: MethodInfo = cast(MethodInfo, result.public[1]) + self.assertEqual(method.name, "process") + self.assertEqual(method.type, MethodType.STANDARD) + self.assertEqual(method.return_type.details, BasicType.VOID) + self.assertEqual(method.body, None) + self.assertEqual(method.arguments[0].type.is_generic, True) + self.assertEqual(method.arguments[0].type.details.name, "T") + + def test_concept_struct(self): + node = find_by_name(self.cursor, "ConceptStruct") + result = ClassInfo.from_cursor(node) + self.assertTrue(result.is_struct) + self.assertEqual(result.name, "ConceptStruct") + self.assertEqual(result.template.requires, "false or true") + self.assertEqual(len(result.template.parameters), 2) + self.assertEqual(result.template.parameters[0].name, "A") + self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(result.template.parameters[1].name, "T") + self.assertEqual(result.template.parameters[1].specifier.name, "TestConcept") + self.assertEqual(result.template.parameters[1].specifier.body, "true") + self.assertEqual(result.template.parameters[1].default_value, None) + field: FieldInfo = cast(FieldInfo, result.public[0]) + self.assertEqual(field.name, "abc") + self.assertEqual(field.type.is_generic, True) + self.assertEqual(field.type.details.name, "T") + self.assertEqual(field.default_value, "10") + + # todo: fix this + # Currently there is no way to access the requires in this method + method: MethodInfo = cast(MethodInfo, result.public[1]) + self.assertEqual(method.name, "foo") + self.assertEqual(method.return_type.is_generic, True) + self.assertEqual(method.return_type.name, "T") + self.assertEqual(len(method.arguments), 0) + self.assertEqual(method.body, None) + + method: MethodInfo = cast(MethodInfo, result.public[2]) + self.assertEqual(method.name, "barFoo") + self.assertEqual(method.return_type.is_generic, True) + self.assertEqual(method.return_type.name, "T") + self.assertEqual(len(method.arguments), 2) + self.assertEqual(method.arguments[0].name, "arg1") + self.assertEqual(method.arguments[0].type.is_generic, True) + self.assertEqual(method.arguments[0].type.name, "T") + self.assertEqual(method.arguments[1].name, "arg2") + self.assertEqual(method.arguments[1].type.is_generic, True) + self.assertEqual(method.arguments[1].type.name, "A") + self.assertEqual(method.body, None) class TestClassTemplatePartial(unittest.TestCase): diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py new file mode 100644 index 0000000..8bc5a52 --- /dev/null +++ b/tests/parsing/unit/test_concept.py @@ -0,0 +1,137 @@ +import unittest +import clang +import os + +from devana.syntax_abstraction.conceptinfo import ConceptInfo +from devana.syntax_abstraction.functioninfo import FunctionInfo +from tests.helpers import find_by_name, stub_lexicon + + +class TestConcept(unittest.TestCase): + + def setUp(self): + index = clang.cindex.Index.create() + self.cursor = index.parse( + os.path.dirname(__file__) + r"/source_files/concept.hpp", + args=("-std=c++20",) + ).cursor + + def test_decrementable(self): + node = find_by_name(self.cursor, "ConceptCase1") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase1") + self.assertEqual(result.body, "requires(T a) {\r\n { --a };\r\n { a-- };\r\n}") + self.assertEqual(len(result.template.parameters), 1) + + def test_addable(self): + node = find_by_name(self.cursor, "ConceptCase2") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase2") + self.assertEqual(result.body, "requires(T a, T b) {\r\n { a + b };\r\n}") + self.assertEqual(len(result.template.parameters), 1) + + def test_assignable(self): + node = find_by_name(self.cursor, "ConceptCase3") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase3") + self.assertEqual(result.body, + "requires(T a, T b) {\r\n a = b;\r\n} || requires(T a, T b) {\r\n b = a;\r\n}" + ) + self.assertEqual(len(result.template.parameters), 1) + + def test_default_constructible(self): + node = find_by_name(self.cursor, "ConceptCase4") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase4") + self.assertEqual(result.body, "requires {\r\n T{};\r\n}") + self.assertEqual(len(result.template.parameters), 1) + + def test_signed(self): + node = find_by_name(self.cursor, "ConceptCase5") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase5") + self.assertEqual(result.body, "requires {\r\n T(-1) < T(0); \r\n}") + self.assertEqual(len(result.template.parameters), 1) + + def test_rev_iterator(self): + node = find_by_name(self.cursor, "ConceptCase6") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase6") + self.assertEqual(result.body, "ConceptCase1 && requires(T t) {\r\n *t;\r\n}") + self.assertEqual(len(result.template.parameters), 1) + + def test_default_positive(self): + node = find_by_name(self.cursor, "ConceptCase7") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase7") + self.assertEqual(result.body, "(T{} > 0)") + self.assertEqual(len(result.template.parameters), 1) + + def test_integral(self): + node = find_by_name(self.cursor, "ConceptCase8") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase8") + self.assertEqual(result.body, "ConceptCase7") + self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.template.parameters[0].specifier.name, "ConceptCase7") + + def test_decrementable_and_addable(self): + node = find_by_name(self.cursor, "ConceptCase9") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase9") + self.assertEqual(result.body, "ConceptCase1 && ConceptCase2") + self.assertEqual(len(result.template.parameters), 1) + + def test_decrementable_or_addable(self): + node = find_by_name(self.cursor, "ConceptCase10") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase10") + self.assertEqual(result.body, "ConceptCase1 || ConceptCase2") + self.assertEqual(len(result.template.parameters), 1) + + def test_always_true(self): + node = find_by_name(self.cursor, "ConceptCase11") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase11") + self.assertEqual(result.body, "true") + self.assertEqual(len(result.template.parameters), 1) + + def test_has_value_with_bool(self): + node = find_by_name(self.cursor, "ConceptCase12") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase12") + self.assertEqual(result.body, "T::value || true") + self.assertEqual(len(result.template.parameters), 1) + + def test_pointer(self): + node = find_by_name(self.cursor, "ConceptCase13") + result = ConceptInfo.from_cursor(node) + stub_lexicon(result) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptCase13") + self.assertEqual(result.body, "ConceptCase11") + self.assertEqual(len(result.template.parameters), 1) diff --git a/tests/parsing/unit/test_function.py b/tests/parsing/unit/test_function.py index ce746f8..8d89fd0 100644 --- a/tests/parsing/unit/test_function.py +++ b/tests/parsing/unit/test_function.py @@ -2,6 +2,8 @@ import clang.cindex import clang import os + +from devana.syntax_abstraction import CodePiece from tests.helpers import find_by_name, stub_lexicon from devana.syntax_abstraction.typeexpression import BasicType, TypeModification, TypeExpression from devana.syntax_abstraction.functioninfo import FunctionInfo, FunctionModification @@ -251,7 +253,10 @@ class TestFunctionsTemplate(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() - self.cursor = index.parse(os.path.dirname(__file__) + r"/source_files/template_functions.hpp").cursor + self.cursor = index.parse( + os.path.dirname(__file__) + r"/source_files/template_functions.hpp", + args=("-std=c++20",) + ).cursor def test_common_function_template(self): node = find_by_name(self.cursor, "simple_function_typename") @@ -276,6 +281,7 @@ def test_common_function_template(self): self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier, "typename") self.assertEqual(result.template.parameters[0].default_value, None) + self.assertEqual(result.template.requires, None) node = find_by_name(self.cursor, "simple_function_class") result = FunctionInfo.from_cursor(node) @@ -299,6 +305,7 @@ def test_common_function_template(self): self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier, "class") self.assertEqual(result.template.parameters[0].default_value, None) + self.assertEqual(result.template.requires, None) def test_complex_function_template(self): node = find_by_name(self.cursor, "complex_function") @@ -339,6 +346,7 @@ def test_complex_function_template(self): self.assertEqual(result.template.parameters[1].specifier, "typename") self.assertEqual(result.template.parameters[1].default_value, "const float") self.assertEqual(len(result.template.specialisations), 0) + self.assertEqual(result.template.requires, None) def test_specialisation_function_template(self): node = find_by_name(self.cursor, "specialisation_function") @@ -372,7 +380,53 @@ def test_specialisation_function_template(self): self.assertTrue(result.return_type.modification.is_pointer) self.assertTrue(result.return_type.modification.is_const) self.assertEqual(result.body, None) + self.assertEqual(result.template.requires, None) + + def test_requires_concept_function(self): + node = find_by_name(self.cursor, "requires_concept_function") + result = FunctionInfo.from_cursor(node) + stub_lexicon(result) + self.assertEqual(result.name, "requires_concept_function") + self.assertEqual(result.body, None) + self.assertEqual(result.return_type.details, BasicType.VOID) + self.assertEqual(len(result.arguments), 1) + self.assertEqual(result.arguments[0].name, "a") + self.assertEqual(result.arguments[0].type.is_generic, True) + self.assertEqual(result.arguments[0].type.details.name, "T") + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(result.template.requires, "AlwaysTrue") + + def test_requires_bool_function(self): + node = find_by_name(self.cursor, "requires_bool_function") + result = FunctionInfo.from_cursor(node) + stub_lexicon(result) + self.assertEqual(result.name, "requires_bool_function") + self.assertEqual(result.body, None) + self.assertEqual(result.return_type.details, BasicType.INT) + self.assertEqual(len(result.arguments), 1) + self.assertEqual(result.arguments[0].name, "a") + self.assertEqual(result.arguments[0].default_value, "1") + self.assertEqual(result.arguments[0].type.is_generic, True) + self.assertEqual(result.arguments[0].type.details.name, "T") + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") + self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.template.requires, "true or false") + def test_basic_concept_function(self): + node = find_by_name(self.cursor, "basic_concept_function") + result = FunctionInfo.from_cursor(node) + stub_lexicon(result) + self.assertEqual(result.name, "basic_concept_function") + self.assertEqual(result.body, None) + self.assertEqual(len(result.arguments), 0) + self.assertEqual(result.return_type.is_generic, True) + self.assertEqual(result.return_type.details.name, "T") + self.assertEqual(result.template.requires, None) + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") + self.assertEqual(result.template.parameters[0].specifier.body, "true") class TestFunctionsOverload(unittest.TestCase): def setUp(self): diff --git a/tests/parsing/unit/test_templates.py b/tests/parsing/unit/test_templates.py index b16ebcf..a49dbfa 100644 --- a/tests/parsing/unit/test_templates.py +++ b/tests/parsing/unit/test_templates.py @@ -3,18 +3,23 @@ import clang import os from devana.syntax_abstraction.organizers.sourcefile import SourceFile -from devana.syntax_abstraction.typeexpression import BasicType, TypeModification +from devana.syntax_abstraction.typeexpression import TypeModification from devana.syntax_abstraction.classinfo import * from devana.syntax_abstraction.typedefinfo import TypedefInfo +from devana.syntax_abstraction.conceptinfo import ConceptInfo +from tests.helpers import find_by_name class TestTemplateAdvanced(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() - self.cursor = index.parse(os.path.dirname(__file__) + r"/source_files/advanced_template.hpp").cursor + self.cursor = index.parse( + os.path.dirname(__file__) + r"/source_files/advanced_template.hpp", + args=("-std=c++20",) + ).cursor self.file = SourceFile.from_cursor(self.cursor) - self.assertEqual(len(self.file.content), 32) + self.assertEqual(len(self.file.content), 34) def test_functions_arguments(self): result: FunctionInfo = self.file.content[2] @@ -350,3 +355,26 @@ def test_template_method_spec_outside(self): self.assertEqual(result.template.specialisation_values[0].details, self.file.content[23]) self.assertEqual(len(result.template.specialisation_values[0].template_arguments), 1) self.assertEqual(result.template.specialisation_values[0].template_arguments[0].details, BasicType.CHAR) + + def test_template_concept(self): + result: ConceptInfo = self.file.content[32] + with self.subTest(result.name): + self.assertEqual(result.name, "TestConceptCase1") + self.assertEqual(result.body, "A{} and B{}") + self.assertEqual(len(result.template.parameters), 2) + self.assertEqual(len(result.template.specialisation_values), 0) + self.assertEqual(result.template.parameters[0].name, "A") + self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(result.template.parameters[1].name, "B") + self.assertEqual(result.template.parameters[1].specifier, "class") + self.assertEqual(result.template.requires, None) + + result = self.file.content[33] + with self.subTest(result.name): + self.assertEqual(result.name, "TestConceptCase2") + self.assertEqual(result.body, "false") + self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(len(result.template.specialisation_values), 0) + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier, "class") + self.assertEqual(result.template.requires, None) \ No newline at end of file diff --git a/tests/parsing/unit/test_using.py b/tests/parsing/unit/test_using.py index 63ea3c6..ad39291 100644 --- a/tests/parsing/unit/test_using.py +++ b/tests/parsing/unit/test_using.py @@ -12,7 +12,10 @@ class TestUsing(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() - self.cursor = index.parse(os.path.dirname(__file__) + r"/source_files/using.hpp").cursor + self.cursor = index.parse( + os.path.dirname(__file__) + r"/source_files/using.hpp", + args=("-std=c++20",) + ).cursor self.file = SourceFile.from_cursor(self.cursor) def test_using_as_simple_alias(self): @@ -31,3 +34,9 @@ def test_using_as_template_alias(self): self.assertEqual(source.type_info.details, self.file.content[3]) self.assertEqual(len(source.type_info.template_arguments), 1) self.assertEqual(source.type_info.template_arguments[0].details, BasicType.DOUBLE) + + def test_using_as_concept_template_alias(self): + pass + # todo: Add support to this + #source: Using = self.file.content[6] + # self.assertEqual(source.name, "TestConceptType") From d4a221d0cb1a600c040073933dbb812963a91d95 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 17 Feb 2025 01:16:36 +0100 Subject: [PATCH 03/11] fix: failing tests --- tests/parsing/unit/test_concept.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py index 8bc5a52..3ae6640 100644 --- a/tests/parsing/unit/test_concept.py +++ b/tests/parsing/unit/test_concept.py @@ -22,7 +22,7 @@ def test_decrementable(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase1") - self.assertEqual(result.body, "requires(T a) {\r\n { --a };\r\n { a-- };\r\n}") + self.assertEqual(result.body, "requires(T a) {\n { --a };\n { a-- };\n}") self.assertEqual(len(result.template.parameters), 1) def test_addable(self): @@ -31,7 +31,7 @@ def test_addable(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase2") - self.assertEqual(result.body, "requires(T a, T b) {\r\n { a + b };\r\n}") + self.assertEqual(result.body, "requires(T a, T b) {\n { a + b };\n}") self.assertEqual(len(result.template.parameters), 1) def test_assignable(self): @@ -41,7 +41,7 @@ def test_assignable(self): self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase3") self.assertEqual(result.body, - "requires(T a, T b) {\r\n a = b;\r\n} || requires(T a, T b) {\r\n b = a;\r\n}" + "requires(T a, T b) {\n a = b;\n} || requires(T a, T b) {\n b = a;\n}" ) self.assertEqual(len(result.template.parameters), 1) @@ -51,7 +51,7 @@ def test_default_constructible(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase4") - self.assertEqual(result.body, "requires {\r\n T{};\r\n}") + self.assertEqual(result.body, "requires {\n T{};\n}") self.assertEqual(len(result.template.parameters), 1) def test_signed(self): @@ -60,7 +60,7 @@ def test_signed(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase5") - self.assertEqual(result.body, "requires {\r\n T(-1) < T(0); \r\n}") + self.assertEqual(result.body, "requires {\n T(-1) < T(0); \n}") self.assertEqual(len(result.template.parameters), 1) def test_rev_iterator(self): @@ -69,7 +69,7 @@ def test_rev_iterator(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase6") - self.assertEqual(result.body, "ConceptCase1 && requires(T t) {\r\n *t;\r\n}") + self.assertEqual(result.body, "ConceptCase1 && requires(T t) {\n *t;\n}") self.assertEqual(len(result.template.parameters), 1) def test_default_positive(self): From 920ac626ff426c098c47c6406309c0d668b61e7c Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 17 Feb 2025 19:21:55 +0100 Subject: [PATCH 04/11] fix: failing tests + pylint warnings --- .pylintrc | 3 ++- .../default/templateparameterprinter.py | 1 - src/devana/syntax_abstraction/conceptinfo.py | 21 ++++++++++-------- src/devana/syntax_abstraction/templateinfo.py | 11 +++++----- tests/parsing/unit/test_concept.py | 22 ++++++++++++++----- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/.pylintrc b/.pylintrc index a418d8e..b2451f3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -440,7 +440,8 @@ disable=raw-checker-failed, consider-using-dict-items, no-else-break, wildcard-import, - unused-wildcard-import + unused-wildcard-import, + too-many-positional-arguments diff --git a/src/devana/code_generation/printers/default/templateparameterprinter.py b/src/devana/code_generation/printers/default/templateparameterprinter.py index b07adbe..5b6acd7 100644 --- a/src/devana/code_generation/printers/default/templateparameterprinter.py +++ b/src/devana/code_generation/printers/default/templateparameterprinter.py @@ -1,7 +1,6 @@ from devana.code_generation.printers.icodeprinter import ICodePrinter from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.code_generation.printers.dispatcherinjectable import DispatcherInjectable -from devana.code_generation.printers.default.typeexpressionprinter import TypeExpressionPrinter class TemplateParameterPrinter(ICodePrinter, DispatcherInjectable): diff --git a/src/devana/syntax_abstraction/conceptinfo.py b/src/devana/syntax_abstraction/conceptinfo.py index 3bff14a..ad23fb4 100644 --- a/src/devana/syntax_abstraction/conceptinfo.py +++ b/src/devana/syntax_abstraction/conceptinfo.py @@ -1,3 +1,6 @@ +from typing import Optional, List, Any +from clang import cindex + from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.typeexpression import TypeExpression from devana.syntax_abstraction.templateinfo import TemplateInfo @@ -8,13 +11,11 @@ from devana.utility.init_params import init_params from devana.utility.errors import ParserError -from clang.cindex import CursorKind, Cursor -from typing import Optional, List - class ConceptInfo(CodeContainer): + """Represents a C++ concept, either as a full definition or as a template requirement (e.g. in `template`).""" - def __init__(self, cursor: Optional[Cursor] = None, parent: Optional[CodeContainer] = None): + def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[CodeContainer] = None): super().__init__(cursor, parent) self._cursor = cursor self._parent = parent @@ -42,16 +43,18 @@ def create_default(cls, parent: Optional = None) -> "ConceptInfo": return cls(None, parent) @classmethod - def from_cursor(cls, cursor: Cursor, parent: Optional = None) -> Optional["ConceptInfo"]: + def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional["ConceptInfo"]: if cls.is_cursor_valid(cursor): return cls(cursor, parent) return None @classmethod @init_params(skip={"parent"}) - def from_params( + def from_params( # pylint: disable=unused-argument cls, parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None, name: Optional[str] = None, body: Optional[str] = None, template: Optional[TemplateInfo] = None, @@ -61,8 +64,8 @@ def from_params( return cls(None, parent) @staticmethod - def is_cursor_valid(cursor: Cursor) -> bool: - return cursor.kind == CursorKind.CONCEPT_DECL + def is_cursor_valid(cursor: cindex.Cursor) -> bool: + return cursor.kind == cindex.CursorKind.CONCEPT_DECL @property def parent(self) -> CodeContainer: @@ -94,7 +97,7 @@ def template(self, value: TemplateInfo) -> None: def body(self) -> str: self._body = "" for child in self._cursor.get_children(): - if child.kind != CursorKind.TEMPLATE_TYPE_PARAMETER: + if child.kind != cindex.CursorKind.TEMPLATE_TYPE_PARAMETER: self._body = CodePiece(child).text break return self._body diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 9f48ed4..4184fad 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -1,7 +1,6 @@ import re -from ctypes import c_uint from pathlib import Path -from typing import Optional, List, Union, Tuple, Any, TYPE_CHECKING, Iterable +from typing import Optional, List, Union, Tuple, Any, TYPE_CHECKING from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression, TypeModification @@ -92,6 +91,8 @@ def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParame def from_params( # pylint: disable=unused-argument cls, parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None, specifier: Optional[Union[str, "ConceptInfo"]] = None, name: Optional[str] = None, default_value: Optional[str] = None, @@ -114,7 +115,7 @@ def is_cursor_valid(cursor: cindex.Cursor) -> bool: @lazy_invoke def specifier(self) -> Union["ConceptInfo", str]: """Keyword or ConceptInfo instance preceding the name.""" - from devana.syntax_abstraction.conceptinfo import ConceptInfo + from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel cursors = filter( lambda c: c.kind == cindex.CursorKind.TEMPLATE_REF and c.referenced, @@ -130,7 +131,7 @@ def specifier(self) -> Union["ConceptInfo", str]: @specifier.setter def specifier(self, value: Union["ConceptInfo", str]): - from devana.syntax_abstraction.conceptinfo import ConceptInfo + from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel if not isinstance(value, ConceptInfo) and value not in ("class", "typename"): raise ValueError("Specifier must be class, typename, or an instance of ConceptInfo.") self._specifier = value @@ -192,7 +193,7 @@ def __eq__(self, other): @property def _content_types(self) -> List: - from devana.syntax_abstraction.conceptinfo import ConceptInfo + from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel return [ConceptInfo] def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py index 3ae6640..6bbbfcd 100644 --- a/tests/parsing/unit/test_concept.py +++ b/tests/parsing/unit/test_concept.py @@ -22,7 +22,7 @@ def test_decrementable(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase1") - self.assertEqual(result.body, "requires(T a) {\n { --a };\n { a-- };\n}") + self.assertEqual(result.body.replace("\r\n", "\n"), "requires(T a) {\n { --a };\n { a-- };\n}") self.assertEqual(len(result.template.parameters), 1) def test_addable(self): @@ -31,7 +31,10 @@ def test_addable(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase2") - self.assertEqual(result.body, "requires(T a, T b) {\n { a + b };\n}") + self.assertEqual( + result.body.replace("\r\n", "\n"), + "requires(T a, T b) {\n { a + b };\n}" + ) self.assertEqual(len(result.template.parameters), 1) def test_assignable(self): @@ -40,7 +43,8 @@ def test_assignable(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase3") - self.assertEqual(result.body, + self.assertEqual( + result.body.replace("\r\n", "\n"), "requires(T a, T b) {\n a = b;\n} || requires(T a, T b) {\n b = a;\n}" ) self.assertEqual(len(result.template.parameters), 1) @@ -51,7 +55,7 @@ def test_default_constructible(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase4") - self.assertEqual(result.body, "requires {\n T{};\n}") + self.assertEqual(result.body.replace("\r\n", "\n"), "requires {\n T{};\n}") self.assertEqual(len(result.template.parameters), 1) def test_signed(self): @@ -60,7 +64,10 @@ def test_signed(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase5") - self.assertEqual(result.body, "requires {\n T(-1) < T(0); \n}") + self.assertEqual( + result.body.replace("\r\n", "\n"), + "requires {\n T(-1) < T(0); \n}" + ) self.assertEqual(len(result.template.parameters), 1) def test_rev_iterator(self): @@ -69,7 +76,10 @@ def test_rev_iterator(self): stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase6") - self.assertEqual(result.body, "ConceptCase1 && requires(T t) {\n *t;\n}") + self.assertEqual( + result.body.replace("\r\n", "\n"), + "ConceptCase1 && requires(T t) {\n *t;\n}" + ) self.assertEqual(len(result.template.parameters), 1) def test_default_positive(self): From 8316230acd816d7043c5ad910d85cba347283404 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Tue, 18 Feb 2025 17:21:53 +0100 Subject: [PATCH 05/11] refactor: delete unnecessary overwrites --- src/devana/syntax_abstraction/conceptinfo.py | 20 +------------------ src/devana/syntax_abstraction/templateinfo.py | 12 ----------- tests/parsing/unit/test_concept.py | 13 ------------ 3 files changed, 1 insertion(+), 44 deletions(-) diff --git a/src/devana/syntax_abstraction/conceptinfo.py b/src/devana/syntax_abstraction/conceptinfo.py index ad23fb4..9252223 100644 --- a/src/devana/syntax_abstraction/conceptinfo.py +++ b/src/devana/syntax_abstraction/conceptinfo.py @@ -5,7 +5,6 @@ from devana.syntax_abstraction.typeexpression import TypeExpression from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.syntax_abstraction.codepiece import CodePiece -from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.syntax import ISyntaxElement from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.init_params import init_params @@ -17,8 +16,6 @@ class ConceptInfo(CodeContainer): def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[CodeContainer] = None): super().__init__(cursor, parent) - self._cursor = cursor - self._parent = parent if cursor is None: self._name = "DefaultConcept" self._body = "true" @@ -33,7 +30,6 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._body = LazyNotInit self._template = LazyNotInit self._parameters = LazyNotInit - self._lexicon = Lexicon.create(self) def __repr__(self): return f"{type(self).__name__}:{self.name} ({super().__repr__()})" @@ -58,8 +54,7 @@ def from_params( # pylint: disable=unused-argument name: Optional[str] = None, body: Optional[str] = None, template: Optional[TemplateInfo] = None, - parameters: Optional[List["TypeExpression"]] = None, - lexicon: Optional[Lexicon] = None + parameters: Optional[List["TypeExpression"]] = None ) -> "ConceptInfo": return cls(None, parent) @@ -67,11 +62,6 @@ def from_params( # pylint: disable=unused-argument def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.CONCEPT_DECL - @property - def parent(self) -> CodeContainer: - """Structural parent element like file, namespace or class.""" - return self._parent - @property @lazy_invoke def name(self) -> str: @@ -126,14 +116,6 @@ def is_specifier(self) -> bool: """Determines whether this ConceptInfo instance is acting as a template specifier.""" return isinstance(self.parent, TemplateInfo.TemplateParameter) - @property - def lexicon(self): - return self._lexicon - - @lexicon.setter - def lexicon(self, value): - self._lexicon = value - @property def _content_types(self) -> List: return [ConceptInfo] diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 4184fad..3dc18f6 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -66,8 +66,6 @@ class TemplateParameter(CodeContainer): def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): super().__init__(cursor, parent) - self._cursor = cursor - self._parent = parent if cursor is None: self._specifier = "typename" self._name = "T" @@ -80,7 +78,6 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No self._name = LazyNotInit self._default_value = LazyNotInit self._is_variadic = LazyNotInit - self._lexicon = Lexicon.create(self) @classmethod def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParameter": @@ -177,15 +174,6 @@ def is_variadic(self) -> bool: def is_variadic(self, value): self._is_variadic = value - @property - def lexicon(self) -> CodeContainer: - """Current lexicon storage of an object.""" - return self._lexicon - - @property - def parent(self): - return self._parent - def __eq__(self, other): if not isinstance(other, type(self)): return False diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py index 6bbbfcd..e5ab0d4 100644 --- a/tests/parsing/unit/test_concept.py +++ b/tests/parsing/unit/test_concept.py @@ -19,7 +19,6 @@ def setUp(self): def test_decrementable(self): node = find_by_name(self.cursor, "ConceptCase1") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase1") self.assertEqual(result.body.replace("\r\n", "\n"), "requires(T a) {\n { --a };\n { a-- };\n}") @@ -28,7 +27,6 @@ def test_decrementable(self): def test_addable(self): node = find_by_name(self.cursor, "ConceptCase2") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase2") self.assertEqual( @@ -40,7 +38,6 @@ def test_addable(self): def test_assignable(self): node = find_by_name(self.cursor, "ConceptCase3") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase3") self.assertEqual( @@ -52,7 +49,6 @@ def test_assignable(self): def test_default_constructible(self): node = find_by_name(self.cursor, "ConceptCase4") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase4") self.assertEqual(result.body.replace("\r\n", "\n"), "requires {\n T{};\n}") @@ -61,7 +57,6 @@ def test_default_constructible(self): def test_signed(self): node = find_by_name(self.cursor, "ConceptCase5") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase5") self.assertEqual( @@ -73,7 +68,6 @@ def test_signed(self): def test_rev_iterator(self): node = find_by_name(self.cursor, "ConceptCase6") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase6") self.assertEqual( @@ -85,7 +79,6 @@ def test_rev_iterator(self): def test_default_positive(self): node = find_by_name(self.cursor, "ConceptCase7") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase7") self.assertEqual(result.body, "(T{} > 0)") @@ -94,7 +87,6 @@ def test_default_positive(self): def test_integral(self): node = find_by_name(self.cursor, "ConceptCase8") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase8") self.assertEqual(result.body, "ConceptCase7") @@ -104,7 +96,6 @@ def test_integral(self): def test_decrementable_and_addable(self): node = find_by_name(self.cursor, "ConceptCase9") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase9") self.assertEqual(result.body, "ConceptCase1 && ConceptCase2") @@ -113,7 +104,6 @@ def test_decrementable_and_addable(self): def test_decrementable_or_addable(self): node = find_by_name(self.cursor, "ConceptCase10") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase10") self.assertEqual(result.body, "ConceptCase1 || ConceptCase2") @@ -122,7 +112,6 @@ def test_decrementable_or_addable(self): def test_always_true(self): node = find_by_name(self.cursor, "ConceptCase11") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase11") self.assertEqual(result.body, "true") @@ -131,7 +120,6 @@ def test_always_true(self): def test_has_value_with_bool(self): node = find_by_name(self.cursor, "ConceptCase12") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase12") self.assertEqual(result.body, "T::value || true") @@ -140,7 +128,6 @@ def test_has_value_with_bool(self): def test_pointer(self): node = find_by_name(self.cursor, "ConceptCase13") result = ConceptInfo.from_cursor(node) - stub_lexicon(result) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase13") self.assertEqual(result.body, "ConceptCase11") From 5cd13874348d90b5302ed3009dbde1a6a5e1e468 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Tue, 25 Feb 2025 20:37:05 +0100 Subject: [PATCH 06/11] feat: the `requires` parsing + some cr suggestions --- .../printers/default/classprinter.py | 8 +- .../printers/default/conceptprinter.py | 8 ++ .../printers/default/functionprinter.py | 14 +++- .../default/templateparameterprinter.py | 14 +--- src/devana/syntax_abstraction/classinfo.py | 4 + src/devana/syntax_abstraction/conceptinfo.py | 31 +++++-- src/devana/syntax_abstraction/functioninfo.py | 59 +++++++++++++- src/devana/syntax_abstraction/templateinfo.py | 81 +++++++++++++------ tests/code_generation/unit/test_concept.py | 58 +++++++++---- .../unit/source_files/advanced_template.hpp | 8 +- tests/parsing/unit/source_files/concept.hpp | 5 +- .../unit/source_files/template_class.hpp | 19 ++++- .../unit/source_files/template_functions.hpp | 15 ++-- tests/parsing/unit/test_class.py | 75 ++++++++++++++--- tests/parsing/unit/test_concept.py | 68 +++++++++++++--- tests/parsing/unit/test_function.py | 81 +++++++++++++++---- tests/parsing/unit/test_templates.py | 30 +------ 17 files changed, 432 insertions(+), 146 deletions(-) diff --git a/src/devana/code_generation/printers/default/classprinter.py b/src/devana/code_generation/printers/default/classprinter.py index e7e364d..9d1aa1c 100644 --- a/src/devana/code_generation/printers/default/classprinter.py +++ b/src/devana/code_generation/printers/default/classprinter.py @@ -182,10 +182,14 @@ def print(self, source: ClassInfo, config: Optional[PrinterConfiguration] = None parameters = ','.join(parameters) template_prefix = f"template<{parameters}>" if source.template.requires: - template_prefix += f" requires {source.template.requires}" + template_prefix += " requires" + for req in source.template.requires: + if isinstance(req, str): + template_prefix += f" {req}" + continue + template_prefix += f" {self.printer_dispatcher.print(req, config, source)}" specialisation_values = [] - for s in source.template.specialisation_values: if isinstance(s, str): specialisation_values.append(s) diff --git a/src/devana/code_generation/printers/default/conceptprinter.py b/src/devana/code_generation/printers/default/conceptprinter.py index 15b6169..b19b0b6 100644 --- a/src/devana/code_generation/printers/default/conceptprinter.py +++ b/src/devana/code_generation/printers/default/conceptprinter.py @@ -14,6 +14,14 @@ def print(self, source: ConceptInfo, config: Optional[PrinterConfiguration] = No if config is None: config = PrinterConfiguration() formatter = Formatter(config) + if source.is_requirement: + parameters = [] + for p in source.parameters: + parameters.append(self.printer_dispatcher.print(p, config, source)) + parameters = ', '.join(parameters) + if len(parameters) > 0: + return f"{source.name}<{parameters}>" + return source.name parameters = [] for p in source.template.parameters: parameters.append(self.printer_dispatcher.print(p, config, source)) diff --git a/src/devana/code_generation/printers/default/functionprinter.py b/src/devana/code_generation/printers/default/functionprinter.py index 99dd176..ceda49e 100644 --- a/src/devana/code_generation/printers/default/functionprinter.py +++ b/src/devana/code_generation/printers/default/functionprinter.py @@ -42,7 +42,12 @@ def print(self, source: FunctionInfo, config: Optional[PrinterConfiguration] = N parameters = ','.join(parameters) template_prefix = f"template<{parameters}>" if source.template.requires: - template_prefix += f" requires {source.template.requires}" + template_prefix += " requires" + for req in source.template.requires: + if isinstance(req, str): + template_prefix += f" {req}" + continue + template_prefix += f" {self.printer_dispatcher.print(req, config, source)}" specialisation_values = [] @@ -73,6 +78,13 @@ def print(self, source: FunctionInfo, config: Optional[PrinterConfiguration] = N result = f"{return_type} {name}{template_suffix}({args})" else: result = f"{name}{template_suffix}({args})" + if source.requires is not None: + result += " requires" + for req in source.requires: + if isinstance(req, str): + result += f" {req}" + continue + result += f" {self.printer_dispatcher.print(req, config, source)}" if source.modification.is_static: result = "static " + result diff --git a/src/devana/code_generation/printers/default/templateparameterprinter.py b/src/devana/code_generation/printers/default/templateparameterprinter.py index 5b6acd7..d1d2766 100644 --- a/src/devana/code_generation/printers/default/templateparameterprinter.py +++ b/src/devana/code_generation/printers/default/templateparameterprinter.py @@ -1,23 +1,17 @@ from devana.code_generation.printers.icodeprinter import ICodePrinter from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.code_generation.printers.dispatcherinjectable import DispatcherInjectable +from devana.syntax_abstraction.conceptinfo import ConceptInfo class TemplateParameterPrinter(ICodePrinter, DispatcherInjectable): """Printer for template parameter.""" def print(self, source: TemplateInfo.TemplateParameter, _1=None, _2=None) -> str: - if isinstance(source.specifier, str): - text = f"{source.specifier} {source.name}" + if isinstance(source.specifier, ConceptInfo): + text = f"{self.printer_dispatcher.print(source.specifier)} {source.name}" else: - if len(source.specifier.parameters) == 0: - text = f"{source.specifier.name} {source.name}" - else: - parameters = [] - for p in source.specifier.parameters: - parameters.append(self.printer_dispatcher.print(p)) - parameters = ', '.join(parameters) - text = f"{source.specifier.name}<{parameters}> {source.name}" + text = f"{source.specifier} {source.name}" if source.is_variadic: return f"{text}..." diff --git a/src/devana/syntax_abstraction/classinfo.py b/src/devana/syntax_abstraction/classinfo.py index 4269f25..5c82acd 100644 --- a/src/devana/syntax_abstraction/classinfo.py +++ b/src/devana/syntax_abstraction/classinfo.py @@ -12,6 +12,7 @@ from devana.syntax_abstraction.comment import Comment from devana.syntax_abstraction.attribute import DescriptiveByAttributes, AttributeDeclaration from devana.syntax_abstraction._external_source import create_external +from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.init_params import init_params from devana.utility.traits import IBasicCreatable, ICursorValidate, IFromCursorCreatable, IFromParamsCreatable @@ -207,6 +208,7 @@ def from_params( # pylint: disable=unused-argument, arguments-renamed template: Optional[TemplateInfo] = None, associated_comment: Optional[Comment] = None, prefix: Optional[str] = None, + requires: Optional[List[Union[str, ConceptInfo]]] = None, access_specifier: Optional[AccessSpecifier] = None, type: Optional[MethodType] = None, # noqa pylint: disable=redefined-builtin ) -> "MethodInfo": @@ -305,6 +307,7 @@ def from_params( # pylint: disable=unused-argument, arguments-renamed template: Optional[TemplateInfo] = None, associated_comment: Optional[Comment] = None, prefix: Optional[str] = None, + requires: Optional[List[Union[str, ConceptInfo]]] = None, access_specifier: Optional[AccessSpecifier] = None, type: Optional[MethodType] = None, # noqa pylint: disable=redefined-builtin initializer_list: Optional[List[InitializerInfo]] = None, @@ -416,6 +419,7 @@ def from_params( # pylint: disable=unused-argument, arguments-differ template: Optional[TemplateInfo] = None, associated_comment: Optional[Comment] = None, prefix: Optional[str] = None, + requires: Optional[List[Union[str, ConceptInfo]]] = None, access_specifier: Optional[AccessSpecifier] = None, ) -> "DestructorInfo": return cls(None, parent) diff --git a/src/devana/syntax_abstraction/conceptinfo.py b/src/devana/syntax_abstraction/conceptinfo.py index 9252223..088196f 100644 --- a/src/devana/syntax_abstraction/conceptinfo.py +++ b/src/devana/syntax_abstraction/conceptinfo.py @@ -3,16 +3,16 @@ from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.typeexpression import TypeExpression -from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.init_params import init_params from devana.utility.errors import ParserError class ConceptInfo(CodeContainer): - """Represents a C++ concept, either as a full definition or as a template requirement (e.g. in `template`).""" + """Represents a C++ concept, either as a full definition or as a requirement.""" def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[CodeContainer] = None): super().__init__(cursor, parent) @@ -23,6 +23,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code TemplateInfo.TemplateParameter.create_default() ]) self._parameters = [] + self._is_requirement = False else: if not self.is_cursor_valid(cursor): raise ParserError(f"It is not a valid type cursor: {cursor.kind}.") @@ -30,6 +31,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._body = LazyNotInit self._template = LazyNotInit self._parameters = LazyNotInit + self._is_requirement = LazyNotInit def __repr__(self): return f"{type(self).__name__}:{self.name} ({super().__repr__()})" @@ -54,7 +56,8 @@ def from_params( # pylint: disable=unused-argument name: Optional[str] = None, body: Optional[str] = None, template: Optional[TemplateInfo] = None, - parameters: Optional[List["TypeExpression"]] = None + parameters: Optional[List[TypeExpression]] = None, + is_requirement: Optional[bool] = None ) -> "ConceptInfo": return cls(None, parent) @@ -98,7 +101,7 @@ def body(self, value: str) -> None: @property @lazy_invoke - def parameters(self) -> List["TypeExpression"]: + def parameters(self) -> List[TypeExpression]: """Retrieves the concept parameters '<...>'.""" if not isinstance(self.parent, TemplateInfo.TemplateParameter): return [] @@ -108,13 +111,25 @@ def parameters(self) -> List["TypeExpression"]: return [] @parameters.setter - def parameters(self, value: List["TypeExpression"]) -> None: + def parameters(self, value: List[TypeExpression]) -> None: self._parameters = value @property - def is_specifier(self) -> bool: - """Determines whether this ConceptInfo instance is acting as a template specifier.""" - return isinstance(self.parent, TemplateInfo.TemplateParameter) + @lazy_invoke + def is_requirement(self) -> bool: + """Determines whether this ConceptInfo instance is acting as a requirement.""" + from devana.syntax_abstraction.functioninfo import FunctionInfo # pylint: disable=import-outside-toplevel) + self._is_requirement = isinstance( + self.parent, ( + TemplateInfo.TemplateParameter, + TemplateInfo, FunctionInfo + ) + ) + return self._is_requirement + + @is_requirement.setter + def is_requirement(self, value: bool) -> None: + self._is_requirement = value @property def _content_types(self) -> List: diff --git a/src/devana/syntax_abstraction/functioninfo.py b/src/devana/syntax_abstraction/functioninfo.py index 575656e..055fb5a 100644 --- a/src/devana/syntax_abstraction/functioninfo.py +++ b/src/devana/syntax_abstraction/functioninfo.py @@ -1,7 +1,8 @@ -from typing import Optional, Tuple, List, Any, Union +from typing import Optional, Tuple, List, Any, Union, Iterable from enum import auto, IntFlag import re from clang import cindex + from devana.syntax_abstraction.variable import Variable from devana.syntax_abstraction.typeexpression import TypeExpression, BasicType from devana.syntax_abstraction.organizers.lexicon import Lexicon @@ -10,6 +11,7 @@ from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.syntax_abstraction.attribute import DescriptiveByAttributes, AttributeDeclaration +from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.utility import FakeEnum from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.traits import IBasicCreatable, ICursorValidate @@ -319,6 +321,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._template = None self._namespaces = None self._associated_comment = None + self._requires = None else: if not self.is_cursor_valid(cursor): msg = f"It is not a valid type cursor: {cursor.kind}." @@ -333,6 +336,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._template = LazyNotInit self._namespaces = LazyNotInit self._associated_comment = LazyNotInit + self._requires = LazyNotInit self._lexicon = Lexicon.create(self) @classmethod @@ -366,6 +370,7 @@ def from_params( # pylint: disable=unused-argument template: Optional[TemplateInfo] = None, associated_comment: Optional[Comment] = None, prefix: Optional[str] = None, + requires: Optional[List[Union[str, ConceptInfo]]] = None ) -> "FunctionInfo": return cls(None, parent) @@ -660,5 +665,57 @@ def prefix(self) -> str: def prefix(self, value: str): self._prefix = value + @property + @lazy_invoke + def requires(self) -> Optional[List[Union[ConceptInfo, str]]]: + """Extracts constraints from the 'requires' clause of the function. None if absent.""" + + # Need to get rid of the template line, because it could also have a requires clause, + # which we don't want to touch, since the template has its own property. + code = re.sub( + r'(?m)^template[^\n]*(?:\r?\n(?:[ \t]+.*|requires\b.*))*\r?\n?', + '', + CodePiece(self._cursor).text + ) + match = re.search(r"requires\s+([\s\S]*)", code) + if not match: + self._requires = None + return self._requires + self._requires = [] + + def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: + for child in cursor.get_children(): + if child.location.line < self._cursor.location.line: + # Ignore template elements. + continue + if child.kind == cindex.CursorKind.TEMPLATE_REF and child.referenced: + yield child + if child.kind in ( + cindex.CursorKind.BINARY_OPERATOR, + cindex.CursorKind.CONCEPT_SPECIALIZATION_EXPR, + cindex.CursorKind.PAREN_EXPR + ): + yield from find_concepts(child) + + # clang does not provide detailed info for all things in the requires (e.g., 'or', 'true'), + # so we use a regex to extract missing elements. + raw_elements: List[str] = re.findall(r'((?:<[^>]+>|[^\s<])+)', match.group(1)) + cursors: List[cindex.Cursor] = list(find_concepts(self._cursor)) + for raw_element in raw_elements: + if len(cursors) > 0 and re.search(r'<[^>]+>', raw_element): + maybe_concept = ConceptInfo.from_cursor( + cursor=cursors.pop(0).referenced, + parent=self + ) + if maybe_concept is not None: + self._requires.append(maybe_concept) + continue + self._requires.append(raw_element.strip().strip("()")) + return self._requires + + @requires.setter + def requires(self, value: Optional[List[Union[ConceptInfo, str]]]) -> None: + self._requires = value + def __repr__(self): return f"{type(self).__name__}:{self.name} ({super().__repr__()})" diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 3dc18f6..9ac451d 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -1,6 +1,6 @@ import re from pathlib import Path -from typing import Optional, List, Union, Tuple, Any, TYPE_CHECKING +from typing import Optional, List, Union, Tuple, Any, Iterable, TYPE_CHECKING from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression, TypeModification @@ -35,6 +35,8 @@ def name(self, value): @staticmethod def from_cursor(type_c, cursor: cindex.Type, parent: Optional = None) -> Optional["GenericTypeParameter"]: if type_c.kind == cindex.TypeKind.UNEXPOSED: + if type_c.get_num_template_arguments() > 0: + return None if hasattr(cursor, 'get_children'): for c in cursor.get_children(): if c.kind == cindex.CursorKind.TYPE_REF: @@ -61,11 +63,12 @@ def __repr__(self): class TemplateInfo(IBasicCreatable, ICursorValidate, ISyntaxElement): """General template syntax information abut template definition.""" - class TemplateParameter(CodeContainer): + class TemplateParameter(IBasicCreatable, ICursorValidate, ISyntaxElement): """A description of the generic component for the type/function claim.""" def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): - super().__init__(cursor, parent) + self._cursor = cursor + self._parent = parent if cursor is None: self._specifier = "typename" self._name = "T" @@ -78,6 +81,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No self._name = LazyNotInit self._default_value = LazyNotInit self._is_variadic = LazyNotInit + self._lexicon = Lexicon.create(self) @classmethod def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParameter": @@ -88,8 +92,6 @@ def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParame def from_params( # pylint: disable=unused-argument cls, parent: Optional[ISyntaxElement] = None, - content: Optional[List[Any]] = None, - namespace: Optional[str] = None, specifier: Optional[Union[str, "ConceptInfo"]] = None, name: Optional[str] = None, default_value: Optional[str] = None, @@ -166,7 +168,7 @@ def default_value(self, value): def is_variadic(self) -> bool: self._is_variadic = False text = CodePiece(self._cursor).text - if re.search(r"\.\.\." + self.name, text): + if re.search(r"\.\.\.\s*" + self.name, text): self._is_variadic = True return self._is_variadic @@ -174,16 +176,20 @@ def is_variadic(self) -> bool: def is_variadic(self, value): self._is_variadic = value + @property + def lexicon(self) -> CodeContainer: + """Current lexicon storage of an object.""" + return self._lexicon + + @property + def parent(self): + return self._parent + def __eq__(self, other): if not isinstance(other, type(self)): return False return self.name == other.name and self.is_variadic == other.is_variadic - @property - def _content_types(self) -> List: - from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel - return [ConceptInfo] - def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): self._cursor = cursor self._parent = parent @@ -219,7 +225,7 @@ def from_params( # pylint: disable=unused-argument parameters: Optional[List[TemplateParameter]] = None, is_empty: Optional[bool] = None, lexicon: Optional[Lexicon] = None, - requires: Optional[str] = None + requires: Optional[List[Union[str, "ConceptInfo"]]] = None ) -> "TemplateInfo": return cls(None, parent) @@ -451,19 +457,46 @@ def parent(self) -> ISyntaxElement: @property @lazy_invoke - def requires(self) -> Optional[str]: - """Extracts the constraint expression from the 'requires' clause.""" - - # Eventually I'd like to go for the [ConceptInfo | str] list here, - # but that's going to be a bit of work, because clang gives little information here, - # and when it does, it's in a snide way. - # Additionally, this property should be in classes/methods/functions etc. I originally assumed template == requires, which was a mistake. - self._requires = None - match = re.search(r"(?<=requires\s)([^\n{}]+)", CodePiece(self._cursor).text) - if match: - self._requires = match.group().strip() + def requires(self) -> Optional[List[Union[str, "ConceptInfo"]]]: + """Extracts constraints from the 'requires' clause of the template. None if absent.""" + match = re.search( + r"template\s*<[^>]+>\s*(?:\r?\n\s*)?requires\s+(?:\r?\n\s*)?(.+?)(?=\r?\n\S|$)", + CodePiece(self._cursor).text, + flags=re.DOTALL + ) + if not match: + self._requires = None + return self._requires + self._requires = [] + + def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: + for child in cursor.get_children(): + if child.kind == cindex.CursorKind.TEMPLATE_REF and child.referenced: + yield child + if child.kind in ( + cindex.CursorKind.BINARY_OPERATOR, + cindex.CursorKind.CONCEPT_SPECIALIZATION_EXPR, + cindex.CursorKind.PAREN_EXPR + ): + yield from find_concepts(child) + # clang does not provide info for all things in the requires (e.g., 'or', 'true'), + # so we use a regex to extract missing elements. + from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel) + raw_elements: List[str] = re.findall("((?:[^ <]+|<[^>]*>)+)", match.group(1)) + cursors: List[cindex.Cursor] = list(find_concepts(self._cursor)) + + for raw_element in raw_elements: + if len(cursors) > 0 and re.search(r'<[^>]+>', raw_element): + maybe_concept = ConceptInfo.from_cursor( + cursor=cursors.pop(0).referenced, + parent=self + ) + if maybe_concept is not None: + self._requires.append(maybe_concept) + continue + self._requires.append(raw_element.strip().strip("()")) return self._requires @requires.setter - def requires(self, value: Optional[str]) -> None: + def requires(self, value: Optional[List[Union[str, "ConceptInfo"]]]) -> None: self._requires = value diff --git a/tests/code_generation/unit/test_concept.py b/tests/code_generation/unit/test_concept.py index 65830c4..a9cdb96 100644 --- a/tests/code_generation/unit/test_concept.py +++ b/tests/code_generation/unit/test_concept.py @@ -1,13 +1,13 @@ from devana.code_generation.printers.default.templateparameterprinter import TemplateParameterPrinter from devana.code_generation.printers.default.conceptprinter import ConceptPrinter -from devana.code_generation.printers.default.classprinter import ClassPrinter, FieldPrinter +from devana.code_generation.printers.default.classprinter import ClassPrinter, FieldPrinter, MethodPrinter from devana.code_generation.printers.default.typeexpressionprinter import TypeExpressionPrinter from devana.code_generation.printers.default.basictypeprinter import BasicTypePrinter from devana.code_generation.printers.default.functionprinter import FunctionPrinter from devana.syntax_abstraction.templateinfo import GenericTypeParameter from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.syntax_abstraction.functioninfo import FunctionInfo -from devana.syntax_abstraction.classinfo import ClassInfo, FieldInfo +from devana.syntax_abstraction.classinfo import ClassInfo, FieldInfo, MethodInfo from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.syntax_abstraction.typeexpression import TypeExpression, BasicType from devana.code_generation.printers.codeprinter import CodePrinter @@ -70,10 +70,15 @@ def test_print_concept_template_params(self): """) def test_print_concept_skip_requires(self): concept = ConceptInfo.create_default() - concept.template.requires = "true" + concept.template.requires = ["true"] result = self.printer.print(concept) self.assertEqual(result, "template\nconcept DefaultConcept = true;\n") + def test_print_concept_requirement(self): + concept = ConceptInfo.from_params(name="Test", is_requirement=True) + result = self.printer.print(concept) + self.assertEqual(result, "Test") + class TestConceptClass(unittest.TestCase): def setUp(self): @@ -85,10 +90,12 @@ def setUp(self): self.printer.register(BasicTypePrinter, BasicType) self.printer.register(FieldPrinter, FieldInfo) self.printer.register(BasicTypePrinter, GenericTypeParameter) + self.printer.register(MethodPrinter, MethodInfo) def test_print_simple_class_concept(self): concept = ConceptInfo.from_params( - name="TestConcept" + name="TestConcept", + is_requirement=True ) class_ = ClassInfo.from_params( name="ClassConcept", @@ -111,7 +118,7 @@ def test_print_class_requires(self): class_ = ClassInfo.from_params( name="ClassRequires", template=TemplateInfo.from_params( - requires="true", + requires=["true"], parameters=[ TemplateInfo.TemplateParameter.create_default() ] @@ -124,8 +131,14 @@ def test_print_class_requires(self): """) def test_print_complex_concept_class(self): - concept = ConceptInfo.from_params( - parameters=[TypeExpression.create_default()] + concept1 = ConceptInfo.from_params( + parameters=[TypeExpression.create_default()], + is_requirement=True + ) + concept2 = ConceptInfo.from_params( + is_requirement=True, + name="Testing", + parameters=[TypeExpression.from_params(details=GenericTypeParameter("T"))] ) class_ = ClassInfo.from_params( name="ComplexConceptClass", @@ -136,23 +149,29 @@ def test_print_complex_concept_class(self): type=TypeExpression.from_params( details=GenericTypeParameter("T") ) + ), + MethodInfo.from_params( + name="foo", + return_type=BasicType.VOID, + requires=[concept2, "and", concept1] ) ], template=TemplateInfo.from_params( - requires="true", + requires=["true", "&&", concept2], parameters=[ TemplateInfo.TemplateParameter.from_params( name="T", - specifier=concept + specifier=concept1 ) ] ) ) result = self.printer.print(class_) - self.assertEqual(result, """template T> requires true + self.assertEqual(result, """template T> requires true && Testing class ComplexConceptClass { T atr; + void foo() requires Testing and DefaultConcept; }; """) @@ -170,17 +189,21 @@ def setUp(self): self.printer.register(FunctionPrinter, FunctionInfo) def test_function_requires(self): + concept = ConceptInfo.from_params( + is_requirement=True, + name="Concept", + parameters=[TypeExpression.from_params(details=GenericTypeParameter("T"))] + ) source = FunctionInfo.from_params( template=TemplateInfo.from_params( - parameters=[ - TemplateInfo.TemplateParameter.create_default() - ], - requires="Concept" + parameters=[TemplateInfo.TemplateParameter.create_default()], + requires=[concept, "or", "true"] ), - body=None + body=None, + requires=["true", "||", concept] ) result = self.printer.print(source) - self.assertEqual(result, "template requires Concept\nvoid foo();\n") + self.assertEqual(result, "template requires Concept or true\nvoid foo() requires true || Concept;\n") def test_function_concept(self): source = FunctionInfo.from_params( @@ -188,7 +211,8 @@ def test_function_concept(self): parameters=[TemplateInfo.TemplateParameter.from_params( name="T", specifier=ConceptInfo.from_params( - parameters=[TypeExpression.create_default()] + parameters=[TypeExpression.create_default()], + is_requirement=True ) )] ) diff --git a/tests/parsing/unit/source_files/advanced_template.hpp b/tests/parsing/unit/source_files/advanced_template.hpp index 1f82abd..0e44e50 100644 --- a/tests/parsing/unit/source_files/advanced_template.hpp +++ b/tests/parsing/unit/source_files/advanced_template.hpp @@ -82,7 +82,7 @@ template void templateFunctionSpec_1(double b, T a) {} template<> -void templateFunctionSpec_1*>(double b, FieldHolderTemplate* a) {} +void templateFunctionSpec_1*>(double b, FieldHolderTemplate* a) {} struct TemplateFunctionSpecHolder { @@ -110,9 +110,3 @@ struct TestTemplateFields BaseArray> data2; BaseArray data3; }; - -template -concept TestConceptCase1 = A{} and B{}; - -template -concept TestConceptCase2 = false; \ No newline at end of file diff --git a/tests/parsing/unit/source_files/concept.hpp b/tests/parsing/unit/source_files/concept.hpp index 984f55f..84f27c9 100644 --- a/tests/parsing/unit/source_files/concept.hpp +++ b/tests/parsing/unit/source_files/concept.hpp @@ -50,4 +50,7 @@ template concept ConceptCase12 = T::value || true; template -concept ConceptCase13 = ConceptCase11; \ No newline at end of file +concept ConceptCase13 = ConceptCase11; + +template +concept ConceptTemplate = true; diff --git a/tests/parsing/unit/source_files/template_class.hpp b/tests/parsing/unit/source_files/template_class.hpp index e81f063..891f20a 100644 --- a/tests/parsing/unit/source_files/template_class.hpp +++ b/tests/parsing/unit/source_files/template_class.hpp @@ -52,11 +52,14 @@ class template_class_complex }; template -struct struct_varidaic_template +struct struct_variadic_template { int a; }; +template +struct struct_variadic_template2; + template struct template_struct { @@ -107,14 +110,22 @@ struct multiple_pointer_struct template concept TestConcept = true; -template requires true +template +class BasicConceptClass { + T foo(T arg); +}; + +template requires true class ConceptClass { public: T a; - void process(T arg) requires TestConcept; + void process(T arg) requires TestConcept or + true; }; -template requires false or true +template + requires false or + true and TestConcept< T> struct ConceptStruct { T abc = 10; T foo() requires TestConcept; diff --git a/tests/parsing/unit/source_files/template_functions.hpp b/tests/parsing/unit/source_files/template_functions.hpp index 9b0ad1b..57a18a1 100644 --- a/tests/parsing/unit/source_files/template_functions.hpp +++ b/tests/parsing/unit/source_files/template_functions.hpp @@ -13,11 +13,16 @@ const int* specialisation_function(float a, int* b, float& c, char d); template concept AlwaysTrue = true; -template -void requires_concept_function(T a) requires AlwaysTrue; +template +T template_concept_function(); -template -int requires_bool_function(T a = 1) requires true or false; +template requires AlwaysTrue< T> +void requires_template_function1(T a) requires true or false; template -T basic_concept_function(); + requires true or AlwaysTrue +int requires_template_function2(T a = 1) + requires AlwaysTrue and true; + +template requires (AlwaysTrue or true) and false +void basic_concept_function(); diff --git a/tests/parsing/unit/test_class.py b/tests/parsing/unit/test_class.py index 141a151..c504fb9 100644 --- a/tests/parsing/unit/test_class.py +++ b/tests/parsing/unit/test_class.py @@ -662,12 +662,12 @@ def test_complex_template_class(self): self.assertEqual(content.template.parameters[0].name, "D") self.assertEqual(content.template.parameters[0].default_value, None) - def test_struct_varidaic_template(self): - node = find_by_name(self.cursor, "struct_varidaic_template") + def test_struct_variadic_template(self): + node = find_by_name(self.cursor, "struct_variadic_template") result = ClassInfo.from_cursor(node) self.assertTrue(result.is_struct) self.assertFalse(result.template is None) - self.assertEqual(result.name, "struct_varidaic_template") + self.assertEqual(result.name, "struct_variadic_template") self.assertEqual(len(result.template.parameters), 3) self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier, "typename") @@ -683,6 +683,19 @@ def test_struct_varidaic_template(self): self.assertTrue(result.template.parameters[2].is_variadic) self.assertIsNone(result.template.requires) + def test_struct_variadic_template2(self): + node = find_by_name(self.cursor, "struct_variadic_template2") + result = ClassInfo.from_cursor(node) + self.assertTrue(result.is_struct) + self.assertFalse(result.template is None) + self.assertEqual(result.name, "struct_variadic_template2") + self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.template.parameters[0].name, "Args") + self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(result.template.parameters[0].default_value, None) + self.assertTrue(result.template.parameters[0].is_variadic) + self.assertIsNone(result.template.requires) + def test_multiple_pointer_type_template(self): node = find_by_name(self.cursor, "multiple_pointer_struct") result = ClassInfo.from_cursor(node) @@ -700,26 +713,58 @@ def test_multiple_pointer_type_template(self): self.assertEqual(content.type.details.name, "T") self.assertIsNone(result.template.requires) + def test_basic_concept_class(self): + node = find_by_name(self.cursor, "BasicConceptClass") + result = ClassInfo.from_cursor(node) + self.assertTrue(result.is_class) + self.assertEqual(result.name, "BasicConceptClass") + self.assertEqual(result.template.requires, None) + self.assertEqual(len(result.template.parameters), 2) + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier.name, "TestConcept") + self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.template.parameters[0].default_value, "bool") + self.assertEqual(result.template.parameters[0].is_variadic, False) + self.assertEqual(result.template.parameters[1].name, "Args") + self.assertEqual(result.template.parameters[1].specifier.name, "TestConcept") + self.assertEqual(result.template.parameters[1].specifier.body, "true") + self.assertEqual(result.template.parameters[1].default_value, None) + self.assertEqual(result.template.parameters[1].is_variadic, True) + + method: MethodInfo = cast(MethodInfo, result.private[0]) + self.assertEqual(method.name, "foo") + self.assertEqual(method.type, MethodType.STANDARD) + self.assertEqual(method.return_type.details.name, "T") + self.assertEqual(method.body, None) + self.assertEqual(len(method.arguments), 1) + self.assertEqual(method.arguments[0].name, "arg") + self.assertEqual(method.arguments[0].type.is_generic, True) + self.assertEqual(method.arguments[0].type.details.name, "T") + self.assertEqual(method.requires, None) + def test_concept_class(self): node = find_by_name(self.cursor, "ConceptClass") result = ClassInfo.from_cursor(node) self.assertTrue(result.is_class) self.assertEqual(result.name, "ConceptClass") - self.assertEqual(result.template.requires, "true") + self.assertEqual(len(result.template.requires), 1) + self.assertEqual(result.template.requires[0], "true") self.assertEqual(len(result.template.parameters), 2) self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier.name, "TestConcept") self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.template.parameters[0].default_value, None) + self.assertEqual(result.template.parameters[0].is_variadic, False) self.assertEqual(result.template.parameters[1].name, "B") self.assertEqual(result.template.parameters[1].specifier, "class") + self.assertEqual(result.template.parameters[1].default_value, "int") + self.assertEqual(result.template.parameters[1].is_variadic, False) field: FieldInfo = cast(FieldInfo, result.public[0]) self.assertEqual(field.name, "a") self.assertEqual(field.type.is_generic, True) self.assertEqual(field.type.details.name, "T") - # todo: fix this - # Currently there is no way to access the requires in this method method: MethodInfo = cast(MethodInfo, result.public[1]) self.assertEqual(method.name, "process") self.assertEqual(method.type, MethodType.STANDARD) @@ -727,13 +772,22 @@ def test_concept_class(self): self.assertEqual(method.body, None) self.assertEqual(method.arguments[0].type.is_generic, True) self.assertEqual(method.arguments[0].type.details.name, "T") + self.assertEqual(len(method.requires), 3) + self.assertEqual(method.requires[0].name, "TestConcept") + self.assertEqual(method.requires[0].body, "true") + self.assertEqual(method.requires[0].is_requirement, True) + self.assertEqual(method.requires[1], "or") + self.assertEqual(method.requires[2], "true") def test_concept_struct(self): node = find_by_name(self.cursor, "ConceptStruct") result = ClassInfo.from_cursor(node) self.assertTrue(result.is_struct) self.assertEqual(result.name, "ConceptStruct") - self.assertEqual(result.template.requires, "false or true") + self.assertEqual(len(result.template.requires), 5) + self.assertEqual(result.template.requires[0:4], ["false", "or", "true", "and"]) + self.assertEqual(result.template.requires[4].name, "TestConcept") + self.assertEqual(result.template.requires[4].is_requirement, True) self.assertEqual(len(result.template.parameters), 2) self.assertEqual(result.template.parameters[0].name, "A") self.assertEqual(result.template.parameters[0].specifier, "typename") @@ -748,14 +802,16 @@ def test_concept_struct(self): self.assertEqual(field.type.details.name, "T") self.assertEqual(field.default_value, "10") - # todo: fix this - # Currently there is no way to access the requires in this method method: MethodInfo = cast(MethodInfo, result.public[1]) self.assertEqual(method.name, "foo") self.assertEqual(method.return_type.is_generic, True) self.assertEqual(method.return_type.name, "T") self.assertEqual(len(method.arguments), 0) self.assertEqual(method.body, None) + self.assertEqual(len(method.requires), 1) + self.assertEqual(method.requires[0].name, "TestConcept") + self.assertEqual(method.requires[0].is_requirement, True) + self.assertEqual(method.requires[0].body, "true") method: MethodInfo = cast(MethodInfo, result.public[2]) self.assertEqual(method.name, "barFoo") @@ -769,6 +825,7 @@ def test_concept_struct(self): self.assertEqual(method.arguments[1].type.is_generic, True) self.assertEqual(method.arguments[1].type.name, "A") self.assertEqual(method.body, None) + self.assertEqual(method.requires, None) class TestClassTemplatePartial(unittest.TestCase): diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py index e5ab0d4..1940a39 100644 --- a/tests/parsing/unit/test_concept.py +++ b/tests/parsing/unit/test_concept.py @@ -1,4 +1,6 @@ import unittest +from difflib import restore + import clang import os @@ -16,15 +18,16 @@ def setUp(self): args=("-std=c++20",) ).cursor - def test_decrementable(self): + def test_concept_case_1(self): node = find_by_name(self.cursor, "ConceptCase1") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase1") self.assertEqual(result.body.replace("\r\n", "\n"), "requires(T a) {\n { --a };\n { a-- };\n}") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_addable(self): + def test_concept_case_2(self): node = find_by_name(self.cursor, "ConceptCase2") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) @@ -34,8 +37,9 @@ def test_addable(self): "requires(T a, T b) {\n { a + b };\n}" ) self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_assignable(self): + def test_concept_case_3(self): node = find_by_name(self.cursor, "ConceptCase3") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) @@ -45,16 +49,18 @@ def test_assignable(self): "requires(T a, T b) {\n a = b;\n} || requires(T a, T b) {\n b = a;\n}" ) self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_default_constructible(self): + def test_concept_case_4(self): node = find_by_name(self.cursor, "ConceptCase4") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase4") self.assertEqual(result.body.replace("\r\n", "\n"), "requires {\n T{};\n}") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_signed(self): + def test_concept_case_5(self): node = find_by_name(self.cursor, "ConceptCase5") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) @@ -64,8 +70,9 @@ def test_signed(self): "requires {\n T(-1) < T(0); \n}" ) self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_rev_iterator(self): + def test_concept_case_6(self): node = find_by_name(self.cursor, "ConceptCase6") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) @@ -75,60 +82,95 @@ def test_rev_iterator(self): "ConceptCase1 && requires(T t) {\n *t;\n}" ) self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_default_positive(self): + def test_concept_case_7(self): node = find_by_name(self.cursor, "ConceptCase7") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase7") self.assertEqual(result.body, "(T{} > 0)") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_integral(self): + def test_concept_case_8(self): node = find_by_name(self.cursor, "ConceptCase8") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase8") self.assertEqual(result.body, "ConceptCase7") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) self.assertEqual(result.template.parameters[0].specifier.name, "ConceptCase7") + self.assertEqual(result.template.parameters[0].specifier.is_requirement, True) - def test_decrementable_and_addable(self): + def test_concept_case_9(self): node = find_by_name(self.cursor, "ConceptCase9") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase9") self.assertEqual(result.body, "ConceptCase1 && ConceptCase2") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_decrementable_or_addable(self): + def test_concept_case_10(self): node = find_by_name(self.cursor, "ConceptCase10") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase10") self.assertEqual(result.body, "ConceptCase1 || ConceptCase2") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_always_true(self): + def test_concept_case_11(self): node = find_by_name(self.cursor, "ConceptCase11") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase11") self.assertEqual(result.body, "true") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_has_value_with_bool(self): + def test_concept_case_12(self): node = find_by_name(self.cursor, "ConceptCase12") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase12") self.assertEqual(result.body, "T::value || true") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) - def test_pointer(self): + def test_concept_case_13(self): node = find_by_name(self.cursor, "ConceptCase13") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase13") self.assertEqual(result.body, "ConceptCase11") self.assertEqual(len(result.template.parameters), 1) + self.assertEqual(result.is_requirement, False) + + def test_concept_template(self): + node = find_by_name(self.cursor, "ConceptTemplate") + result = ConceptInfo.from_cursor(node) + self.assertIsNone(result.parent) + self.assertEqual(result.name, "ConceptTemplate") + self.assertEqual(result.body, "true") + self.assertEqual(result.template.parent, None) + self.assertEqual(result.template.is_empty, False) + self.assertEqual(result.template.requires, None) + self.assertEqual(len(result.template.parameters), 3) + + self.assertEqual(result.template.parameters[0].name, "A") + self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(result.template.parameters[0].is_variadic, False) + self.assertEqual(result.template.parameters[0].default_value, None) + + self.assertEqual(result.template.parameters[1].name, "B") + self.assertEqual(result.template.parameters[1].specifier, "class") + self.assertEqual(result.template.parameters[1].is_variadic, False) + self.assertEqual(result.template.parameters[1].default_value, "int") + + self.assertEqual(result.template.parameters[2].name, "Args") + self.assertEqual(result.template.parameters[2].specifier, "typename") + self.assertEqual(result.template.parameters[2].is_variadic, True) + self.assertEqual(result.template.parameters[2].default_value, None) \ No newline at end of file diff --git a/tests/parsing/unit/test_function.py b/tests/parsing/unit/test_function.py index 8d89fd0..0709ed7 100644 --- a/tests/parsing/unit/test_function.py +++ b/tests/parsing/unit/test_function.py @@ -3,9 +3,8 @@ import clang import os -from devana.syntax_abstraction import CodePiece from tests.helpers import find_by_name, stub_lexicon -from devana.syntax_abstraction.typeexpression import BasicType, TypeModification, TypeExpression +from devana.syntax_abstraction.typeexpression import BasicType, TypeModification from devana.syntax_abstraction.functioninfo import FunctionInfo, FunctionModification from devana.syntax_abstraction.organizers.sourcefile import SourceFile from devana.utility.errors import CodeError @@ -282,6 +281,7 @@ def test_common_function_template(self): self.assertEqual(result.template.parameters[0].specifier, "typename") self.assertEqual(result.template.parameters[0].default_value, None) self.assertEqual(result.template.requires, None) + self.assertEqual(result.requires, None) node = find_by_name(self.cursor, "simple_function_class") result = FunctionInfo.from_cursor(node) @@ -306,6 +306,7 @@ def test_common_function_template(self): self.assertEqual(result.template.parameters[0].specifier, "class") self.assertEqual(result.template.parameters[0].default_value, None) self.assertEqual(result.template.requires, None) + self.assertEqual(result.requires, None) def test_complex_function_template(self): node = find_by_name(self.cursor, "complex_function") @@ -347,12 +348,14 @@ def test_complex_function_template(self): self.assertEqual(result.template.parameters[1].default_value, "const float") self.assertEqual(len(result.template.specialisations), 0) self.assertEqual(result.template.requires, None) + self.assertEqual(result.requires, None) def test_specialisation_function_template(self): node = find_by_name(self.cursor, "specialisation_function") result = FunctionInfo.from_cursor(node) stub_lexicon(result) self.assertFalse(result.template is None) + self.assertEqual(result.requires, None) self.assertTrue(result.template.is_empty) self.assertEqual(result.name, "specialisation_function") self.assertEqual(len(result.arguments), 4) @@ -382,37 +385,81 @@ def test_specialisation_function_template(self): self.assertEqual(result.body, None) self.assertEqual(result.template.requires, None) - def test_requires_concept_function(self): - node = find_by_name(self.cursor, "requires_concept_function") + def test_template_concept_function(self): + node = find_by_name(self.cursor, "template_concept_function") result = FunctionInfo.from_cursor(node) stub_lexicon(result) - self.assertEqual(result.name, "requires_concept_function") + self.assertEqual(result.name, "template_concept_function") + self.assertEqual(result.body, None) + self.assertEqual(result.return_type.is_generic, True) + self.assertEqual(result.return_type.details.name, "T") + self.assertEqual(result.requires, None) + self.assertEqual(result.template.requires, None) + self.assertEqual(len(result.template.parameters), 2) + + self.assertEqual(result.template.parameters[0].name, "T") + self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") + self.assertEqual(result.template.parameters[0].specifier.is_requirement, True) + self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.template.parameters[0].default_value, "int") + self.assertEqual(result.template.parameters[0].is_variadic, False) + + self.assertEqual(result.template.parameters[1].name, "Args") + self.assertEqual(result.template.parameters[1].specifier.name, "AlwaysTrue") + self.assertEqual(result.template.parameters[1].specifier.is_requirement, True) + self.assertEqual(result.template.parameters[1].specifier.body, "true") + self.assertEqual(result.template.parameters[1].default_value, None) + self.assertEqual(result.template.parameters[1].is_variadic, True) + + def test_requires_template_function1(self): + node = find_by_name(self.cursor, "requires_template_function1") + result = FunctionInfo.from_cursor(node) + stub_lexicon(result) + self.assertEqual(result.name, "requires_template_function1") self.assertEqual(result.body, None) self.assertEqual(result.return_type.details, BasicType.VOID) + self.assertEqual(result.requires, ["true", "or", "false"]) self.assertEqual(len(result.arguments), 1) self.assertEqual(result.arguments[0].name, "a") self.assertEqual(result.arguments[0].type.is_generic, True) self.assertEqual(result.arguments[0].type.details.name, "T") self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier, "typename") - self.assertEqual(result.template.requires, "AlwaysTrue") + self.assertEqual(len(result.template.requires), 1) + self.assertEqual(result.template.requires[0].name, "AlwaysTrue") + self.assertEqual(result.template.requires[0].is_requirement, True) + self.assertEqual(result.template.requires[0].body, "true") - def test_requires_bool_function(self): - node = find_by_name(self.cursor, "requires_bool_function") + def test_requires_template_function2(self): + node = find_by_name(self.cursor, "requires_template_function2") result = FunctionInfo.from_cursor(node) stub_lexicon(result) - self.assertEqual(result.name, "requires_bool_function") + self.assertEqual(result.name, "requires_template_function2") self.assertEqual(result.body, None) self.assertEqual(result.return_type.details, BasicType.INT) + self.assertEqual(len(result.arguments), 1) self.assertEqual(result.arguments[0].name, "a") self.assertEqual(result.arguments[0].default_value, "1") self.assertEqual(result.arguments[0].type.is_generic, True) self.assertEqual(result.arguments[0].type.details.name, "T") + + self.assertEqual(len(result.requires), 3) + self.assertEqual(result.requires[0].name, "AlwaysTrue") + self.assertEqual(result.requires[0].is_requirement, True) + self.assertEqual(result.requires[0].body, "true") + self.assertEqual(result.requires[1], "and") + self.assertEqual(result.requires[2], "true") self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") self.assertEqual(result.template.parameters[0].specifier.body, "true") - self.assertEqual(result.template.requires, "true or false") + + self.assertEqual(len(result.template.requires), 3) + self.assertEqual(result.template.requires[0], "true") + self.assertEqual(result.template.requires[1], "or") + self.assertEqual(result.template.requires[2].name, "AlwaysTrue") + self.assertEqual(result.template.requires[2].is_requirement, True) + self.assertEqual(result.template.requires[2].body, "true") def test_basic_concept_function(self): node = find_by_name(self.cursor, "basic_concept_function") @@ -420,13 +467,15 @@ def test_basic_concept_function(self): stub_lexicon(result) self.assertEqual(result.name, "basic_concept_function") self.assertEqual(result.body, None) + self.assertEqual(result.requires, None) self.assertEqual(len(result.arguments), 0) - self.assertEqual(result.return_type.is_generic, True) - self.assertEqual(result.return_type.details.name, "T") - self.assertEqual(result.template.requires, None) - self.assertEqual(result.template.parameters[0].name, "T") - self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") - self.assertEqual(result.template.parameters[0].specifier.body, "true") + self.assertEqual(result.return_type.details, BasicType.VOID) + self.assertEqual(len(result.template.requires), 5) + self.assertEqual(result.template.requires[0].name, "AlwaysTrue") + self.assertEqual(result.template.requires[0].is_requirement, True) + self.assertEqual(result.template.requires[0].body, "true") + self.assertEqual(result.template.requires[1:5], ["or", "true", "and", "false"]) + class TestFunctionsOverload(unittest.TestCase): def setUp(self): diff --git a/tests/parsing/unit/test_templates.py b/tests/parsing/unit/test_templates.py index a49dbfa..e60d219 100644 --- a/tests/parsing/unit/test_templates.py +++ b/tests/parsing/unit/test_templates.py @@ -6,8 +6,6 @@ from devana.syntax_abstraction.typeexpression import TypeModification from devana.syntax_abstraction.classinfo import * from devana.syntax_abstraction.typedefinfo import TypedefInfo -from devana.syntax_abstraction.conceptinfo import ConceptInfo -from tests.helpers import find_by_name class TestTemplateAdvanced(unittest.TestCase): @@ -15,11 +13,10 @@ class TestTemplateAdvanced(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() self.cursor = index.parse( - os.path.dirname(__file__) + r"/source_files/advanced_template.hpp", - args=("-std=c++20",) + os.path.dirname(__file__) + r"/source_files/advanced_template.hpp" ).cursor self.file = SourceFile.from_cursor(self.cursor) - self.assertEqual(len(self.file.content), 34) + self.assertEqual(len(self.file.content), 32) def test_functions_arguments(self): result: FunctionInfo = self.file.content[2] @@ -355,26 +352,3 @@ def test_template_method_spec_outside(self): self.assertEqual(result.template.specialisation_values[0].details, self.file.content[23]) self.assertEqual(len(result.template.specialisation_values[0].template_arguments), 1) self.assertEqual(result.template.specialisation_values[0].template_arguments[0].details, BasicType.CHAR) - - def test_template_concept(self): - result: ConceptInfo = self.file.content[32] - with self.subTest(result.name): - self.assertEqual(result.name, "TestConceptCase1") - self.assertEqual(result.body, "A{} and B{}") - self.assertEqual(len(result.template.parameters), 2) - self.assertEqual(len(result.template.specialisation_values), 0) - self.assertEqual(result.template.parameters[0].name, "A") - self.assertEqual(result.template.parameters[0].specifier, "typename") - self.assertEqual(result.template.parameters[1].name, "B") - self.assertEqual(result.template.parameters[1].specifier, "class") - self.assertEqual(result.template.requires, None) - - result = self.file.content[33] - with self.subTest(result.name): - self.assertEqual(result.name, "TestConceptCase2") - self.assertEqual(result.body, "false") - self.assertEqual(len(result.template.parameters), 1) - self.assertEqual(len(result.template.specialisation_values), 0) - self.assertEqual(result.template.parameters[0].name, "T") - self.assertEqual(result.template.parameters[0].specifier, "class") - self.assertEqual(result.template.requires, None) \ No newline at end of file From 1e5e75b1c5172f786043377676db2a36235b2ff5 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Mon, 3 Mar 2025 00:10:20 +0100 Subject: [PATCH 07/11] fix: separate '(' and ')' tokens in requires parsing --- src/devana/syntax_abstraction/functioninfo.py | 7 +++++-- src/devana/syntax_abstraction/templateinfo.py | 8 ++++++-- .../parsing/unit/source_files/template_class.hpp | 6 +++--- .../unit/source_files/template_functions.hpp | 2 +- tests/parsing/unit/test_class.py | 14 +++++++------- tests/parsing/unit/test_function.py | 16 ++++++++-------- 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/devana/syntax_abstraction/functioninfo.py b/src/devana/syntax_abstraction/functioninfo.py index 055fb5a..8a44b67 100644 --- a/src/devana/syntax_abstraction/functioninfo.py +++ b/src/devana/syntax_abstraction/functioninfo.py @@ -699,7 +699,10 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: # clang does not provide detailed info for all things in the requires (e.g., 'or', 'true'), # so we use a regex to extract missing elements. - raw_elements: List[str] = re.findall(r'((?:<[^>]+>|[^\s<])+)', match.group(1)) + raw_elements: List[str] = re.findall( + r'\(|\)|[^\s()<]+(?:\s*<\s*[^\s>]+(?:\s+[^\s>]+)*\s*>)?', + match.group(1) + ) cursors: List[cindex.Cursor] = list(find_concepts(self._cursor)) for raw_element in raw_elements: if len(cursors) > 0 and re.search(r'<[^>]+>', raw_element): @@ -710,7 +713,7 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: if maybe_concept is not None: self._requires.append(maybe_concept) continue - self._requires.append(raw_element.strip().strip("()")) + self._requires.append(raw_element.strip()) return self._requires @requires.setter diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 9ac451d..c603bfe 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -479,10 +479,14 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: cindex.CursorKind.PAREN_EXPR ): yield from find_concepts(child) + # clang does not provide info for all things in the requires (e.g., 'or', 'true'), # so we use a regex to extract missing elements. from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel) - raw_elements: List[str] = re.findall("((?:[^ <]+|<[^>]*>)+)", match.group(1)) + raw_elements: List[str] = re.findall( + r'\(|\)|[^\s()<]+(?:\s*<\s*[^\s>]+(?:\s+[^\s>]+)*\s*>)?', + match.group(1) + ) cursors: List[cindex.Cursor] = list(find_concepts(self._cursor)) for raw_element in raw_elements: @@ -494,7 +498,7 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: if maybe_concept is not None: self._requires.append(maybe_concept) continue - self._requires.append(raw_element.strip().strip("()")) + self._requires.append(raw_element.strip()) return self._requires @requires.setter diff --git a/tests/parsing/unit/source_files/template_class.hpp b/tests/parsing/unit/source_files/template_class.hpp index 891f20a..8bad673 100644 --- a/tests/parsing/unit/source_files/template_class.hpp +++ b/tests/parsing/unit/source_files/template_class.hpp @@ -119,8 +119,8 @@ template requires true class ConceptClass { public: T a; - void process(T arg) requires TestConcept or - true; + void process(T arg) + requires TestConcept; }; template @@ -128,6 +128,6 @@ template true and TestConcept< T> struct ConceptStruct { T abc = 10; - T foo() requires TestConcept; + T foo() requires (TestConcept || true); T barFoo(T arg1, A arg2); }; \ No newline at end of file diff --git a/tests/parsing/unit/source_files/template_functions.hpp b/tests/parsing/unit/source_files/template_functions.hpp index 57a18a1..d701c43 100644 --- a/tests/parsing/unit/source_files/template_functions.hpp +++ b/tests/parsing/unit/source_files/template_functions.hpp @@ -22,7 +22,7 @@ void requires_template_function1(T a) requires true or false; template requires true or AlwaysTrue int requires_template_function2(T a = 1) - requires AlwaysTrue and true; + requires AlwaysTrue and (true); template requires (AlwaysTrue or true) and false void basic_concept_function(); diff --git a/tests/parsing/unit/test_class.py b/tests/parsing/unit/test_class.py index c504fb9..9aac00a 100644 --- a/tests/parsing/unit/test_class.py +++ b/tests/parsing/unit/test_class.py @@ -772,12 +772,10 @@ def test_concept_class(self): self.assertEqual(method.body, None) self.assertEqual(method.arguments[0].type.is_generic, True) self.assertEqual(method.arguments[0].type.details.name, "T") - self.assertEqual(len(method.requires), 3) + self.assertEqual(len(method.requires), 1) self.assertEqual(method.requires[0].name, "TestConcept") self.assertEqual(method.requires[0].body, "true") self.assertEqual(method.requires[0].is_requirement, True) - self.assertEqual(method.requires[1], "or") - self.assertEqual(method.requires[2], "true") def test_concept_struct(self): node = find_by_name(self.cursor, "ConceptStruct") @@ -808,10 +806,12 @@ def test_concept_struct(self): self.assertEqual(method.return_type.name, "T") self.assertEqual(len(method.arguments), 0) self.assertEqual(method.body, None) - self.assertEqual(len(method.requires), 1) - self.assertEqual(method.requires[0].name, "TestConcept") - self.assertEqual(method.requires[0].is_requirement, True) - self.assertEqual(method.requires[0].body, "true") + self.assertEqual(len(method.requires), 5) + self.assertEqual(method.requires[0], "(") + self.assertEqual(method.requires[1].name, "TestConcept") + self.assertEqual(method.requires[1].is_requirement, True) + self.assertEqual(method.requires[1].body, "true") + self.assertEqual(method.requires[2:5], ["||", "true", ")"]) method: MethodInfo = cast(MethodInfo, result.public[2]) self.assertEqual(method.name, "barFoo") diff --git a/tests/parsing/unit/test_function.py b/tests/parsing/unit/test_function.py index 0709ed7..0c35251 100644 --- a/tests/parsing/unit/test_function.py +++ b/tests/parsing/unit/test_function.py @@ -444,12 +444,11 @@ def test_requires_template_function2(self): self.assertEqual(result.arguments[0].type.is_generic, True) self.assertEqual(result.arguments[0].type.details.name, "T") - self.assertEqual(len(result.requires), 3) + self.assertEqual(len(result.requires), 5) self.assertEqual(result.requires[0].name, "AlwaysTrue") self.assertEqual(result.requires[0].is_requirement, True) self.assertEqual(result.requires[0].body, "true") - self.assertEqual(result.requires[1], "and") - self.assertEqual(result.requires[2], "true") + self.assertEqual(result.requires[1:5], ["and", "(", "true", ")"]) self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier.name, "AlwaysTrue") self.assertEqual(result.template.parameters[0].specifier.body, "true") @@ -470,11 +469,12 @@ def test_basic_concept_function(self): self.assertEqual(result.requires, None) self.assertEqual(len(result.arguments), 0) self.assertEqual(result.return_type.details, BasicType.VOID) - self.assertEqual(len(result.template.requires), 5) - self.assertEqual(result.template.requires[0].name, "AlwaysTrue") - self.assertEqual(result.template.requires[0].is_requirement, True) - self.assertEqual(result.template.requires[0].body, "true") - self.assertEqual(result.template.requires[1:5], ["or", "true", "and", "false"]) + self.assertEqual(len(result.template.requires), 7) + self.assertEqual(result.template.requires[0], "(") + self.assertEqual(result.template.requires[1].name, "AlwaysTrue") + self.assertEqual(result.template.requires[1].is_requirement, True) + self.assertEqual(result.template.requires[1].body, "true") + self.assertEqual(result.template.requires[2:7], ["or", "true", ")", "and", "false"]) class TestFunctionsOverload(unittest.TestCase): From 56d1dd1779465487c827b719d7974021361b2805 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Tue, 4 Mar 2025 19:17:21 +0100 Subject: [PATCH 08/11] feat: add template and concept support for using --- .pylintrc | 3 +- .../printers/default/usingprinter.py | 18 +++++ src/devana/syntax_abstraction/conceptinfo.py | 17 +++-- src/devana/syntax_abstraction/functioninfo.py | 1 - src/devana/syntax_abstraction/templateinfo.py | 24 ++---- .../syntax_abstraction/typeexpression.py | 15 +++- src/devana/syntax_abstraction/using.py | 36 ++++++++- tests/code_generation/unit/test_concept.py | 18 ++++- tests/code_generation/unit/test_using.py | 62 ++++++++++++++++ tests/parsing/unit/source_files/using.hpp | 14 +++- tests/parsing/unit/test_function.py | 2 + tests/parsing/unit/test_using.py | 74 +++++++++++++++++-- 12 files changed, 247 insertions(+), 37 deletions(-) diff --git a/.pylintrc b/.pylintrc index b2451f3..a418d8e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -440,8 +440,7 @@ disable=raw-checker-failed, consider-using-dict-items, no-else-break, wildcard-import, - unused-wildcard-import, - too-many-positional-arguments + unused-wildcard-import diff --git a/src/devana/code_generation/printers/default/usingprinter.py b/src/devana/code_generation/printers/default/usingprinter.py index 7baf106..6db9855 100644 --- a/src/devana/code_generation/printers/default/usingprinter.py +++ b/src/devana/code_generation/printers/default/usingprinter.py @@ -21,6 +21,24 @@ def print(self, source: Using, config: Optional[PrinterConfiguration] = None, if source.associated_comment: formatter.print_line(self.printer_dispatcher.print(source.associated_comment, config, source)) + + template_prefix = "" + if source.template: + parameters = [] + for p in source.template.parameters: + parameters.append(self.printer_dispatcher.print(p, config, source)) + parameters = ','.join(parameters) + template_prefix = f"template<{parameters}>" + if source.template.requires: + template_prefix += " requires" + for req in source.template.requires: + if isinstance(req, str): + template_prefix += f" {req}" + continue + template_prefix += f" {self.printer_dispatcher.print(req, config, source)}" + if template_prefix: + formatter.print_line(template_prefix) + formatter.line = f"using {source.name} = {self.printer_dispatcher.print(source.type_info, config, source)};" formatter.next_line() return formatter.text diff --git a/src/devana/syntax_abstraction/conceptinfo.py b/src/devana/syntax_abstraction/conceptinfo.py index 088196f..dfa3b53 100644 --- a/src/devana/syntax_abstraction/conceptinfo.py +++ b/src/devana/syntax_abstraction/conceptinfo.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import Optional, List, Any from clang import cindex @@ -5,7 +6,6 @@ from devana.syntax_abstraction.typeexpression import TypeExpression from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.syntax import ISyntaxElement -from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.init_params import init_params from devana.utility.errors import ParserError @@ -17,6 +17,8 @@ class ConceptInfo(CodeContainer): def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[CodeContainer] = None): super().__init__(cursor, parent) if cursor is None: + from devana.syntax_abstraction.templateinfo import TemplateInfo # pylint: disable=import-outside-toplevel + self._name = "DefaultConcept" self._body = "true" self._template = TemplateInfo.from_params(parameters=[ @@ -55,7 +57,7 @@ def from_params( # pylint: disable=unused-argument namespace: Optional[str] = None, name: Optional[str] = None, body: Optional[str] = None, - template: Optional[TemplateInfo] = None, + template: Optional[ISyntaxElement] = None, parameters: Optional[List[TypeExpression]] = None, is_requirement: Optional[bool] = None ) -> "ConceptInfo": @@ -77,17 +79,20 @@ def name(self, value) -> None: @property @lazy_invoke - def template(self) -> TemplateInfo: + def template(self) -> ISyntaxElement: + """Template associated with this concept.""" + from devana.syntax_abstraction.templateinfo import TemplateInfo # pylint: disable=import-outside-toplevel self._template = TemplateInfo.from_cursor(self._cursor) return self._template @template.setter - def template(self, value: TemplateInfo) -> None: + def template(self, value: ISyntaxElement) -> None: self._template = value @property @lazy_invoke def body(self) -> str: + """The body of the concept, which defines its constraint expression.""" self._body = "" for child in self._cursor.get_children(): if child.kind != cindex.CursorKind.TEMPLATE_TYPE_PARAMETER: @@ -103,6 +108,7 @@ def body(self, value: str) -> None: @lazy_invoke def parameters(self) -> List[TypeExpression]: """Retrieves the concept parameters '<...>'.""" + from devana.syntax_abstraction.templateinfo import TemplateInfo # pylint: disable=import-outside-toplevel if not isinstance(self.parent, TemplateInfo.TemplateParameter): return [] # Probably without a cursor from the parent it will not be possible to extract it. @@ -118,7 +124,8 @@ def parameters(self, value: List[TypeExpression]) -> None: @lazy_invoke def is_requirement(self) -> bool: """Determines whether this ConceptInfo instance is acting as a requirement.""" - from devana.syntax_abstraction.functioninfo import FunctionInfo # pylint: disable=import-outside-toplevel) + from devana.syntax_abstraction.functioninfo import FunctionInfo # pylint: disable=import-outside-toplevel + from devana.syntax_abstraction.templateinfo import TemplateInfo # pylint: disable=import-outside-toplevel self._is_requirement = isinstance( self.parent, ( TemplateInfo.TemplateParameter, diff --git a/src/devana/syntax_abstraction/functioninfo.py b/src/devana/syntax_abstraction/functioninfo.py index 8a44b67..5f31d83 100644 --- a/src/devana/syntax_abstraction/functioninfo.py +++ b/src/devana/syntax_abstraction/functioninfo.py @@ -698,7 +698,6 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: yield from find_concepts(child) # clang does not provide detailed info for all things in the requires (e.g., 'or', 'true'), - # so we use a regex to extract missing elements. raw_elements: List[str] = re.findall( r'\(|\)|[^\s()<]+(?:\s*<\s*[^\s>]+(?:\s+[^\s>]+)*\s*>)?', match.group(1) diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index c603bfe..fb51993 100644 --- a/src/devana/syntax_abstraction/templateinfo.py +++ b/src/devana/syntax_abstraction/templateinfo.py @@ -1,9 +1,10 @@ import re from pathlib import Path -from typing import Optional, List, Union, Tuple, Any, Iterable, TYPE_CHECKING +from typing import Optional, List, Union, Tuple, Any, Iterable from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression, TypeModification +from devana.syntax_abstraction.conceptinfo import ConceptInfo from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.utility.lazy import LazyNotInit, lazy_invoke @@ -13,9 +14,6 @@ from devana.syntax_abstraction.syntax import ISyntaxElement from devana.configuration import Configuration -if TYPE_CHECKING: - from devana.syntax_abstraction.conceptinfo import ConceptInfo - class GenericTypeParameter(ISyntaxElement): """An unresolved generic template parameter, known idiomatically in C++ as T.""" @@ -33,7 +31,7 @@ def name(self, value): self._name = value @staticmethod - def from_cursor(type_c, cursor: cindex.Type, parent: Optional = None) -> Optional["GenericTypeParameter"]: + def from_cursor(type_c, cursor: cindex.Cursor, parent: Optional = None) -> Optional["GenericTypeParameter"]: if type_c.kind == cindex.TypeKind.UNEXPOSED: if type_c.get_num_template_arguments() > 0: return None @@ -65,7 +63,6 @@ class TemplateInfo(IBasicCreatable, ICursorValidate, ISyntaxElement): class TemplateParameter(IBasicCreatable, ICursorValidate, ISyntaxElement): """A description of the generic component for the type/function claim.""" - def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = None): self._cursor = cursor self._parent = parent @@ -92,7 +89,7 @@ def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParame def from_params( # pylint: disable=unused-argument cls, parent: Optional[ISyntaxElement] = None, - specifier: Optional[Union[str, "ConceptInfo"]] = None, + specifier: Optional[Union[str, ConceptInfo]] = None, name: Optional[str] = None, default_value: Optional[str] = None, is_variadic: Optional[bool] = None, @@ -112,10 +109,8 @@ def is_cursor_valid(cursor: cindex.Cursor) -> bool: @property @lazy_invoke - def specifier(self) -> Union["ConceptInfo", str]: + def specifier(self) -> Union[ConceptInfo, str]: """Keyword or ConceptInfo instance preceding the name.""" - from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel - cursors = filter( lambda c: c.kind == cindex.CursorKind.TEMPLATE_REF and c.referenced, self._cursor.get_children() @@ -129,8 +124,7 @@ def specifier(self) -> Union["ConceptInfo", str]: return self._specifier @specifier.setter - def specifier(self, value: Union["ConceptInfo", str]): - from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel + def specifier(self, value: Union[ConceptInfo, str]): if not isinstance(value, ConceptInfo) and value not in ("class", "typename"): raise ValueError("Specifier must be class, typename, or an instance of ConceptInfo.") self._specifier = value @@ -241,7 +235,8 @@ def is_cursor_valid(cursor: cindex.Cursor) -> bool: cindex.CursorKind.FUNCTION_TEMPLATE, cindex.CursorKind.CLASS_TEMPLATE, cindex.CursorKind.CLASS_TEMPLATE_PARTIAL_SPECIALIZATION, - cindex.CursorKind.CONCEPT_DECL + cindex.CursorKind.CONCEPT_DECL, + cindex.CursorKind.TYPE_ALIAS_TEMPLATE_DECL ) return cursor.kind in valid_cursors or re.search( r"template\s*<>", CodePiece(cursor).text @@ -481,14 +476,11 @@ def find_concepts(cursor: cindex.Cursor) -> Iterable[cindex.Cursor]: yield from find_concepts(child) # clang does not provide info for all things in the requires (e.g., 'or', 'true'), - # so we use a regex to extract missing elements. - from devana.syntax_abstraction.conceptinfo import ConceptInfo # pylint: disable=import-outside-toplevel) raw_elements: List[str] = re.findall( r'\(|\)|[^\s()<]+(?:\s*<\s*[^\s>]+(?:\s+[^\s>]+)*\s*>)?', match.group(1) ) cursors: List[cindex.Cursor] = list(find_concepts(self._cursor)) - for raw_element in raw_elements: if len(cursors) > 0 and re.search(r'<[^>]+>', raw_element): maybe_concept = ConceptInfo.from_cursor( diff --git a/src/devana/syntax_abstraction/typeexpression.py b/src/devana/syntax_abstraction/typeexpression.py index 33c2378..1512d6e 100644 --- a/src/devana/syntax_abstraction/typeexpression.py +++ b/src/devana/syntax_abstraction/typeexpression.py @@ -419,6 +419,7 @@ def __init__(self, cursor: Optional[Union[cindex.Cursor, cindex.Type]] = None, p self._base_type_c = self._cursor.underlying_typedef_type elif self._cursor.kind == cindex.CursorKind.TYPE_ALIAS_DECL: self._base_type_c = self._cursor.type.get_canonical() + self._lexicon = Lexicon.create(self) @classmethod @@ -660,9 +661,21 @@ def template_arguments(self) -> Optional[List["TypeExpression"]]: type_c.kind is cindex.TypeKind.ELABORATED and match is None): self._template_arguments = None return self._template_arguments + + params = tuple( + t.spelling for t in getattr(self._cursor, "get_children", lambda: [])() + if t.kind == cindex.CursorKind.TYPE_REF + ) for i in range(type_c.get_num_template_arguments()): el = type_c.get_template_argument_type(i) - self._template_arguments.append(TypeExpression(el, self)) + type_expr = TypeExpression(el, self) + if type_expr.is_generic: + match = re.match(r"type-parameter-(\d+)-(\d+)", type_expr.name) + if match: + param_name = params[int(match.group(2))] + type_expr._name = param_name + type_expr.details._name = param_name + self._template_arguments.append(type_expr) if not self._template_arguments: self._template_arguments = None diff --git a/src/devana/syntax_abstraction/using.py b/src/devana/syntax_abstraction/using.py index 3beb960..55fdef4 100644 --- a/src/devana/syntax_abstraction/using.py +++ b/src/devana/syntax_abstraction/using.py @@ -3,6 +3,7 @@ from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression from devana.syntax_abstraction.organizers.codecontainer import CodeContainer +from devana.syntax_abstraction.templateinfo import TemplateInfo from devana.syntax_abstraction.comment import Comment from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.utility.errors import ParserError @@ -24,6 +25,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._text_source = None self._name = "" self._associated_comment = None + self._template = None else: if not self.is_cursor_valid(cursor): raise ParserError("Element is not using type alias.") @@ -31,6 +33,7 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._text_source = LazyNotInit self._name = LazyNotInit self._associated_comment = LazyNotInit + self._template = LazyNotInit self._lexicon = Lexicon.create(self) @classmethod @@ -46,8 +49,9 @@ def from_params( # pylint: disable=unused-argument parent: Optional[ISyntaxElement] = None, type_info: Union[TypeExpression, ISyntaxElement, None] = None, name: Optional[str] = None, + template: Optional[TemplateInfo] = None, lexicon: Optional[Lexicon] = None, - associated_comment: Optional[Comment] = None, + associated_comment: Optional[Comment] = None ) -> "Using": return cls(None, parent) @@ -55,13 +59,21 @@ def from_params( # pylint: disable=unused-argument def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind in ( cindex.CursorKind.TYPE_ALIAS_DECL, + cindex.CursorKind.TYPE_ALIAS_TEMPLATE_DECL ) @property @lazy_invoke def type_info(self) -> Union[TypeExpression, "ISyntaxElement"]: """Type alias can be true type or next typedef.""" - self._type_info = TypeExpression(self._cursor, self) + cursor = self._cursor + for child in self._cursor.get_children(): + # template using scenario + if child.kind == cindex.CursorKind.TYPE_ALIAS_DECL: + cursor = child + break + + self._type_info = TypeExpression(cursor, self) return self._type_info @type_info.setter @@ -72,7 +84,14 @@ def type_info(self, value): @lazy_invoke def name(self) -> str: """Typedef alias name.""" - self._name = self._cursor.type.get_typedef_name() + cursor = self._cursor + for child in self._cursor.get_children(): + # template using scenario + if child.kind == cindex.CursorKind.TYPE_ALIAS_DECL: + cursor = child + break + + self._name = cursor.type.get_typedef_name() return self._name @name.setter @@ -86,6 +105,17 @@ def text_source(self) -> Optional[CodePiece]: self._text_source = CodePiece(self._cursor) return self._text_source + @property + @lazy_invoke + def template(self) -> Optional[TemplateInfo]: + """Template associated with this using.""" + self._template = TemplateInfo.from_cursor(self._cursor) + return self._template + + @template.setter + def template(self, value: Optional[TemplateInfo]) -> None: + self._template = value + @property def parent(self) -> CodeContainer: """Structural parent element like file, namespace or class.""" diff --git a/tests/code_generation/unit/test_concept.py b/tests/code_generation/unit/test_concept.py index a9cdb96..b6a17e6 100644 --- a/tests/code_generation/unit/test_concept.py +++ b/tests/code_generation/unit/test_concept.py @@ -20,6 +20,9 @@ def setUp(self): self.printer = CodePrinter() self.printer.register(ConceptPrinter, ConceptInfo) self.printer.register(TemplateParameterPrinter, TemplateInfo.TemplateParameter) + self.printer.register(TypeExpressionPrinter, TypeExpression) + self.printer.register(BasicTypePrinter, GenericTypeParameter) + self.printer.register(BasicTypePrinter, BasicType) def test_print_simple_concept(self): concept = ConceptInfo.from_params( @@ -74,10 +77,19 @@ def test_print_concept_skip_requires(self): result = self.printer.print(concept) self.assertEqual(result, "template\nconcept DefaultConcept = true;\n") - def test_print_concept_requirement(self): + def test_print_concept_as_requirement(self): concept = ConceptInfo.from_params(name="Test", is_requirement=True) - result = self.printer.print(concept) - self.assertEqual(result, "Test") + with self.subTest("No parameters"): + result = self.printer.print(concept) + self.assertEqual(result, "Test") + + with self.subTest("With parameters"): + concept.parameters = [ + TypeExpression.from_params(details=GenericTypeParameter("T")), + TypeExpression.create_default() + ] + result = self.printer.print(concept) + self.assertEqual(result, "Test") class TestConceptClass(unittest.TestCase): diff --git a/tests/code_generation/unit/test_using.py b/tests/code_generation/unit/test_using.py index 409d573..e5a9ccf 100644 --- a/tests/code_generation/unit/test_using.py +++ b/tests/code_generation/unit/test_using.py @@ -1,9 +1,13 @@ import unittest from devana.code_generation.printers.default.basictypeprinter import BasicTypePrinter from devana.code_generation.printers.default.typeexpressionprinter import TypeExpressionPrinter +from devana.code_generation.printers.default.templateparameterprinter import TemplateParameterPrinter +from devana.code_generation.printers.default.conceptprinter import ConceptPrinter from devana.code_generation.printers.default.usingprinter import UsingPrinter from devana.code_generation.printers.codeprinter import CodePrinter from devana.syntax_abstraction.using import Using +from devana.syntax_abstraction.conceptinfo import ConceptInfo +from devana.syntax_abstraction.templateinfo import TemplateInfo, GenericTypeParameter from devana.syntax_abstraction.typeexpression import BasicType, TypeExpression, TypeModification @@ -14,6 +18,9 @@ def setUp(self): printer.register(BasicTypePrinter, BasicType) printer.register(TypeExpressionPrinter, TypeExpression) printer.register(UsingPrinter, Using) + printer.register(TemplateParameterPrinter, TemplateInfo.TemplateParameter) + printer.register(BasicTypePrinter, GenericTypeParameter) + printer.register(ConceptPrinter, ConceptInfo) self.printer: CodePrinter = printer def test_definition_basic(self): @@ -24,3 +31,58 @@ def test_definition_basic(self): source.type_info.modification |= TypeModification.POINTER | TypeModification.CONST result = self.printer.print(source) self.assertEqual(result, "using const_ptr_t = const char*;\n") + + def test_using_template(self): + source = Using.from_params( + name="constType", + type_info=TypeExpression.from_params( + details=GenericTypeParameter("T"), + modification=TypeModification.CONST + ), + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + specifier="typename", + name="T" + )] + ) + ) + result = self.printer.print(source) + self.assertEqual(result, "template\nusing constType = const T;\n") + + def test_using_template_with_requires(self): + source = Using.from_params( + name="constexprType", + type_info=TypeExpression.from_params( + details=GenericTypeParameter("B"), + modification=TypeModification.CONSTEXPR + ), + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + specifier="typename", + name="B" + )], + requires=["true"] + ) + ) + result = self.printer.print(source) + self.assertEqual(result, "template requires true\nusing constexprType = constexpr B;\n") + + def test_using_template_with_concept(self): + source = Using.from_params( + name="ConceptPtr", + type_info=TypeExpression.from_params( + details=GenericTypeParameter("C"), + modification=TypeModification.POINTER + ), + template=TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + specifier=ConceptInfo.from_params(name="Concept", is_requirement=True), + name="C" + )] + ) + ) + result = self.printer.print(source) + self.assertEqual(result, "template\nusing ConceptPtr = C*;\n") diff --git a/tests/parsing/unit/source_files/using.hpp b/tests/parsing/unit/source_files/using.hpp index a3e41f1..c11affc 100644 --- a/tests/parsing/unit/source_files/using.hpp +++ b/tests/parsing/unit/source_files/using.hpp @@ -27,4 +27,16 @@ namespace num { char x; int y; }; -} \ No newline at end of file +} + +template +concept TestConcept = true; + +template +using UsingTemplate = A; + +template requires true or TestConcept +using UsingTemplateRequires = const A_T; + +template +using UsingConcept = const UsingTemplateRequires*; \ No newline at end of file diff --git a/tests/parsing/unit/test_function.py b/tests/parsing/unit/test_function.py index 0c35251..72145c3 100644 --- a/tests/parsing/unit/test_function.py +++ b/tests/parsing/unit/test_function.py @@ -419,12 +419,14 @@ def test_requires_template_function1(self): self.assertEqual(result.body, None) self.assertEqual(result.return_type.details, BasicType.VOID) self.assertEqual(result.requires, ["true", "or", "false"]) + self.assertEqual(len(result.arguments), 1) self.assertEqual(result.arguments[0].name, "a") self.assertEqual(result.arguments[0].type.is_generic, True) self.assertEqual(result.arguments[0].type.details.name, "T") self.assertEqual(result.template.parameters[0].name, "T") self.assertEqual(result.template.parameters[0].specifier, "typename") + self.assertEqual(len(result.template.requires), 1) self.assertEqual(result.template.requires[0].name, "AlwaysTrue") self.assertEqual(result.template.requires[0].is_requirement, True) diff --git a/tests/parsing/unit/test_using.py b/tests/parsing/unit/test_using.py index ad39291..fff4121 100644 --- a/tests/parsing/unit/test_using.py +++ b/tests/parsing/unit/test_using.py @@ -26,6 +26,7 @@ def test_using_as_simple_alias(self): self.assertEqual(source.type_info.details, self.file.content[0].content[0]) fnc: FunctionInfo = self.file.content[2] self.assertEqual(fnc.arguments[0].type.details, source) + self.assertEqual(source.template, None) def test_using_as_template_alias(self): source: Using = self.file.content[4] @@ -34,9 +35,72 @@ def test_using_as_template_alias(self): self.assertEqual(source.type_info.details, self.file.content[3]) self.assertEqual(len(source.type_info.template_arguments), 1) self.assertEqual(source.type_info.template_arguments[0].details, BasicType.DOUBLE) + self.assertEqual(source.template, None) - def test_using_as_concept_template_alias(self): - pass - # todo: Add support to this - #source: Using = self.file.content[6] - # self.assertEqual(source.name, "TestConceptType") + def test_using_with_template(self): + source: Using = self.file.content[7] + self.assertEqual(source.name, "UsingTemplate") + self.assertEqual(source.type_info.is_generic, True) + self.assertEqual(source.type_info.details.name, "A") + self.assertEqual(source.associated_comment, None) + self.assertNotEqual(source.template, None) + self.assertEqual(source.template.requires, None) + + self.assertEqual(len(source.template.parameters), 3) + self.assertEqual(source.template.parameters[0].specifier, "typename") + self.assertEqual(source.template.parameters[0].name, "A") + self.assertEqual(source.template.parameters[0].default_value, None) + self.assertEqual(source.template.parameters[0].is_variadic, False) + self.assertEqual(source.template.parameters[1].specifier, "class") + self.assertEqual(source.template.parameters[1].name, "B") + self.assertEqual(source.template.parameters[1].default_value, "float") + self.assertEqual(source.template.parameters[1].is_variadic, False) + self.assertEqual(source.template.parameters[2].specifier, "typename") + self.assertEqual(source.template.parameters[2].name, "Args") + self.assertEqual(source.template.parameters[2].default_value, None) + self.assertEqual(source.template.parameters[2].is_variadic, True) + + def test_using_with_template_requires(self): + source: Using = self.file.content[8] + self.assertEqual(source.name, "UsingTemplateRequires") + self.assertEqual(source.type_info.is_generic, False) + self.assertEqual(source.type_info.modification.is_const, True) + self.assertEqual(source.type_info.details, self.file.content[3]) + self.assertEqual(len(source.type_info.template_arguments), 1) + self.assertEqual(source.type_info.template_arguments[0].is_generic, True) + self.assertEqual(source.type_info.template_arguments[0].details.name, "T") + self.assertEqual(source.associated_comment, None) + self.assertNotEqual(source.template, None) + + self.assertEqual(len(source.template.requires), 3) + self.assertEqual(source.template.requires[0:2], ["true", "or"]) + self.assertEqual(source.template.requires[2].name, "TestConcept") + self.assertEqual(source.template.requires[2].is_requirement, True) + self.assertEqual(source.template.requires[2].body, "true") + self.assertEqual(len(source.template.parameters), 1) + self.assertEqual(source.template.parameters[0].specifier, "typename") + self.assertEqual(source.template.parameters[0].name, "T") + self.assertEqual(source.template.parameters[0].default_value, None) + self.assertEqual(source.template.parameters[0].is_variadic, False) + + def test_using_with_concept(self): + source: Using = self.file.content[9] + self.assertEqual(source.name, "UsingConcept") + self.assertEqual(source.type_info.is_generic, False) + self.assertEqual(source.type_info.modification.is_const, True) + self.assertEqual(source.type_info.modification.is_pointer, True) + self.assertEqual(source.type_info.details, self.file.content[3]) + self.assertEqual(len(source.type_info.template_arguments), 1) + self.assertEqual(source.type_info.template_arguments[0].is_generic, True) + self.assertEqual(source.type_info.template_arguments[0].details.name, "B") + self.assertEqual(source.associated_comment, None) + self.assertNotEqual(source.template, None) + self.assertEqual(source.template.requires, None) + + self.assertEqual(len(source.template.parameters), 1) + self.assertEqual(source.template.parameters[0].specifier.name, "TestConcept") + self.assertEqual(source.template.parameters[0].specifier.is_requirement, True) + self.assertEqual(source.template.parameters[0].specifier.body, "true") + self.assertEqual(source.template.parameters[0].name, "B") + self.assertEqual(source.template.parameters[0].default_value, "int") + self.assertEqual(source.template.parameters[0].is_variadic, False) From d81752537c6117b22398414a975c586d208fe26b Mon Sep 17 00:00:00 2001 From: xXenvy Date: Tue, 4 Mar 2025 19:24:11 +0100 Subject: [PATCH 09/11] fix: ignore pylint messages --- src/devana/syntax_abstraction/typeexpression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/devana/syntax_abstraction/typeexpression.py b/src/devana/syntax_abstraction/typeexpression.py index 1512d6e..203e7a2 100644 --- a/src/devana/syntax_abstraction/typeexpression.py +++ b/src/devana/syntax_abstraction/typeexpression.py @@ -673,8 +673,8 @@ def template_arguments(self) -> Optional[List["TypeExpression"]]: match = re.match(r"type-parameter-(\d+)-(\d+)", type_expr.name) if match: param_name = params[int(match.group(2))] - type_expr._name = param_name - type_expr.details._name = param_name + type_expr._name = param_name # pylint: disable=protected-access + type_expr.details._name = param_name # pylint: disable=protected-access self._template_arguments.append(type_expr) if not self._template_arguments: From f63355441256c6b9f6d33736ba4a0e2c06942aae Mon Sep 17 00:00:00 2001 From: xXenvy Date: Fri, 7 Mar 2025 21:55:11 +0100 Subject: [PATCH 10/11] fix: invalid order of args in `template_arguments` when template using --- .../syntax_abstraction/typeexpression.py | 14 +++++++----- tests/parsing/unit/source_files/using.hpp | 9 +++++--- tests/parsing/unit/test_using.py | 22 ++++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/devana/syntax_abstraction/typeexpression.py b/src/devana/syntax_abstraction/typeexpression.py index 203e7a2..3352b26 100644 --- a/src/devana/syntax_abstraction/typeexpression.py +++ b/src/devana/syntax_abstraction/typeexpression.py @@ -662,17 +662,19 @@ def template_arguments(self) -> Optional[List["TypeExpression"]]: self._template_arguments = None return self._template_arguments - params = tuple( - t.spelling for t in getattr(self._cursor, "get_children", lambda: [])() - if t.kind == cindex.CursorKind.TYPE_REF - ) + params = [] + if isinstance(self._cursor, cindex.Cursor) and self._cursor.kind == cindex.CursorKind.TYPE_ALIAS_DECL: + match = re.search(r'<([^>]+)>', CodePiece(self._cursor).text) + if match: + params = [param.strip() for param in match.group(1).split(',')] + for i in range(type_c.get_num_template_arguments()): el = type_c.get_template_argument_type(i) type_expr = TypeExpression(el, self) if type_expr.is_generic: match = re.match(r"type-parameter-(\d+)-(\d+)", type_expr.name) - if match: - param_name = params[int(match.group(2))] + if match and params: + param_name = params.pop(0) type_expr._name = param_name # pylint: disable=protected-access type_expr.details._name = param_name # pylint: disable=protected-access self._template_arguments.append(type_expr) diff --git a/tests/parsing/unit/source_files/using.hpp b/tests/parsing/unit/source_files/using.hpp index c11affc..95fb01d 100644 --- a/tests/parsing/unit/source_files/using.hpp +++ b/tests/parsing/unit/source_files/using.hpp @@ -29,14 +29,17 @@ namespace num { }; } +template +struct TestStruct; + template concept TestConcept = true; template using UsingTemplate = A; -template requires true or TestConcept -using UsingTemplateRequires = const A_T; +template requires true or TestConcept +using UsingTemplateRequires = const TestStruct; template -using UsingConcept = const UsingTemplateRequires*; \ No newline at end of file +using UsingConcept = const UsingTemplateRequires*; \ No newline at end of file diff --git a/tests/parsing/unit/test_using.py b/tests/parsing/unit/test_using.py index fff4121..032978e 100644 --- a/tests/parsing/unit/test_using.py +++ b/tests/parsing/unit/test_using.py @@ -38,7 +38,7 @@ def test_using_as_template_alias(self): self.assertEqual(source.template, None) def test_using_with_template(self): - source: Using = self.file.content[7] + source: Using = self.file.content[8] self.assertEqual(source.name, "UsingTemplate") self.assertEqual(source.type_info.is_generic, True) self.assertEqual(source.type_info.details.name, "A") @@ -61,14 +61,16 @@ def test_using_with_template(self): self.assertEqual(source.template.parameters[2].is_variadic, True) def test_using_with_template_requires(self): - source: Using = self.file.content[8] + source: Using = self.file.content[9] self.assertEqual(source.name, "UsingTemplateRequires") self.assertEqual(source.type_info.is_generic, False) self.assertEqual(source.type_info.modification.is_const, True) - self.assertEqual(source.type_info.details, self.file.content[3]) - self.assertEqual(len(source.type_info.template_arguments), 1) + self.assertEqual(source.type_info.details, self.file.content[6]) + self.assertEqual(len(source.type_info.template_arguments), 2) self.assertEqual(source.type_info.template_arguments[0].is_generic, True) - self.assertEqual(source.type_info.template_arguments[0].details.name, "T") + self.assertEqual(source.type_info.template_arguments[0].details.name, "C") + self.assertEqual(source.type_info.template_arguments[1].is_generic, True) + self.assertEqual(source.type_info.template_arguments[1].details.name, "T") self.assertEqual(source.associated_comment, None) self.assertNotEqual(source.template, None) @@ -77,22 +79,22 @@ def test_using_with_template_requires(self): self.assertEqual(source.template.requires[2].name, "TestConcept") self.assertEqual(source.template.requires[2].is_requirement, True) self.assertEqual(source.template.requires[2].body, "true") - self.assertEqual(len(source.template.parameters), 1) + self.assertEqual(len(source.template.parameters), 2) self.assertEqual(source.template.parameters[0].specifier, "typename") self.assertEqual(source.template.parameters[0].name, "T") self.assertEqual(source.template.parameters[0].default_value, None) self.assertEqual(source.template.parameters[0].is_variadic, False) def test_using_with_concept(self): - source: Using = self.file.content[9] + source: Using = self.file.content[10] self.assertEqual(source.name, "UsingConcept") self.assertEqual(source.type_info.is_generic, False) self.assertEqual(source.type_info.modification.is_const, True) self.assertEqual(source.type_info.modification.is_pointer, True) - self.assertEqual(source.type_info.details, self.file.content[3]) - self.assertEqual(len(source.type_info.template_arguments), 1) + self.assertEqual(source.type_info.details, self.file.content[6]) + self.assertEqual(len(source.type_info.template_arguments), 2) self.assertEqual(source.type_info.template_arguments[0].is_generic, True) - self.assertEqual(source.type_info.template_arguments[0].details.name, "B") + self.assertEqual(source.type_info.template_arguments[0].details.name, "float") self.assertEqual(source.associated_comment, None) self.assertNotEqual(source.template, None) self.assertEqual(source.template.requires, None) From f161743803b18546568575658c62a108c9b373e9 Mon Sep 17 00:00:00 2001 From: xXenvy Date: Sun, 9 Mar 2025 21:27:14 +0100 Subject: [PATCH 11/11] feat(tests): concept namespace and parameters tests --- .../source_files/{concept.hpp => concepts.hpp} | 8 +++++--- .../unit/source_files/template_functions.hpp | 10 +++++++++- tests/parsing/unit/test_concept.py | 18 ++++++++++++++++-- tests/parsing/unit/test_function.py | 18 ++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) rename tests/parsing/unit/source_files/{concept.hpp => concepts.hpp} (87%) diff --git a/tests/parsing/unit/source_files/concept.hpp b/tests/parsing/unit/source_files/concepts.hpp similarity index 87% rename from tests/parsing/unit/source_files/concept.hpp rename to tests/parsing/unit/source_files/concepts.hpp index 84f27c9..f300b5c 100644 --- a/tests/parsing/unit/source_files/concept.hpp +++ b/tests/parsing/unit/source_files/concepts.hpp @@ -43,14 +43,16 @@ concept ConceptCase9 = ConceptCase1 && ConceptCase2; template concept ConceptCase10 = ConceptCase1 || ConceptCase2; -template -concept ConceptCase11 = true; +namespace testNamespace { + template + concept ConceptCase11 = true; +}; template concept ConceptCase12 = T::value || true; template -concept ConceptCase13 = ConceptCase11; +concept ConceptCase13 = testNamespace::ConceptCase11; template concept ConceptTemplate = true; diff --git a/tests/parsing/unit/source_files/template_functions.hpp b/tests/parsing/unit/source_files/template_functions.hpp index d701c43..75d0bdb 100644 --- a/tests/parsing/unit/source_files/template_functions.hpp +++ b/tests/parsing/unit/source_files/template_functions.hpp @@ -13,10 +13,15 @@ const int* specialisation_function(float a, int* b, float& c, char d); template concept AlwaysTrue = true; +namespace test { + template + concept AlwaysFalse = false; +}; + template T template_concept_function(); -template requires AlwaysTrue< T> +template requires AlwaysTrue void requires_template_function1(T a) requires true or false; template @@ -26,3 +31,6 @@ int requires_template_function2(T a = 1) template requires (AlwaysTrue or true) and false void basic_concept_function(); + +template T> +void concept_namespace_function(); diff --git a/tests/parsing/unit/test_concept.py b/tests/parsing/unit/test_concept.py index 1940a39..9d351cf 100644 --- a/tests/parsing/unit/test_concept.py +++ b/tests/parsing/unit/test_concept.py @@ -14,7 +14,7 @@ class TestConcept(unittest.TestCase): def setUp(self): index = clang.cindex.Index.create() self.cursor = index.parse( - os.path.dirname(__file__) + r"/source_files/concept.hpp", + os.path.dirname(__file__) + r"/source_files/concepts.hpp", args=("-std=c++20",) ).cursor @@ -26,6 +26,7 @@ def test_concept_case_1(self): self.assertEqual(result.body.replace("\r\n", "\n"), "requires(T a) {\n { --a };\n { a-- };\n}") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_2(self): node = find_by_name(self.cursor, "ConceptCase2") @@ -38,6 +39,7 @@ def test_concept_case_2(self): ) self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_3(self): node = find_by_name(self.cursor, "ConceptCase3") @@ -50,6 +52,7 @@ def test_concept_case_3(self): ) self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_4(self): node = find_by_name(self.cursor, "ConceptCase4") @@ -59,6 +62,7 @@ def test_concept_case_4(self): self.assertEqual(result.body.replace("\r\n", "\n"), "requires {\n T{};\n}") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_5(self): node = find_by_name(self.cursor, "ConceptCase5") @@ -71,6 +75,7 @@ def test_concept_case_5(self): ) self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_6(self): node = find_by_name(self.cursor, "ConceptCase6") @@ -83,6 +88,7 @@ def test_concept_case_6(self): ) self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_7(self): node = find_by_name(self.cursor, "ConceptCase7") @@ -92,6 +98,7 @@ def test_concept_case_7(self): self.assertEqual(result.body, "(T{} > 0)") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_8(self): node = find_by_name(self.cursor, "ConceptCase8") @@ -103,6 +110,7 @@ def test_concept_case_8(self): self.assertEqual(result.is_requirement, False) self.assertEqual(result.template.parameters[0].specifier.name, "ConceptCase7") self.assertEqual(result.template.parameters[0].specifier.is_requirement, True) + self.assertEqual(len(result.parameters), 0) def test_concept_case_9(self): node = find_by_name(self.cursor, "ConceptCase9") @@ -112,6 +120,7 @@ def test_concept_case_9(self): self.assertEqual(result.body, "ConceptCase1 && ConceptCase2") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_10(self): node = find_by_name(self.cursor, "ConceptCase10") @@ -121,6 +130,7 @@ def test_concept_case_10(self): self.assertEqual(result.body, "ConceptCase1 || ConceptCase2") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_11(self): node = find_by_name(self.cursor, "ConceptCase11") @@ -130,6 +140,7 @@ def test_concept_case_11(self): self.assertEqual(result.body, "true") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_12(self): node = find_by_name(self.cursor, "ConceptCase12") @@ -139,20 +150,23 @@ def test_concept_case_12(self): self.assertEqual(result.body, "T::value || true") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_case_13(self): node = find_by_name(self.cursor, "ConceptCase13") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) self.assertEqual(result.name, "ConceptCase13") - self.assertEqual(result.body, "ConceptCase11") + self.assertEqual(result.body, "testNamespace::ConceptCase11") self.assertEqual(len(result.template.parameters), 1) self.assertEqual(result.is_requirement, False) + self.assertEqual(len(result.parameters), 0) def test_concept_template(self): node = find_by_name(self.cursor, "ConceptTemplate") result = ConceptInfo.from_cursor(node) self.assertIsNone(result.parent) + self.assertEqual(len(result.parameters), 0) self.assertEqual(result.name, "ConceptTemplate") self.assertEqual(result.body, "true") self.assertEqual(result.template.parent, None) diff --git a/tests/parsing/unit/test_function.py b/tests/parsing/unit/test_function.py index 72145c3..7244be2 100644 --- a/tests/parsing/unit/test_function.py +++ b/tests/parsing/unit/test_function.py @@ -478,6 +478,24 @@ def test_basic_concept_function(self): self.assertEqual(result.template.requires[1].body, "true") self.assertEqual(result.template.requires[2:7], ["or", "true", ")", "and", "false"]) + def test_namespace_concept_function(self): + node = find_by_name(self.cursor, "concept_namespace_function") + result = FunctionInfo.from_cursor(node) + stub_lexicon(result) + self.assertEqual(result.name, "concept_namespace_function") + self.assertEqual(result.body, None) + self.assertEqual(result.requires, None) + self.assertEqual(len(result.arguments), 0) + self.assertEqual(result.return_type.details, BasicType.VOID) + self.assertEqual(result.template.requires, None) + + param = result.template.parameters[0] + self.assertEqual(param.name, "T") + self.assertEqual(param.specifier.name, "AlwaysFalse") + self.assertEqual(param.specifier.is_requirement, True) + self.assertEqual(len(param.specifier.parameters), 1) + self.assertEqual(param.specifier.namespace, "test") + class TestFunctionsOverload(unittest.TestCase): def setUp(self):