summaryrefslogtreecommitdiffstats
path: root/generator/plugins/rust/rust_structs.py
diff options
context:
space:
mode:
Diffstat (limited to 'generator/plugins/rust/rust_structs.py')
-rw-r--r--generator/plugins/rust/rust_structs.py460
1 files changed, 460 insertions, 0 deletions
diff --git a/generator/plugins/rust/rust_structs.py b/generator/plugins/rust/rust_structs.py
new file mode 100644
index 0000000..571cd62
--- /dev/null
+++ b/generator/plugins/rust/rust_structs.py
@@ -0,0 +1,460 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from typing import Dict, Iterable, List, Optional
+
+import generator.model as model
+
+from .rust_commons import (
+ TypeData,
+ fix_lsp_method_name,
+ generate_extras,
+ generate_literal_struct_name,
+ generate_property,
+ get_extended_properties,
+ get_message_type_name,
+ get_type_name,
+ struct_wrapper,
+ type_alias_wrapper,
+)
+from .rust_lang_utils import get_parts, lines_to_doc_comments, to_upper_camel_case
+
+
+def generate_type_aliases(spec: model.LSPModel, types: TypeData) -> None:
+ for alias in spec.typeAliases:
+ if not types.has_id(alias):
+ generate_type_alias(alias, types, spec)
+
+
+def _get_doc(doc: Optional[str]) -> str:
+ if doc:
+ return lines_to_doc_comments(doc.splitlines(keepends=False))
+ return []
+
+
+def _is_some_array_type(items: Iterable[model.LSP_TYPE_SPEC]) -> bool:
+ items_list = list(items)
+ assert len(items_list) == 2
+ item1, item2 = items_list
+
+ if item1.kind == "array" and item2.kind == "reference":
+ return item1.element.kind == "reference" and item1.element.name == item2.name
+
+ if item2.kind == "array" and item1.kind == "reference":
+ return item2.element.kind == "reference" and item2.element.name == item1.name
+ return False
+
+
+def _get_some_array_code(
+ items: Iterable[model.LSP_TYPE_SPEC],
+ types: Dict[str, List[str]],
+ spec: model.LSPModel,
+) -> List[str]:
+ assert _is_some_array_type(items)
+ items_list = list(items)
+ item1 = items_list[0]
+ item2 = items_list[1]
+
+ if item1.kind == "array" and item2.kind == "reference":
+ return [
+ f" One({get_type_name(item2, types, spec)}),",
+ f" Many({get_type_name(item1, types, spec)}),",
+ ]
+
+ if item2.kind == "array" and item1.kind == "reference":
+ return [
+ f" One({get_type_name(item1, types, spec)}),",
+ f" Many({get_type_name(item2, types, spec)}),",
+ ]
+ return []
+
+
+def _get_common_name(items: Iterable[model.LSP_TYPE_SPEC], kind: str) -> List[str]:
+ names = [get_parts(item.name) for item in list(items) if item.kind == kind]
+ if len(names) < 2:
+ return []
+
+ smallest = min(names, key=len)
+ common = []
+ for i in range(len(smallest)):
+ if all(name[i] == smallest[i] for name in names):
+ common.append(smallest[i])
+ return common
+
+
+def _is_all_reference_similar_type(alias: model.TypeAlias) -> bool:
+ items_list = list(alias.type.items)
+ return all(item.kind in ["reference", "base", "literal"] for item in items_list)
+
+
+def _get_all_reference_similar_code(
+ alias: model.TypeAlias,
+ types: TypeData,
+ spec: model.LSPModel,
+) -> List[str]:
+ items = alias.type.items
+ assert _is_all_reference_similar_type(alias)
+
+ # Ensure all literal types have a name
+ for item in list(items):
+ if item.kind == "literal":
+ get_type_name(item, types, spec, None, alias.name)
+
+ common_name = [
+ i.lower()
+ for i in (
+ _get_common_name(items, "reference")
+ + _get_common_name(items, "literal")
+ + ["struct"]
+ )
+ ]
+
+ lines = []
+ value = 0
+ field_names = []
+ for item in list(items):
+ if item.kind == "base" and item.name == "null":
+ lines += ["None,"]
+ field_names += ["None"]
+ elif item.kind == "base":
+ name = _base_to_field_name(item.name)
+ lines += [f"{name}({get_type_name(item, types, spec)}),"]
+ field_names += [name]
+ elif item.kind == "reference":
+ name = [
+ part for part in get_parts(item.name) if part.lower() not in common_name
+ ]
+ if len(name) == 0:
+ name = [f"Value{value}"]
+ value += 1
+ common_name += [n.lower() for n in name]
+ name = to_upper_camel_case("".join(name))
+ field_names += [name]
+ lines += [f"{name}({get_type_name(item, types, spec)}),"]
+ elif item.kind == "literal":
+ name = [
+ part for part in get_parts(item.name) if part.lower() not in common_name
+ ]
+ optional_props = [p for p in item.value.properties if p.optional]
+ required_props = [p for p in item.value.properties if not p.optional]
+
+ # Try picking a name using required props first and then optional props
+ if len(name) == 0:
+ for p in required_props + optional_props:
+ name = [
+ part
+ for part in get_parts(p.name)
+ if part.lower() not in common_name
+ ]
+ if len(name) != 0:
+ break
+
+ # If we still don't have a name, then try picking a name using required props
+ # and then optional props without checking for common name list. But check
+ # that the name is not already used.
+ if len(name) == 0:
+ for p in required_props + optional_props:
+ if to_upper_camel_case(p.name) not in field_names:
+ name = get_parts(p.name)
+ break
+
+ # If we still don't have a name, then just use a generic "Value{int}" as name
+ if len(name) == 0:
+ name = [f"Value{value}"]
+ value += 1
+ common_name += [n.lower() for n in name]
+ name = to_upper_camel_case("".join(name))
+ field_names += [name]
+ lines += [f"{name}({item.name}),"]
+ else:
+ raise ValueError(f"Unknown type {item}")
+ return lines
+
+
+def _base_to_field_name(base_name: str) -> str:
+ if base_name == "boolean":
+ return "Bool"
+ if base_name == "integer":
+ return "Int"
+ if base_name == "decimal":
+ return "Real"
+ if base_name == "string":
+ return "String"
+ if base_name == "uinteger":
+ return "UInt"
+ if base_name == "null":
+ return "None"
+ raise ValueError(f"Unknown base type {base_name}")
+
+
+def _get_literal_field_name(literal: model.LiteralType, types: TypeData) -> str:
+ properties = list(literal.value.properties)
+
+ if len(properties) == 1 and properties[0].kind == "base":
+ return _base_to_field_name(properties[0].name)
+
+ if len(properties) == 1 and properties[0].kind == "reference":
+ return to_upper_camel_case(properties[0].name)
+
+ return generate_literal_struct_name(literal, types)
+
+
+def _generate_or_type_alias(
+ alias_def: model.TypeAlias, types: Dict[str, List[str]], spec: model.LSPModel
+) -> List[str]:
+ inner = []
+
+ if len(alias_def.type.items) == 2 and _is_some_array_type(alias_def.type.items):
+ inner += _get_some_array_code(alias_def.type.items, types, spec)
+ elif _is_all_reference_similar_type(alias_def):
+ inner += _get_all_reference_similar_code(alias_def, types, spec)
+ else:
+ index = 0
+
+ for sub_type in alias_def.type.items:
+ if sub_type.kind == "base" and sub_type.name == "null":
+ inner += [f"None,"]
+ else:
+ inner += [f"ValueType{index}({get_type_name(sub_type, types, spec)}),"]
+ index += 1
+ return type_alias_wrapper(alias_def, inner)
+
+
+def generate_type_alias(
+ alias_def: model.TypeAlias, types: TypeData, spec: model.LSPModel
+) -> List[str]:
+ doc = _get_doc(alias_def.documentation)
+ doc += generate_extras(alias_def)
+
+ lines = []
+ if alias_def.type.kind == "reference":
+ lines += doc
+ lines += [f"pub type {alias_def.name} = {alias_def.type.name};"]
+ elif alias_def.type.kind == "array":
+ lines += doc
+ lines += [
+ f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};"
+ ]
+ elif alias_def.type.kind == "or":
+ lines += _generate_or_type_alias(alias_def, types, spec)
+ elif alias_def.type.kind == "and":
+ raise ValueError("And type not supported")
+ elif alias_def.type.kind == "literal":
+ lines += doc
+ lines += [
+ f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};"
+ ]
+ elif alias_def.type.kind == "base":
+ lines += doc
+ lines += [
+ f"pub type {alias_def.name} = {get_type_name(alias_def.type, types, spec)};"
+ ]
+ else:
+ pass
+
+ types.add_type_info(alias_def, alias_def.name, lines)
+
+
+def generate_structures(spec: model.LSPModel, types: TypeData) -> Dict[str, List[str]]:
+ for struct in spec.structures:
+ if not types.has_id(struct):
+ generate_struct(struct, types, spec)
+ return types
+
+
+def generate_struct(
+ struct_def: model.Structure, types: TypeData, spec: model.LSPModel
+) -> None:
+ inner = []
+ for prop_def in get_extended_properties(struct_def, spec):
+ inner += generate_property(prop_def, types, spec)
+
+ lines = struct_wrapper(struct_def, inner)
+ types.add_type_info(struct_def, struct_def.name, lines)
+
+
+def generate_notifications(
+ spec: model.LSPModel, types: TypeData
+) -> Dict[str, List[str]]:
+ for notification in spec.notifications:
+ if not types.has_id(notification):
+ generate_notification(notification, types, spec)
+ return types
+
+
+def required_rpc_properties(name: Optional[str] = None) -> List[model.Property]:
+ props = [
+ model.Property(
+ name="jsonrpc",
+ type=model.BaseType(kind="base", name="string"),
+ optional=False,
+ documentation="The version of the JSON RPC protocol.",
+ ),
+ ]
+ if name:
+ props += [
+ model.Property(
+ name="method",
+ type=model.ReferenceType(kind="reference", name=name),
+ optional=False,
+ documentation="The method to be invoked.",
+ ),
+ ]
+ return props
+
+
+def generate_notification(
+ notification_def: model.Notification, types: TypeData, spec: model.LSPModel
+) -> None:
+ properties = required_rpc_properties("LSPNotificationMethods")
+ if notification_def.params:
+ properties += [
+ model.Property(
+ name="params",
+ type=notification_def.params,
+ )
+ ]
+
+ inner = []
+ for prop_def in properties:
+ inner += generate_property(prop_def, types, spec)
+
+ lines = struct_wrapper(notification_def, inner)
+ types.add_type_info(
+ notification_def, get_message_type_name(notification_def), lines
+ )
+
+
+def generate_required_request_types(
+ spec: model.LSPModel, types: TypeData
+) -> Dict[str, List[str]]:
+ lsp_id = model.TypeAlias(
+ name="LSPId",
+ documentation="An identifier to denote a specific request.",
+ type=model.OrType(
+ kind="or",
+ items=[
+ model.BaseType(kind="base", name="integer"),
+ model.BaseType(kind="base", name="string"),
+ ],
+ ),
+ )
+ generate_type_alias(lsp_id, types, spec)
+
+ lsp_id_optional = model.TypeAlias(
+ name="LSPIdOptional",
+ documentation="An identifier to denote a specific response.",
+ type=model.OrType(
+ kind="or",
+ items=[
+ model.BaseType(kind="base", name="integer"),
+ model.BaseType(kind="base", name="string"),
+ model.BaseType(kind="base", name="null"),
+ ],
+ ),
+ )
+ generate_type_alias(lsp_id_optional, types, spec)
+
+
+def generate_requests(spec: model.LSPModel, types: TypeData) -> Dict[str, List[str]]:
+ generate_required_request_types(spec, types)
+ for request in spec.requests:
+ if not types.has_id(request):
+ generate_request(request, types, spec)
+ generate_response(request, types, spec)
+ generate_partial_result(request, types, spec)
+ generate_registration_options(request, types, spec)
+ return types
+
+
+def generate_request(
+ request_def: model.Request, types: TypeData, spec: model.LSPModel
+) -> None:
+ properties = required_rpc_properties("LSPRequestMethods")
+
+ properties += [
+ model.Property(
+ name="id",
+ type=model.ReferenceType(kind="reference", name="LSPId"),
+ optional=False,
+ documentation="The request id.",
+ )
+ ]
+ if request_def.params:
+ properties += [
+ model.Property(
+ name="params",
+ type=request_def.params,
+ )
+ ]
+
+ inner = []
+ for prop_def in properties:
+ inner += generate_property(prop_def, types, spec)
+
+ lines = struct_wrapper(request_def, inner)
+ types.add_type_info(request_def, get_message_type_name(request_def), lines)
+
+
+def generate_response(
+ request_def: model.Request, types: TypeData, spec: model.LSPModel
+) -> None:
+ properties = required_rpc_properties("LSPRequestMethods")
+ properties += [
+ model.Property(
+ name="id",
+ type=model.ReferenceType(kind="reference", name="LSPIdOptional"),
+ optional=False,
+ documentation="The request id.",
+ )
+ ]
+ if request_def.result:
+ if request_def.result.kind == "base" and request_def.result.name == "null":
+ properties += [
+ model.Property(
+ name="result",
+ type=model.ReferenceType(kind="reference", name="LSPNull"),
+ )
+ ]
+ else:
+ properties += [
+ model.Property(
+ name="result",
+ type=request_def.result,
+ )
+ ]
+ name = fix_lsp_method_name(request_def.method)
+ response_def = model.Structure(
+ name=f"{name}Response",
+ documentation=f"Response to the [{name}Request].",
+ properties=properties,
+ since=request_def.since,
+ deprecated=request_def.deprecated,
+ )
+
+ inner = []
+ for prop_def in properties:
+ inner += generate_property(prop_def, types, spec)
+
+ lines = struct_wrapper(response_def, inner)
+ types.add_type_info(response_def, response_def.name, lines)
+
+
+def generate_partial_result(
+ request_def: model.Request, types: TypeData, spec: model.LSPModel
+) -> None:
+ if not request_def.partialResult:
+ return
+
+ if request_def.partialResult.kind not in ["and", "or"]:
+ return
+
+
+def generate_registration_options(
+ request_def: model.Request, types: TypeData, spec: model.LSPModel
+) -> None:
+ if not request_def.registrationOptions:
+ return
+
+ if request_def.registrationOptions.kind not in ["and", "or"]:
+ return