diff --git a/generator/plugins/dotnet/custom/CustomArrayConverter.cs b/generator/plugins/dotnet/custom/CustomArrayConverter.cs new file mode 100644 index 0000000..d34eb62 --- /dev/null +++ b/generator/plugins/dotnet/custom/CustomArrayConverter.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Collections.Immutable; + +public class CustomArrayConverter : JsonConverter> +{ + public override ImmutableArray ReadJson(JsonReader reader, Type objectType, ImmutableArray existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return default(ImmutableArray); + } + + JArray array = JArray.Load(reader); + ImmutableArray.Builder builder = ImmutableArray.CreateBuilder(); + + for (int i = 0; i < array.Count; i++) + { + builder.Add((T)array[i].ToObject(typeof(T))!); + } + + return builder.ToImmutable(); + + } + + public override void WriteJson(JsonWriter writer, ImmutableArray value, JsonSerializer serializer) + { + if (value.IsDefault) + { + writer.WriteNull(); + } + else + { + writer.WriteStartArray(); + foreach (var item in value) + { + serializer.Serialize(writer, item); + } + writer.WriteEndArray(); + } + } +} \ No newline at end of file diff --git a/generator/plugins/dotnet/custom/CustomObjectConverter.cs b/generator/plugins/dotnet/custom/CustomObjectConverter.cs index 4245c1a..e03b588 100644 --- a/generator/plugins/dotnet/custom/CustomObjectConverter.cs +++ b/generator/plugins/dotnet/custom/CustomObjectConverter.cs @@ -9,15 +9,15 @@ public override T ReadJson(JsonReader reader, Type objectType, T? existingValue, { if (reader.TokenType == JsonToken.Null) { - return default(T); + return default(T)!; } Dictionary? o = serializer.Deserialize>(reader); if (o == null) { - return default(T); + return default(T)!; } - return (T)Activator.CreateInstance(typeof(T), o) ?? default(T); + return (T)Activator.CreateInstance(typeof(T), o)! ?? default(T)!; } public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer) diff --git a/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs b/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs index 55ac9cf..3124416 100644 --- a/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs +++ b/generator/plugins/dotnet/custom/DocumentSelectorConverter.cs @@ -20,7 +20,7 @@ public override DocumentSelector ReadJson(JsonReader reader, Type objectType, Do { if (reader.TokenType == JsonToken.Null) { - return null; + return null!; } var token = JToken.Load(reader); if (token.Type == JTokenType.Array) diff --git a/generator/plugins/dotnet/custom/OrType.cs b/generator/plugins/dotnet/custom/OrType.cs index e80a336..2ddf320 100644 --- a/generator/plugins/dotnet/custom/OrType.cs +++ b/generator/plugins/dotnet/custom/OrType.cs @@ -23,12 +23,12 @@ public OrType(U u) return obj.Value is T x ? x : default; } - public static explicit operator OrType(U obj) => obj is null ? null : new OrType(obj); - public static explicit operator OrType(T obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(U obj) => obj is null ? null! : new OrType(obj); + public static explicit operator OrType(T obj) => obj is null ? null! : new OrType(obj); public override string ToString() { - return Value?.ToString(); + return Value?.ToString()!; } } @@ -66,15 +66,15 @@ public OrType(V v) return obj.Value is V x ? x : default; } - public static explicit operator OrType(U obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(U obj) => obj is null ? null! : new OrType(obj); - public static explicit operator OrType(T obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(T obj) => obj is null ? null! : new OrType(obj); - public static explicit operator OrType(V obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(V obj) => obj is null ? null! : new OrType(obj); public override string ToString() { - return Value?.ToString(); + return Value?.ToString()!; } } @@ -123,16 +123,16 @@ public OrType(W w) return obj.Value is W x ? x : default; } - public static explicit operator OrType(U obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(U obj) => obj is null ? null! : new OrType(obj); - public static explicit operator OrType(T obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(T obj) => obj is null ? null! : new OrType(obj); - public static explicit operator OrType(V obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(V obj) => obj is null ? null! : new OrType(obj); - public static explicit operator OrType(W obj) => obj is null ? null : new OrType(obj); + public static explicit operator OrType(W obj) => obj is null ? null! : new OrType(obj); public override string ToString() { - return Value?.ToString(); + return Value?.ToString()!; } } diff --git a/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs b/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs index 65bb36d..f2dbf23 100644 --- a/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs +++ b/generator/plugins/dotnet/custom/OrTypeArrayConverter.cs @@ -1,9 +1,9 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.Collections.Immutable; - -public class OrTypeArrayConverter : JsonConverter[]> +public class OrTypeArrayConverter : JsonConverter>> { private OrTypeConverter _converter; @@ -12,27 +12,27 @@ public OrTypeArrayConverter() _converter = new OrTypeConverter(); } - public override OrType[] ReadJson(JsonReader reader, Type objectType, OrType[]? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ImmutableArray> ReadJson(JsonReader reader, Type objectType, ImmutableArray> existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { - return null; + return default(ImmutableArray>); } JArray array = JArray.Load(reader); - var result = new OrType[array.Count]; + ImmutableArray>.Builder builder = ImmutableArray.CreateBuilder>(); for (int i = 0; i < array.Count; i++) { - result[i] = (OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer); + builder.Add((OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer)!); } - return result; + return builder.ToImmutable(); } - public override void WriteJson(JsonWriter writer, OrType[]? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, ImmutableArray> value, JsonSerializer serializer) { - if (value is null) + if (value.IsDefault) { writer.WriteNull(); } @@ -49,7 +49,7 @@ public override void WriteJson(JsonWriter writer, OrType[]? value, JsonSer } } } -public class OrTypeArrayConverter : JsonConverter[]> +public class OrTypeArrayConverter : JsonConverter>> { private OrTypeConverter _converter; @@ -58,27 +58,27 @@ public OrTypeArrayConverter() _converter = new OrTypeConverter(); } - public override OrType[] ReadJson(JsonReader reader, Type objectType, OrType[]? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ImmutableArray> ReadJson(JsonReader reader, Type objectType, ImmutableArray> existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { - return null; + return default(ImmutableArray>); } JArray array = JArray.Load(reader); - var result = new OrType[array.Count]; + ImmutableArray>.Builder builder = ImmutableArray.CreateBuilder>(); for (int i = 0; i < array.Count; i++) { - result[i] = (OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer); + builder.Add((OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer)!); } - return result; + return builder.ToImmutable(); } - public override void WriteJson(JsonWriter writer, OrType[]? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, ImmutableArray> value, JsonSerializer serializer) { - if (value is null) + if (value.IsDefault) { writer.WriteNull(); } @@ -97,7 +97,7 @@ public override void WriteJson(JsonWriter writer, OrType[]? value, Json } -public class OrTypeArrayConverter : JsonConverter[]> +public class OrTypeArrayConverter : JsonConverter>> { private OrTypeConverter _converter; @@ -106,27 +106,27 @@ public OrTypeArrayConverter() _converter = new OrTypeConverter(); } - public override OrType[] ReadJson(JsonReader reader, Type objectType, OrType[]? existingValue, bool hasExistingValue, JsonSerializer serializer) + public override ImmutableArray> ReadJson(JsonReader reader, Type objectType, ImmutableArray> existingValue, bool hasExistingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { - return null; + return default(ImmutableArray>); } JArray array = JArray.Load(reader); - var result = new OrType[array.Count]; + ImmutableArray>.Builder builder = ImmutableArray.CreateBuilder>(); for (int i = 0; i < array.Count; i++) { - result[i] = (OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer); + builder.Add((OrType)_converter.ReadJson(array[i].CreateReader(), typeof(OrType), null, serializer)!); } - return result; + return builder.ToImmutable(); } - public override void WriteJson(JsonWriter writer, OrType[]? value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, ImmutableArray> value, JsonSerializer serializer) { - if (value is null) + if (value.IsDefault) { writer.WriteNull(); } diff --git a/generator/plugins/dotnet/custom/OrTypeConverter.cs b/generator/plugins/dotnet/custom/OrTypeConverter.cs index ae3c12a..f2aadbf 100644 --- a/generator/plugins/dotnet/custom/OrTypeConverter.cs +++ b/generator/plugins/dotnet/custom/OrTypeConverter.cs @@ -1,6 +1,28 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; +using System.Collections.Generic; +using System.Linq; + +internal class OrTypeConverterHelpers +{ + public static Type[] SortTypesByHeuristic(Type[] types, JToken jToken) + { + var typePropertyScores = new Dictionary(); + + string[] jTokenPropertyNames = jToken.Children().Select(p => p.Name.ToUpper()).ToArray(); + + foreach (Type type in types) + { + string[] typePropertyNames = type.GetProperties().Select(p => p.Name.ToUpper()).ToArray(); + + int score = jTokenPropertyNames.Count(propertyName => typePropertyNames.Contains(propertyName)); + typePropertyScores[type] = score; + } + + return types.OrderByDescending(type => typePropertyScores[type]).ToArray(); + } +} public class OrTypeConverter : JsonConverter> { @@ -32,7 +54,8 @@ public class OrTypeConverter : JsonConverter> return ReadStringToken(reader, serializer, types); } - return OrTypeConverter.ReadObjectToken(JToken.Load(reader), serializer, types); + var token = JToken.Load(reader); + return OrTypeConverter.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token)); } private static OrType ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types) @@ -93,7 +116,7 @@ private static OrType ReadBooleanToken(JsonReader reader, JsonSerializer s private static OrType ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types) { - string str = serializer.Deserialize(reader); + string str = serializer.Deserialize(reader)!; if (typeof(T) == typeof(string)) { return new OrType((T)(object)str); @@ -198,7 +221,8 @@ public class OrTypeConverter : JsonConverter> return ReadStringToken(reader, serializer, types); } - return OrTypeConverter.ReadObjectToken(JToken.Load(reader), serializer, types); + var token = JToken.Load(reader); + return OrTypeConverter.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token)); } private static OrType ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types) @@ -275,7 +299,7 @@ private static OrType ReadBooleanToken(JsonReader reader, JsonSerialize private static OrType ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types) { - string str = serializer.Deserialize(reader); + string str = serializer.Deserialize(reader)!; if (typeof(T) == typeof(string)) { return new OrType((T)(object)str); @@ -351,7 +375,7 @@ public override void WriteJson(JsonWriter writer, OrType? value, JsonSe } else { - serializer.Serialize(writer, value.Value); + serializer.Serialize(writer, value?.Value); } } } @@ -386,7 +410,8 @@ public class OrTypeConverter : JsonConverter> return ReadStringToken(reader, serializer, types); } - return OrTypeConverter.ReadObjectToken(JToken.Load(reader), serializer, types); + var token = JToken.Load(reader); + return OrTypeConverter.ReadObjectToken(token, serializer, OrTypeConverterHelpers.SortTypesByHeuristic(types, token)); } private static OrType ReadIntegerToken(JsonReader reader, JsonSerializer serializer, Type[] types) @@ -479,7 +504,7 @@ private static OrType ReadBooleanToken(JsonReader reader, JsonSerial private static OrType ReadStringToken(JsonReader reader, JsonSerializer serializer, Type[] types) { - string str = serializer.Deserialize(reader); + string str = serializer.Deserialize(reader)!; if (typeof(T) == typeof(string)) { return new OrType((T)(object)str); @@ -564,7 +589,7 @@ public override void WriteJson(JsonWriter writer, OrType? value, Jso } else { - serializer.Serialize(writer, value.Value); + serializer.Serialize(writer, value?.Value); } } -} +} \ No newline at end of file diff --git a/generator/plugins/dotnet/dotnet_classes.py b/generator/plugins/dotnet/dotnet_classes.py index fce8a9e..0a705cd 100644 --- a/generator/plugins/dotnet/dotnet_classes.py +++ b/generator/plugins/dotnet/dotnet_classes.py @@ -24,7 +24,8 @@ to_upper_camel_case, ) -CONVERTER_RE = re.compile(r"OrType<(?P.*)>\[\]") +ORTYPE_CONVERTER_RE = re.compile(r"OrType<(?P.*)>") +IMMUTABLE_ARRAY_CONVERTER_RE = re.compile(r"ImmutableArray<(?P.*)>") def _get_enum(name: str, spec: model.LSPModel) -> Optional[model.Enum]: @@ -69,6 +70,16 @@ def lsp_to_base_types(lsp_type: model.BaseType): raise ValueError(f"Unknown base type: {lsp_type.name}") +def get_types_for_usings(code: List[str]) -> List[str]: + immutable = [] + for line in code: + if "ImmutableArray<" in line: + immutable.append("ImmutableArray") + if "ImmutableDictionary<" in line: + immutable.append("ImmutableDictionary") + return list(set(immutable)) + + def has_null_base_type(items: List[model.LSP_TYPE_SPEC]) -> bool: return any(item.kind == "base" and item.name == "null" for item in items) @@ -96,7 +107,7 @@ def get_type_name( else: name = get_special_case_class_name(type_def.name) elif type_def.kind == "array": - name = f"{get_type_name(type_def.element, types, spec, name_context)}[]" + name = f"ImmutableArray<{get_type_name(type_def.element, types, spec, name_context)}>" elif type_def.kind == "map": name = generate_map_type(type_def, types, spec, name_context) elif type_def.kind == "base": @@ -156,7 +167,7 @@ def generate_map_type( else: value_type = get_type_name(type_def.value, types, spec, name_context) - return f"Dictionary<{key_type}, {value_type}>" + return f"ImmutableDictionary<{key_type}, {value_type}>" def get_converter(type_def: model.LSP_TYPE_SPEC, type_name: str) -> Optional[str]: @@ -169,7 +180,7 @@ def get_converter(type_def: model.LSP_TYPE_SPEC, type_name: str) -> Optional[str return f"[JsonConverter(typeof(CustomStringConverter<{type_def.name}>))]" elif type_def.kind == "reference" and type_def.name == "DocumentSelector": return "[JsonConverter(typeof(DocumentSelectorConverter))]" - elif type_def.kind == "or" and type_name.startswith("OrType<"): + elif type_def.kind == "or": subset = filter_null_base_type(type_def.items) if len(subset) == 1: return get_converter(subset[0], type_name) @@ -177,10 +188,20 @@ def get_converter(type_def: model.LSP_TYPE_SPEC, type_name: str) -> Optional[str converter = type_name.replace("OrType<", "OrTypeConverter<") return f"[JsonConverter(typeof({converter}))]" elif type_def.kind == "array" and type_name.startswith("OrType<"): - matches = CONVERTER_RE.match(type_name).groupdict() + matches = ORTYPE_CONVERTER_RE.match(type_name).groupdict() if "parts" in matches: converter = f"OrTypeArrayConverter<{matches['parts']}>" return f"[JsonConverter(typeof({converter}))]" + elif type_def.kind == "array": + matches = IMMUTABLE_ARRAY_CONVERTER_RE.match(type_name).groupdict() + elements = matches["elements"] + if elements.startswith("OrType<"): + matches = ORTYPE_CONVERTER_RE.match(elements).groupdict() + converter = f"OrTypeArrayConverter<{matches['parts']}>" + return f"[JsonConverter(typeof({converter}))]" + else: + converter = f"CustomArrayConverter<{elements}>" + return f"[JsonConverter(typeof({converter}))]" return None @@ -202,7 +223,15 @@ def generate_property( special_optional = prop_def.type.kind == "or" and has_null_base_type( prop_def.type.items ) - optional = "?" if prop_def.optional or special_optional else "" + optional = ( + "?" + if (prop_def.optional or special_optional) + and not ( + type_name.startswith("ImmutableArray<") + or type_name.startswith("ImmutableDictionary<") + ) + else "" + ) lines = ( get_doc(prop_def.documentation) + generate_extras(prop_def) @@ -283,7 +312,7 @@ def generate_literal_type( lines = namespace_wrapper( NAMESPACE, - get_usings(usings), + get_usings(usings + get_types_for_usings(inner)), class_wrapper(literal, inner), ) types.add_type_info(literal, literal.name, lines) @@ -311,7 +340,12 @@ def generate_constructor( prop.type.items ) if prop.optional or special_optional: - optional_args += [f"{prop_type}? {name} = null"] + if prop_type.startswith("ImmutableArray<") or prop_type.startswith( + "ImmutableDictionary<" + ): + optional_args += [f"{prop_type} {name} = default!"] + else: + optional_args += [f"{prop_type}? {name} = null"] ctor_data += [(prop_type, name, True)] elif prop.name == "jsonrpc": optional_args += [f'{prop_type} {name} = "2.0"'] @@ -347,6 +381,9 @@ def generate_class_from_struct( if types.get_by_name(struct.name) or struct.name.startswith("_"): return + if attributes is None: + attributes = [] + inner = [] usings = ["DataContract", "JsonConstructor"] @@ -362,7 +399,7 @@ def generate_class_from_struct( lines = namespace_wrapper( NAMESPACE, - get_usings(usings), + get_usings(usings + get_types_for_usings(inner + attributes)), class_wrapper(struct, inner, derived, attributes), ) types.add_type_info(struct, struct.name, lines) @@ -410,8 +447,9 @@ def generate_type_alias_constructor( for t in subset: sub_type = get_type_name(t, types, spec, type_def.name) arg = get_special_case_property_name(to_camel_case(sub_type)) - if arg.endswith("[]"): - arg = f"{arg[:-2]}s" + matches = re.match(r"ImmutableArray<(?P\w+)>", arg) + if matches: + arg = f"{matches['arg']}s" constructor += [ f"public {type_name}({sub_type} {arg}): base({arg}) {{}}", @@ -490,7 +528,9 @@ def generate_type_alias_converter( "}", ] - code = namespace_wrapper(NAMESPACE, get_usings(["JsonConverter"]), code) + code = namespace_wrapper( + NAMESPACE, get_usings(["JsonConverter"] + get_types_for_usings(code)), code + ) ref = model.Structure(**{"name": converter, "properties": []}) types.add_type_info(ref, converter, code) @@ -514,7 +554,7 @@ def generate_class_from_type_alias( inner = generate_type_alias_constructor(type_def, spec, types) lines = namespace_wrapper( NAMESPACE, - get_usings(usings), + get_usings(usings + get_types_for_usings(inner)), class_wrapper(type_def, inner, type_name, class_attributes), ) types.add_type_info(type_def, type_def.name, lines) @@ -576,9 +616,17 @@ def generate_code_for_variant_struct( conditions = [] for prop, prop_type in zip(struct.properties, prop_types): name = get_special_case_property_name(to_camel_case(prop.name)) - constructor_args += [f"{prop_type}? {name}"] + immutable = prop_type.startswith("ImmutableArray<") or prop_type.startswith( + "ImmutableDictionary<" + ) + constructor_args += [ + f"{prop_type} {name}" if immutable else f"{prop_type}? {name}" + ] ctor_data = [(prop_type)] - conditions += [f"({name} is null)"] + if immutable: + conditions += [f"({name}.IsDefault)"] + else: + conditions += [f"({name} is null)"] sig = ", ".join(constructor_args) types.add_ctor(struct.name, ctor_data) @@ -611,7 +659,7 @@ def generate_code_for_variant_struct( return namespace_wrapper( NAMESPACE, - get_usings(usings), + get_usings(usings + get_types_for_usings(inner)), class_wrapper(struct, inner, None), ) @@ -738,7 +786,7 @@ def generate_request_notification_methods(spec: model.LSPModel, types: TypeData) lines = namespace_wrapper( NAMESPACE, - get_usings(["System"]), + get_usings(["System"] + get_types_for_usings(inner_lines)), ["public static class LSPMethods", "{", *indent_lines(inner_lines), "}"], ) enum_type = model.Enum( @@ -923,7 +971,7 @@ def generate_all_classes(spec: model.LSPModel, types: TypeData): ( f"IRequest<{get_type_name(request.params, types, spec)}>" if request.params - else "IRequest" + else "IRequest" ), [ f"[Direction(MessageDirection.{to_upper_camel_case(request.messageDirection)})]", diff --git a/generator/plugins/dotnet/dotnet_helpers.py b/generator/plugins/dotnet/dotnet_helpers.py index 9527142..2eb357a 100644 --- a/generator/plugins/dotnet/dotnet_helpers.py +++ b/generator/plugins/dotnet/dotnet_helpers.py @@ -208,4 +208,8 @@ def get_usings(types: List[str]) -> List[str]: if t in types: usings.append("using System.Collections.Generic;") + for t in ["ImmutableArray", "ImmutableDictionary"]: + if t in types: + usings.append("using System.Collections.Immutable;") + return sorted(list(set(usings))) diff --git a/tests/dotnet/lsprotocol_tests/LSPTests.cs b/tests/dotnet/lsprotocol_tests/LSPTests.cs index d25c556..80a19ae 100644 --- a/tests/dotnet/lsprotocol_tests/LSPTests.cs +++ b/tests/dotnet/lsprotocol_tests/LSPTests.cs @@ -63,7 +63,7 @@ private static void RunTest(bool valid, string data, Type type) JToken token2 = JToken.Parse(newJson); RemoveNullProperties(token1); RemoveNullProperties(token2); - Assert.True(JToken.DeepEquals(token1, token2), $"Failed for : {data}"); + Assert.True(JToken.DeepEquals(token1, token2), $"JSON before and after serialization don't match:\r\nBEFORE:{data}\r\nAFTER:{newJson}"); } catch (Exception e) {