From ae3cc8f98e081b5b639cca3840f29bf2c54a98fc Mon Sep 17 00:00:00 2001 From: xXenvy <111158232+xXenvy@users.noreply.github.com> Date: Sun, 6 Oct 2024 20:18:04 +0200 Subject: [PATCH] new: Better object constructors Adding a constructor to each syntactic element that allows you to set all of its parameters in one function call instead of using setter. Quality of life change. --- src/devana/syntax_abstraction/classinfo.py | 151 ++++++++- src/devana/syntax_abstraction/enuminfo.py | 35 +- src/devana/syntax_abstraction/externc.py | 17 +- src/devana/syntax_abstraction/functioninfo.py | 36 +- src/devana/syntax_abstraction/functiontype.py | 17 +- .../syntax_abstraction/namespaceinfo.py | 21 +- .../organizers/codecontainer.py | 11 + .../organizers/sourcefile.py | 36 +- src/devana/syntax_abstraction/templateinfo.py | 28 +- src/devana/syntax_abstraction/typedefinfo.py | 13 + .../syntax_abstraction/typeexpression.py | 15 + src/devana/syntax_abstraction/unioninfo.py | 18 +- src/devana/syntax_abstraction/using.py | 17 +- .../syntax_abstraction/usingnamespace.py | 15 +- src/devana/syntax_abstraction/variable.py | 28 +- src/devana/utility/__init__.py | 1 + src/devana/utility/init_params.py | 62 ++++ src/devana/utility/traits.py | 10 +- .../unit/test_instance_creations.py | 317 ++++++++++++++++++ 19 files changed, 818 insertions(+), 30 deletions(-) create mode 100644 src/devana/utility/init_params.py create mode 100644 tests/code_generation/unit/test_instance_creations.py diff --git a/src/devana/syntax_abstraction/classinfo.py b/src/devana/syntax_abstraction/classinfo.py index 8bff241..965d54f 100644 --- a/src/devana/syntax_abstraction/classinfo.py +++ b/src/devana/syntax_abstraction/classinfo.py @@ -1,22 +1,24 @@ from enum import Enum, auto import re -from typing import Optional, List, Tuple, cast, Any +from typing import Optional, List, Tuple, cast, Any, Union from clang import cindex -from devana.syntax_abstraction.functioninfo import FunctionInfo +from devana.syntax_abstraction.functioninfo import FunctionInfo, FunctionModification from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.variable import Variable from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.templateinfo import TemplateInfo -from devana.syntax_abstraction.typeexpression import TypeExpression +from devana.syntax_abstraction.typeexpression import TypeExpression, BasicType from devana.syntax_abstraction.comment import Comment -from devana.syntax_abstraction.attribute import DescriptiveByAttributes +from devana.syntax_abstraction.attribute import DescriptiveByAttributes, AttributeDeclaration from devana.syntax_abstraction._external_source import create_external from devana.utility.lazy import LazyNotInit, lazy_invoke -from devana.utility.traits import IBasicCreatable, ICursorValidate, IFromCursorCreatable +from devana.utility.init_params import init_params +from devana.utility.traits import IBasicCreatable, ICursorValidate, IFromCursorCreatable, IFromParamsCreatable from devana.configuration import Configuration, ParsingErrorPolicy from devana.utility.errors import ParserError from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.code_generation.stubtype import StubType class AccessSpecifier(Enum): @@ -56,6 +58,14 @@ def from_cursor(cls, cursor: cindex.Cursor, _: Optional = None) -> Optional["Cla result = cls(cursor) return result + @classmethod + @init_params() + def from_params( # pylint: disable=unused-argument + cls, + access_specifier: Optional[AccessSpecifier] = None + ) -> "ClassMember": + return cls(None) + @property def access_specifier(self) -> AccessSpecifier: """Access scope of member.""" @@ -182,6 +192,26 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code else: self._type = LazyNotInit + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument, arguments-renamed + cls, + parent: Optional[ISyntaxElement] = None, + arguments: Optional[List[FunctionInfo.Argument]] = None, + name: Optional[str] = None, + return_type: Union[TypeExpression, BasicType, StubType, None] = None, + modification: Optional[FunctionModification.ModificationKind] = None, + body: Optional[str] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None, + template: Optional[TemplateInfo] = None, + associated_comment: Optional[Comment] = None, + prefix: Optional[str] = None, + access_specifier: Optional[AccessSpecifier] = None, + type: Optional[MethodType] = None, + ) -> "MethodInfo": + return cls(None, parent) + @property @lazy_invoke def type(self) -> MethodType: @@ -261,6 +291,26 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._initializer_list = LazyNotInit self._name = LazyNotInit + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument, arguments-renamed + cls, + parent: Optional[ISyntaxElement] = None, + arguments: Optional[List[FunctionInfo.Argument]] = None, + name: Optional[str] = None, + modification: Optional[FunctionModification.ModificationKind] = None, + body: Optional[str] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None, + template: Optional[TemplateInfo] = None, + associated_comment: Optional[Comment] = None, + prefix: Optional[str] = None, + access_specifier: Optional[AccessSpecifier] = None, + type: Optional[MethodType] = None, + initializer_list: Optional[List[InitializerInfo]] = None, + ) -> "ConstructorInfo": + return cls(None, parent) + @property @lazy_invoke def initializer_list(self) -> List[InitializerInfo]: @@ -352,6 +402,24 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code else: self._name = LazyNotInit + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument, arguments-differ + cls, + parent: Optional[ISyntaxElement] = None, + arguments: Optional[List[FunctionInfo.Argument]] = None, + name: Optional[str] = None, + modification: Optional[FunctionModification.ModificationKind] = None, + body: Optional[str] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None, + template: Optional[TemplateInfo] = None, + associated_comment: Optional[Comment] = None, + prefix: Optional[str] = None, + access_specifier: Optional[AccessSpecifier] = None, + ) -> "DestructorInfo": + return cls(None, parent) + @property @lazy_invoke def name(self) -> str: @@ -408,6 +476,21 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return cls(cursor, parent) return None + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + type: Optional[TypeExpression] = None, + default_value: Optional[Any] = None, + lexicon: Optional[Lexicon] = None, + access_specifier: Optional[AccessSpecifier] = None, + attributes: Optional[List[AttributeDeclaration]] = None, + associated_comment: Optional[Comment] = None + ) -> "FieldInfo": + return cls(None, parent) + @property @lazy_invoke def associated_comment(self) -> Optional[Comment]: @@ -460,6 +543,17 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return None return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + type: Optional[AccessSpecifier] = None, + is_unnamed: Optional[bool] = None, + content: Optional[List[Any]] = None, + ) -> "SectionInfo": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.CXX_ACCESS_SPEC_DECL @@ -532,7 +626,7 @@ def __repr__(self): return f"{type(self).__name__}:{self.type} ({super().__repr__()})" -class InheritanceInfo(IFromCursorCreatable, ISyntaxElement): +class InheritanceInfo(IFromCursorCreatable, IFromParamsCreatable, ISyntaxElement): """Information about class/structure inheritance.""" class InheritanceValue(IBasicCreatable, ISyntaxElement): @@ -563,6 +657,19 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional ".InheritanceValue")]: return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + access_specifier: Optional[AccessSpecifier] = None, + type: Optional[Any] = None, + is_virtual: Optional[bool] = None, + template_arguments: Optional[List[TypeExpression]] = None, + namespaces: Optional[List[str]] = None, + ) -> "InheritanceInfo.InheritanceValue": + return cls(None, parent) + @property def parent(self) -> CodeContainer: """Class parent.""" @@ -667,6 +774,15 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional["InheritanceInfo"]: return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + type_parents: Optional[List[InheritanceValue]] = None, + ) -> "InheritanceInfo": + return cls(None, parent) + @property @lazy_invoke def type_parents(self) -> List[InheritanceValue]: @@ -776,6 +892,29 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional except ParserError: return None + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None, + attributes: Optional[List[AttributeDeclaration]] = None, + is_class: Optional[bool] = None, + is_struct: Optional[bool] = None, + is_final: Optional[bool] = None, + name: Optional[str] = None, + inheritance: Optional[InheritanceInfo] = None, + is_declaration: Optional[bool] = None, + is_definition: Optional[bool] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None, + template: Optional[TemplateInfo] = None, + associated_comment: Optional[Comment] = None, + prefix: Optional[str] = None + ) -> "ClassInfo": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: if cursor.kind == cindex.CursorKind.STRUCT_DECL: diff --git a/src/devana/syntax_abstraction/enuminfo.py b/src/devana/syntax_abstraction/enuminfo.py index 0679a54..e351713 100644 --- a/src/devana/syntax_abstraction/enuminfo.py +++ b/src/devana/syntax_abstraction/enuminfo.py @@ -6,10 +6,11 @@ from devana.syntax_abstraction.comment import Comment from devana.syntax_abstraction.typeexpression import BasicType from devana.syntax_abstraction.organizers.lexicon import Lexicon -from devana.syntax_abstraction.attribute import DescriptiveByAttributes +from devana.syntax_abstraction.attribute import DescriptiveByAttributes, AttributeDeclaration from devana.utility.errors import ParserError from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement @@ -50,6 +51,18 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return None return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + value: Optional[int] = None, + is_default: Optional[bool] = None, + associated_comment: Optional[Comment] = None, + ) -> "EnumInfo.EnumValue": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.ENUM_CONSTANT_DECL @@ -144,6 +157,26 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.ENUM_DECL + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None, + attributes: Optional[List[AttributeDeclaration]] = None, + name: Optional[str] = None, + values: Optional[List[EnumValue]] = None, + is_scoped: Optional[bool] = None, + prefix: Optional[Literal["class", "struct"]] = None, + numeric_type: Optional[BasicType] = None, + is_declaration: Optional[bool] = None, + is_definition: Optional[bool] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None, + ) -> "EnumInfo": + return cls(None, parent) + @property @lazy_invoke def name(self) -> str: diff --git a/src/devana/syntax_abstraction/externc.py b/src/devana/syntax_abstraction/externc.py index 7ee0f91..ed8babb 100644 --- a/src/devana/syntax_abstraction/externc.py +++ b/src/devana/syntax_abstraction/externc.py @@ -1,11 +1,12 @@ -from typing import Optional, List +from typing import Optional, List, Any from clang import cindex from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.functioninfo import FunctionInfo +from devana.syntax_abstraction.syntax import ISyntaxElement from devana.utility.errors import ParserError from devana.utility.lazy import lazy_invoke -from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.utility.init_params import init_params class ExternC(CodeContainer): @@ -25,6 +26,18 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._namespace = None self._lexicon = Lexicon.create(self) + @classmethod + @init_params(skip={"parent"}) + 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, + lexicon: Optional[Lexicon] = None + ): + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: if cursor.kind != cindex.CursorKind.LINKAGE_SPEC: diff --git a/src/devana/syntax_abstraction/functioninfo.py b/src/devana/syntax_abstraction/functioninfo.py index 431b0aa..2caae3e 100644 --- a/src/devana/syntax_abstraction/functioninfo.py +++ b/src/devana/syntax_abstraction/functioninfo.py @@ -1,4 +1,4 @@ -from typing import Optional, Tuple, List, Any +from typing import Optional, Tuple, List, Any, Union from enum import auto, IntFlag import re from clang import cindex @@ -14,7 +14,9 @@ from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.traits import IBasicCreatable, ICursorValidate from devana.utility.errors import ParserError, CodeError +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.code_generation.stubtype import StubType class FunctionModification(metaclass=FakeEnum): @@ -275,6 +277,19 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.PARM_DECL + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + type: Optional[TypeExpression] = None, + default_value: Optional[str] = None, + lexicon: Optional[Lexicon] = None, + attributes: Optional[List[AttributeDeclaration]] = None + ) -> "FunctionInfo.Argument": + return cls(None, parent) + @property @lazy_invoke def attributes(self) -> List[AttributeDeclaration]: @@ -335,6 +350,25 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return cls(cursor, parent) return None + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + attributes: Optional[List[AttributeDeclaration]] = None, + arguments: Optional[List[Argument]] = None, + name: Optional[str] = None, + return_type: Union[TypeExpression, BasicType, StubType, None] = None, + modification: Optional[FunctionModification.ModificationKind] = None, + body: Optional[str] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None, + template: Optional[TemplateInfo] = None, + associated_comment: Optional[Comment] = None, + prefix: Optional[str] = None, + ) -> "FunctionInfo": + return cls(None, parent) + @property @lazy_invoke def arguments(self) -> List[Argument]: diff --git a/src/devana/syntax_abstraction/functiontype.py b/src/devana/syntax_abstraction/functiontype.py index 0b7485d..b75748d 100644 --- a/src/devana/syntax_abstraction/functiontype.py +++ b/src/devana/syntax_abstraction/functiontype.py @@ -1,13 +1,15 @@ -from typing import Optional, List +from typing import Optional, List, Union from clang import cindex -from devana.syntax_abstraction.typeexpression import TypeExpression +from devana.syntax_abstraction.typeexpression import TypeExpression, BasicType from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.codepiece import CodePiece from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.errors import ParserError from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.code_generation.stubtype import StubType class FunctionType(IBasicCreatable, ICursorValidate, ISyntaxElement): @@ -40,6 +42,17 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional def create_default(cls, parent: Optional = None) -> "FunctionType": return cls(None, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + arguments: Optional[List[TypeExpression]] = None, + return_type: Union[TypeExpression, BasicType, StubType, None] = None, + lexicon: Optional[Lexicon] = None, + ) -> "FunctionType": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind is cindex.TypeKind.FUNCTIONPROTO diff --git a/src/devana/syntax_abstraction/namespaceinfo.py b/src/devana/syntax_abstraction/namespaceinfo.py index 63bdde0..59c1124 100644 --- a/src/devana/syntax_abstraction/namespaceinfo.py +++ b/src/devana/syntax_abstraction/namespaceinfo.py @@ -1,14 +1,15 @@ -from typing import Optional, List +from typing import Optional, List, Any from clang import cindex from devana.syntax_abstraction.organizers.codecontainer import CodeContainer from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.functioninfo import FunctionInfo from devana.syntax_abstraction.comment import Comment -from devana.syntax_abstraction.attribute import DescriptiveByAttributes +from devana.syntax_abstraction.attribute import DescriptiveByAttributes, AttributeDeclaration +from devana.syntax_abstraction.syntax import ISyntaxElement from devana.utility.errors import ParserError from devana.utility.lazy import LazyNotInit, lazy_invoke -from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.utility.init_params import init_params class NamespaceInfo(CodeContainer, DescriptiveByAttributes): @@ -29,6 +30,20 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._associated_comment = LazyNotInit self._lexicon = Lexicon.create(self) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None, + attributes: Optional[List[AttributeDeclaration]] = None, + name: Optional[str] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None + ) -> "NamespaceInfo": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.NAMESPACE diff --git a/src/devana/syntax_abstraction/organizers/codecontainer.py b/src/devana/syntax_abstraction/organizers/codecontainer.py index 8c05a26..c0d9257 100644 --- a/src/devana/syntax_abstraction/organizers/codecontainer.py +++ b/src/devana/syntax_abstraction/organizers/codecontainer.py @@ -4,6 +4,7 @@ from devana.syntax_abstraction.codepiece import CodePiece from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.configuration import Configuration, ParsingErrorPolicy from devana.utility.errors import ParserError from devana.syntax_abstraction.syntax import ISyntaxElement @@ -34,6 +35,16 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional def create_default(cls, parent: Optional = None) -> Any: return cls(None, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + namespace: Optional[str] = None + ) -> "CodeContainer": + return cls(None, parent) + @property @lazy_invoke def content(self) -> List[Any]: diff --git a/src/devana/syntax_abstraction/organizers/sourcefile.py b/src/devana/syntax_abstraction/organizers/sourcefile.py index 7bacbff..773be0a 100644 --- a/src/devana/syntax_abstraction/organizers/sourcefile.py +++ b/src/devana/syntax_abstraction/organizers/sourcefile.py @@ -7,13 +7,15 @@ from devana.syntax_abstraction.comment import CommentMarker, Comment, CommentsFactory from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.codepiece import CodePiece -from devana.utility.lazy import LazyNotInit, lazy_invoke +from devana.syntax_abstraction.syntax import ISyntaxElement from devana.configuration import Configuration, ParsingErrorPolicy +from devana.utility.lazy import LazyNotInit, lazy_invoke +from devana.utility.init_params import init_params +from devana.utility.traits import IFromParamsCreatable from devana.utility.errors import ParserError -from devana.syntax_abstraction.syntax import ISyntaxElement -class IncludeInfo(ISyntaxElement): +class IncludeInfo(ISyntaxElement, IFromParamsCreatable): """Include present in file.""" def __init__(self, cursor: Optional[cindex.FileInclusion] = None, parent: Optional[Any] = None): @@ -43,6 +45,18 @@ def __init__(self, cursor: Optional[cindex.FileInclusion] = None, parent: Option if "<" in self._text: self._is_standard = True + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + value: Optional[str] = None, + is_standard: Optional[bool] = None, + path: Optional[str] = None, + source_file: Optional["SourceFile"] = None, + ) -> "IncludeInfo": + return cls(None, parent) + @property def value(self) -> str: """Value of include.""" @@ -197,6 +211,22 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None, return None return cls(cursor, parent, configuration) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument, arguments-renamed + cls, + parent: Optional[ISyntaxElement] = None, + content: Optional[List[Any]] = None, + type: Optional[SourceFileType] = None, + path: Optional[Path] = None, + lexicon: Optional[Lexicon] = None, + includes: Optional[List[IncludeInfo]] = None, + preamble: Optional[Comment] = None, + header_guard: Optional[str] = None, + configuration: Optional[Configuration] = None + ) -> "SourceFile": + return cls(None, parent) + @classmethod def from_path(cls, source: str, parent: Optional[Any] = None, configuration: Optional[Configuration] = None): return cls(source, parent, configuration) diff --git a/src/devana/syntax_abstraction/templateinfo.py b/src/devana/syntax_abstraction/templateinfo.py index 451a5b8..5531de9 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 +from typing import Optional, List, Union, Tuple, Any from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression, TypeModification @@ -9,6 +9,7 @@ from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.errors import ParserError from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement from devana.configuration import Configuration @@ -80,6 +81,18 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No def create_default(cls, parent: Optional = None) -> "TemplateInfo.TemplateParameter": return cls(None, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + specifier: Optional[str] = None, + name: Optional[str] = None, + default_value: Optional[str] = None, + is_variadic: Optional[bool] = None, + ) -> "TemplateInfo.TemplateParameter": + return cls(None, parent) + @classmethod def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional[("TemplateInfo" ".TemplateParameter")]: @@ -182,6 +195,19 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional = No def create_default(cls, parent: Optional = None) -> "TemplateInfo": return cls(None, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + specialisation_values: Optional[List[Union[TypeExpression, str]]] = None, + specialisations: Optional[Tuple[Any, ...]] = None, + parameters: Optional[List[TemplateParameter]] = None, + is_empty: Optional[bool] = None, + lexicon: Optional[Lexicon] = None, + ) -> "TemplateInfo": + return cls(None, parent) + @classmethod def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional["TemplateInfo"]: if not cls.is_cursor_valid(cursor): diff --git a/src/devana/syntax_abstraction/typedefinfo.py b/src/devana/syntax_abstraction/typedefinfo.py index 58ca2b2..88170d1 100644 --- a/src/devana/syntax_abstraction/typedefinfo.py +++ b/src/devana/syntax_abstraction/typedefinfo.py @@ -8,6 +8,7 @@ from devana.utility.errors import ParserError from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement @@ -37,6 +38,18 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return None return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + type_info: Union[TypeExpression, ISyntaxElement, None] = None, + name: Optional[str] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None, + ) -> "TypedefInfo": + return cls(None, parent) + @classmethod def create_default(cls, parent: Optional = None) -> "TypedefInfo": return cls(None, parent) diff --git a/src/devana/syntax_abstraction/typeexpression.py b/src/devana/syntax_abstraction/typeexpression.py index 1922f28..33c2378 100644 --- a/src/devana/syntax_abstraction/typeexpression.py +++ b/src/devana/syntax_abstraction/typeexpression.py @@ -9,7 +9,9 @@ from devana.utility.fakeenum import FakeEnum from devana.utility.traits import IBasicCreatable from devana.utility.errors import ParserError +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.code_generation.stubtype import StubType class BasicType(Enum): @@ -429,6 +431,19 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional result = cls(cursor, parent) return result + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + modification: Optional[TypeModification.ModificationKind] = None, + namespaces: Optional[List[str]] = None, + template_arguments: Optional[List["TypeExpression"]] = None, + details: Union[ISyntaxElement, BasicType, StubType, None] = None, + lexicon: Optional[Lexicon] = None, + ) -> "TypeExpression": + return cls(None, parent) + @property @lazy_invoke def name(self) -> str: diff --git a/src/devana/syntax_abstraction/unioninfo.py b/src/devana/syntax_abstraction/unioninfo.py index db8e6df..3c32e46 100644 --- a/src/devana/syntax_abstraction/unioninfo.py +++ b/src/devana/syntax_abstraction/unioninfo.py @@ -4,9 +4,10 @@ from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.syntax_abstraction.classinfo import FieldInfo from devana.syntax_abstraction.comment import Comment +from devana.syntax_abstraction.syntax import ISyntaxElement from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.errors import ParserError -from devana.syntax_abstraction.syntax import ISyntaxElement +from devana.utility.init_params import init_params class UnionInfo(CodeContainer): @@ -26,6 +27,21 @@ def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[Code self._associated_comment = LazyNotInit self._lexicon = Lexicon.create(self) + @classmethod + @init_params(skip={"parent"}) + 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, + is_declaration: Optional[bool] = None, + is_definition: Optional[bool] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None, + ) -> "UnionInfo": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.UNION_DECL diff --git a/src/devana/syntax_abstraction/using.py b/src/devana/syntax_abstraction/using.py index 109b9fb..202ed34 100644 --- a/src/devana/syntax_abstraction/using.py +++ b/src/devana/syntax_abstraction/using.py @@ -6,12 +6,13 @@ from devana.syntax_abstraction.comment import Comment from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.utility.errors import ParserError +from devana.utility.init_params import init_params from devana.utility.lazy import LazyNotInit, lazy_invoke -from devana.utility.traits import IFromCursorCreatable, ICursorValidate +from devana.utility.traits import IFromCursorCreatable, ICursorValidate, IFromParamsCreatable from devana.syntax_abstraction.syntax import ISyntaxElement -class Using(IFromCursorCreatable, ICursorValidate, ISyntaxElement): +class Using(IFromCursorCreatable, ICursorValidate, IFromParamsCreatable, ISyntaxElement): """Class represented typedef declaration e.g., using AliasTypeName = const namespace::namespace::Type. Using without "=" as using namespace::Type; is not supported.""" @@ -38,6 +39,18 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional return None return cls(cursor, parent) + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + type_info: Union[TypeExpression, ISyntaxElement, None] = None, + name: Optional[str] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None, + ) -> "Using": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.TYPE_ALIAS_DECL diff --git a/src/devana/syntax_abstraction/usingnamespace.py b/src/devana/syntax_abstraction/usingnamespace.py index 4f71f38..f67e8b1 100644 --- a/src/devana/syntax_abstraction/usingnamespace.py +++ b/src/devana/syntax_abstraction/usingnamespace.py @@ -5,11 +5,12 @@ from devana.syntax_abstraction.organizers.lexicon import Lexicon from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.errors import ParserError -from devana.utility.traits import IFromCursorCreatable, ICursorValidate +from devana.utility.traits import IFromCursorCreatable, ICursorValidate, IFromParamsCreatable +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement -class UsingNamespace(IFromCursorCreatable, ICursorValidate, ISyntaxElement): +class UsingNamespace(IFromCursorCreatable, ICursorValidate, IFromParamsCreatable, ISyntaxElement): """Using namespace in scope.""" def __init__(self, cursor: Optional[cindex.Cursor] = None, parent: Optional[CodeContainer] = None): @@ -39,6 +40,16 @@ def from_namespace(cls, namespace: str, parent: Optional = None) -> "UsingNamesp result._namespace = namespace return result + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + namespaces: Optional[List[str]] = None, + lexicon: Optional[Lexicon] = None + ) -> "UsingNamespace": + return cls(None, parent) + @staticmethod def is_cursor_valid(cursor: cindex.Cursor) -> bool: return cursor.kind == cindex.CursorKind.USING_DIRECTIVE diff --git a/src/devana/syntax_abstraction/variable.py b/src/devana/syntax_abstraction/variable.py index f69d21f..cae8c44 100644 --- a/src/devana/syntax_abstraction/variable.py +++ b/src/devana/syntax_abstraction/variable.py @@ -1,5 +1,5 @@ import re -from typing import Optional +from typing import Optional, Any from clang import cindex from devana.syntax_abstraction.codepiece import CodePiece from devana.syntax_abstraction.typeexpression import TypeExpression @@ -8,6 +8,7 @@ from devana.utility.lazy import LazyNotInit, lazy_invoke from devana.utility.errors import ParserError from devana.utility.traits import IBasicCreatable, ICursorValidate +from devana.utility.init_params import init_params from devana.syntax_abstraction.syntax import ISyntaxElement @@ -39,6 +40,18 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional result = cls(cursor, parent) return result + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + type: Optional[TypeExpression] = None, + default_value: Optional[Any] = None, + lexicon: Optional[Lexicon] = None + ) -> "Variable": + return cls(None, parent) + @property @lazy_invoke def name(self) -> str: @@ -124,6 +137,19 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional result = cls(cursor, parent) return result + @classmethod + @init_params(skip={"parent"}) + def from_params( # pylint: disable=unused-argument + cls, + parent: Optional[ISyntaxElement] = None, + name: Optional[str] = None, + type: Optional[TypeExpression] = None, + default_value: Optional[Any] = None, + lexicon: Optional[Lexicon] = None, + associated_comment: Optional[Comment] = None + ) -> "GlobalVariable": + return cls(None, parent) + @property @lazy_invoke def associated_comment(self) -> Optional[Comment]: diff --git a/src/devana/utility/__init__.py b/src/devana/utility/__init__.py index 7317aa0..2c5fe35 100644 --- a/src/devana/utility/__init__.py +++ b/src/devana/utility/__init__.py @@ -5,4 +5,5 @@ from .lazy import LazyNotInit, lazy_invoke from .errors import CodeError, ParserError from .fakeenum import FakeEnum +from .init_params import init_params from .typeregister import register diff --git a/src/devana/utility/init_params.py b/src/devana/utility/init_params.py new file mode 100644 index 0000000..ad3b51c --- /dev/null +++ b/src/devana/utility/init_params.py @@ -0,0 +1,62 @@ +from typing import Callable, Any, Optional, Set +from inspect import signature, BoundArguments +from functools import wraps + + +def init_params(skip: Optional[Set[str]] = None) -> Callable: + """A decorator that automatically assigns classmethod parameters to instance attributes, + if the attribute has a setter or exists as an instance variable. + + Parameters in the `skip` set will be ignored, "cls" is ignored by default. + If parameter is not settable or doesn't exist in the instance, an AttributeError will be raised. + + Example usage:: + + class Person: + name: str = "" + age: int = 0 + + @classmethod + @init_params() + def create(cls, name: str, age: int): + return cls() + + jerry = Person.create("Jerry", 20) + print(jerry.name, jerry.age) # Outputs: Jerry 20 + """ + def decorator(_classmethod: Callable) -> Callable: + def has_setter(instance: object, name: str) -> bool: + """Checks if the class attribute is a property with a defined setter.""" + try: + maybe_property = getattr(instance.__class__, name) + except AttributeError: + return False + return isinstance(maybe_property, property) and maybe_property.fset is not None + + def has_attr(instance: object, name: str) -> bool: + """Checks if the attribute is directly in the instance or if it's a class attribute, + excluding properties.""" + if not isinstance(getattr(instance.__class__, name, property()), property): + return True + return name in instance.__dict__ + + @wraps(_classmethod) + def wrapper(*args: Any, **kwargs: Any) -> object: + instance: object = _classmethod(*args, **kwargs) + bound_args: BoundArguments = signature(_classmethod).bind(*args, **kwargs) + bound_args.apply_defaults() + ignore: Set[str] = skip or set() + ignore.add("cls") + + for name, value in bound_args.arguments.items(): + if name in ignore: + continue + if not any((has_setter(instance, name), has_attr(instance, name))): + raise AttributeError( + f"'{name}' is not a settable attribute on instance {instance}." + ) + if value is not None: + setattr(instance, name, value) + return instance + return wrapper + return decorator diff --git a/src/devana/utility/traits.py b/src/devana/utility/traits.py index 957937c..6fe6f58 100644 --- a/src/devana/utility/traits.py +++ b/src/devana/utility/traits.py @@ -33,15 +33,15 @@ def from_cursor(cls, cursor: cindex.Cursor, parent: Optional = None) -> Optional Cls parameter allows implementing this method once if init meets requirements in all derivative types.""" -class IBasicCreatable(IDefaultCreatable, IFromCursorCreatable, ABC): - """An interface that describes a set of constructors for a code element.""" - - class IFromParamsCreatable(ABC): """The interface of an object that can be created from many parameters.""" @classmethod @abstractmethod - def from_params(cls, *argv) -> ISyntaxElement: + def from_params(cls) -> ISyntaxElement: """Multi-parameter constructor. Similar to the default value, except that it allows you to set some basic properties.""" + + +class IBasicCreatable(IDefaultCreatable, IFromCursorCreatable, IFromParamsCreatable, ABC): + """An interface that describes a set of constructors for a code element.""" diff --git a/tests/code_generation/unit/test_instance_creations.py b/tests/code_generation/unit/test_instance_creations.py new file mode 100644 index 0000000..1139145 --- /dev/null +++ b/tests/code_generation/unit/test_instance_creations.py @@ -0,0 +1,317 @@ +from devana.syntax_abstraction.organizers.sourcefile import SourceFile, IncludeInfo, SourceFileType +from devana.syntax_abstraction.typeexpression import TypeModification +from devana.syntax_abstraction.usingnamespace import UsingNamespace +from devana.syntax_abstraction.namespaceinfo import NamespaceInfo +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.unioninfo import UnionInfo +from devana.syntax_abstraction.enuminfo import EnumInfo +from devana.syntax_abstraction.externc import ExternC +from devana.syntax_abstraction.using import Using +from devana.syntax_abstraction.classinfo import * + +from devana.utility.init_params import init_params +import unittest + +class TestInstanceCreations(unittest.TestCase): + + def test_variable_creation(self): + variable = Variable.from_params( + name="testVar", + default_value=10, + type=TypeExpression.from_params( + details=BasicType.INT, + modification=TypeModification.CONST + ) + ) + self.assertEqual(variable.type.details, BasicType.INT) + self.assertEqual(variable.type.modification, TypeModification.CONST) + self.assertEqual(variable.default_value, 10) + self.assertEqual(variable.name, "testVar") + + def test_global_variable_creation(self): + global_variable = GlobalVariable.from_params( + name="testGlobalVar", + default_value=5.1, + type=TypeExpression.from_params( + details=BasicType.FLOAT, + modification=TypeModification.CONSTEXPR + ) + ) + self.assertEqual(global_variable.type.details, BasicType.FLOAT) + self.assertEqual(global_variable.type.modification, TypeModification.CONSTEXPR) + self.assertEqual(global_variable.default_value, 5.1) + self.assertEqual(global_variable.name, "testGlobalVar") + + def test_usingnamespace_creation(self): + usingnamespace = UsingNamespace.from_params(namespaces=["foo", "bar"]) + self.assertEqual(usingnamespace.namespaces, ["foo", "bar"]) + + def test_using_creation(self): + using = Using.from_params( + name="str", + type_info=TypeExpression.from_params( + details=StubType("std::string") + ) + ) + self.assertEqual(using.name, "str") + self.assertEqual(using.type_info.details.name, "std::string") + + def test_union_creation(self): + union = UnionInfo.from_params( + name="testUnion", + is_declaration=True, + ) + self.assertEqual(union.name, "testUnion") + self.assertEqual(union.content, []) + self.assertEqual(union.is_declaration, True) + self.assertFalse(union.is_definition, False) + + def test_type_expression_creation(self): + type_expr = TypeExpression.from_params( + modification=TypeModification.CONST | TypeModification.REFERENCE, + namespaces=["foo"], + details=BasicType.CHAR, + ) + self.assertEqual(type_expr.name, "const char&") + self.assertEqual(type_expr.namespaces, ["foo"]) + + def test_typedef_creation(self): + using = TypedefInfo.from_params( + name="str", + type_info=TypeExpression.from_params( + details=StubType("std::string") + ) + ) + self.assertEqual(using.name, "str") + self.assertEqual(using.type_info.details.name, "std::string") + + def test_template_creation(self): + template = TemplateInfo.from_params( + parameters=[ + TemplateInfo.TemplateParameter.from_params( + name="A" + ), + TemplateInfo.TemplateParameter.from_params( + name="B" + ) + ], + is_empty=False + ) + self.assertEqual(len(template.parameters), 2) + self.assertEqual(template.is_empty, False) + + def test_namespace_creation(self): + namespace = NamespaceInfo.from_params( + namespace="cat", + content=[ + FunctionInfo.from_params( + name="isSleeping", + return_type=BasicType.BOOL, + body="return true;" + ) + ] + ) + self.assertEqual(namespace.namespace, "cat") + self.assertTrue(isinstance(namespace.content[0], FunctionInfo)) + + def test_function_type_creation(self): + function_type = FunctionType.from_params( + return_type=BasicType.VOID, + arguments=[] + ) + self.assertEqual(function_type.return_type, BasicType.VOID) + self.assertEqual(function_type.arguments, []) + + def test_function_info_creation(self): + function_info = FunctionInfo.from_params( + name="sayHello", + return_type=BasicType.VOID, + modification=TypeModification.INLINE, + body='std::cout << "Hello, " << name << std::endl;', + arguments=[ + FunctionInfo.Argument.from_params( + name="name", + type=TypeExpression.from_params( + details=StubType("std::string"), + modification=TypeModification.CONST | TypeModification.REFERENCE + ), + ) + ] + ) + self.assertEqual(function_info.name, "sayHello") + self.assertEqual(function_info.return_type, BasicType.VOID) + self.assertEqual(len(function_info.arguments), 1) + self.assertEqual(function_info.body, 'std::cout << "Hello, " << name << std::endl;') + self.assertEqual(function_info.modification, TypeModification.INLINE) + + def test_externc_creation(self): + extern_c = ExternC.from_params( + content=[ + FunctionInfo.from_params( + name="foo", + return_type=BasicType.VOID, + ) + ] + ) + self.assertEqual(len(extern_c.content), 1) + self.assertEqual(extern_c.allowed_namespaces, []) + + def test_enum_creation(self): + my_enum = EnumInfo.from_params( + name="betterLanguagesThanCpp", + values=[ + EnumInfo.EnumValue.from_params( + name="Zig", + value=0, + is_default=True + ), + EnumInfo.EnumValue.from_params( + name="Rust", + value=1 + ) + ] + ) + self.assertEqual(my_enum.name, "betterLanguagesThanCpp") + self.assertEqual(my_enum.is_definition, True) + self.assertEqual(my_enum.is_scoped, False) + self.assertEqual(len(my_enum.values), 2) + self.assertEqual(my_enum.is_declaration, False) + + def test_class_member_creation(self): + class_member = ClassMember.from_params( + access_specifier=AccessSpecifier.PROTECTED + ) + self.assertEqual(class_member.access_specifier, AccessSpecifier.PROTECTED) + + def test_method_creation(self): + method = MethodInfo.from_params( + name="getName", + return_type=StubType("std::string"), + body="return name;" + ) + self.assertEqual(method.name, "getName") + self.assertEqual(method.return_type.name, "std::string") + self.assertEqual(method.body, "return name;") + + def test_constructor_creation(self): + constructor = ConstructorInfo.from_params( + name="testConstructor", + initializer_list=[ + ConstructorInfo.InitializerInfo("a", 10) + ] + ) + self.assertEqual(constructor.name, "testConstructor") + self.assertEqual(len(constructor.initializer_list), 1) + self.assertIsNone(constructor.return_type) + + def test_destructor_creation(self): + destructor = DestructorInfo.from_params( + name="testDestructor" + ) + self.assertEqual(destructor.name, "testDestructor") + self.assertEqual(destructor.type, MethodType.DESTRUCTOR) + self.assertIsNone(destructor.return_type) + + def test_field_creation(self): + field = FieldInfo.from_params( + name="testField", + associated_comment=Comment() + ) + self.assertEqual(field.name, "testField") + self.assertIsNotNone(field.associated_comment) + + def test_section_creation(self): + parent = ClassInfo.from_params(name="testClass", is_class=True) + section = SectionInfo.from_params( + parent=parent, + type=AccessSpecifier.PUBLIC, + ) + self.assertEqual(section.type, AccessSpecifier.PUBLIC) + self.assertEqual(section.is_unnamed, True) + + def test_inheritance_creation(self): + inheritance = InheritanceInfo.from_params( + type_parents=[ + InheritanceInfo.InheritanceValue.from_params( + access_specifier=AccessSpecifier.PUBLIC, + is_virtual=False + ) + ] + ) + self.assertIsNone(inheritance.parent) + self.assertEqual(len(inheritance.type_parents), 1) + + def test_class_creation(self): + parent = ClassInfo.from_params( + name="testClass", + is_class=True, + is_definition=True + ) + self.assertEqual(parent.name, "testClass") + self.assertEqual(parent.is_definition, True) + self.assertEqual(parent.is_class, True) + + def test_source_file_creation(self): + source_file = SourceFile.from_params( + type=SourceFileType.HEADER, + header_guard="testHeaderGuard", + ) + self.assertEqual(source_file.type, SourceFileType.HEADER) + self.assertEqual(source_file.header_guard, "testHeaderGuard") + + def test_include_creation(self): + include_info = IncludeInfo.from_params( + value="string", + is_standard=True + ) + self.assertEqual(include_info.value, "string") + self.assertEqual(include_info.is_standard, True) + + def test_init_params(self): + class A: + @classmethod + @init_params() + def create(cls, value: int): + return cls() + + class B(A): + value: int = 0 + + class C(B): + @classmethod + @init_params() + def create(cls, value: int, name: Optional[str] = None): + return cls() + + class D(C): + def __init__(self): + self._name = "name" + + @property + def name(self) -> str: + return self._name + + @name.setter + def name(self, value: str): + self._name = f"D_{value}" + + class E(D): + @property + def name(self) -> str: + return "Always default" + + with self.subTest("Cases that should raise AttributeError"): + self.assertRaises(AttributeError, A.create, value=10) + self.assertRaises(AttributeError, C.create, value=5) + self.assertRaises(AttributeError, E.create, value=10, name="test") + + with self.subTest("Cases that should succeed"): + my_b = B.create(value=10) + self.assertEqual(my_b.value, 10) + + my_d = D.create(value=5, name="test") + self.assertEqual(my_d.name, "D_test") + my_d.name = "test2" + self.assertEqual(my_d.name, "D_test2") \ No newline at end of file