LuaObjectGenerator.Emit.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. using Microsoft.CodeAnalysis;
  2. using Microsoft.CodeAnalysis.CSharp;
  3. namespace Lua.SourceGenerator;
  4. partial class LuaObjectGenerator
  5. {
  6. static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context)
  7. {
  8. try
  9. {
  10. var error = false;
  11. // must be partial
  12. if (!typeMetadata.IsPartial())
  13. {
  14. context.ReportDiagnostic(Diagnostic.Create(
  15. DiagnosticDescriptors.MustBePartial,
  16. typeMetadata.Syntax.Identifier.GetLocation(),
  17. typeMetadata.Symbol.Name));
  18. error = true;
  19. }
  20. // nested is not allowed
  21. if (typeMetadata.IsNested())
  22. {
  23. context.ReportDiagnostic(Diagnostic.Create(
  24. DiagnosticDescriptors.NestedNotAllowed,
  25. typeMetadata.Syntax.Identifier.GetLocation(),
  26. typeMetadata.Symbol.Name));
  27. error = true;
  28. }
  29. // verify abstract/interface
  30. if (typeMetadata.Symbol.IsAbstract)
  31. {
  32. context.ReportDiagnostic(Diagnostic.Create(
  33. DiagnosticDescriptors.AbstractNotAllowed,
  34. typeMetadata.Syntax.Identifier.GetLocation(),
  35. typeMetadata.TypeName));
  36. error = true;
  37. }
  38. if (!ValidateMembers(typeMetadata, compilation, references, context))
  39. {
  40. error = true;
  41. }
  42. if (error)
  43. {
  44. return false;
  45. }
  46. builder.AppendLine("// <auto-generated />");
  47. builder.AppendLine("#nullable enable");
  48. builder.AppendLine("#pragma warning disable CS0162 // Unreachable code");
  49. builder.AppendLine("#pragma warning disable CS0219 // Variable assigned but never used");
  50. builder.AppendLine("#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.");
  51. builder.AppendLine("#pragma warning disable CS8601 // Possible null reference assignment");
  52. builder.AppendLine("#pragma warning disable CS8602 // Possible null return");
  53. builder.AppendLine("#pragma warning disable CS8604 // Possible null reference argument for parameter");
  54. builder.AppendLine("#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method");
  55. builder.AppendLine();
  56. var ns = typeMetadata.Symbol.ContainingNamespace;
  57. if (!ns.IsGlobalNamespace)
  58. {
  59. builder.AppendLine($"namespace {ns}");
  60. builder.BeginBlock();
  61. }
  62. var typeDeclarationKeyword = (typeMetadata.Symbol.IsRecord, typeMetadata.Symbol.IsValueType) switch
  63. {
  64. (true, true) => "record struct",
  65. (true, false) => "record",
  66. (false, true) => "struct",
  67. (false, false) => "class",
  68. };
  69. using var _ = builder.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMetadata.TypeName} : global::Lua.ILuaUserData");
  70. var metamethodSet = new HashSet<LuaObjectMetamethod>();
  71. if (!TryEmitMethods(typeMetadata, builder, metamethodSet, context))
  72. {
  73. return false;
  74. }
  75. if (!TryEmitIndexMetamethod(typeMetadata, builder, context))
  76. {
  77. return false;
  78. }
  79. if (!TryEmitNewIndexMetamethod(typeMetadata, builder, context))
  80. {
  81. return false;
  82. }
  83. if (!TryEmitMetatable(builder, metamethodSet, context))
  84. {
  85. return false;
  86. }
  87. // implicit operator
  88. builder.AppendLine($"public static implicit operator global::Lua.LuaValue({typeMetadata.FullTypeName} value)");
  89. using (builder.BeginBlockScope())
  90. {
  91. builder.AppendLine("return new(value);");
  92. }
  93. if (!ns.IsGlobalNamespace) builder.EndBlock();
  94. builder.AppendLine("#pragma warning restore CS0162 // Unreachable code");
  95. builder.AppendLine("#pragma warning restore CS0219 // Variable assigned but never used");
  96. builder.AppendLine("#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.");
  97. builder.AppendLine("#pragma warning restore CS8601 // Possible null reference assignment");
  98. builder.AppendLine("#pragma warning restore CS8602 // Possible null return");
  99. builder.AppendLine("#pragma warning restore CS8604 // Possible null reference argument for parameter");
  100. builder.AppendLine("#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method");
  101. return true;
  102. }
  103. catch (Exception)
  104. {
  105. return false;
  106. }
  107. }
  108. static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation, SymbolReferences references, in SourceProductionContext context)
  109. {
  110. var isValid = true;
  111. foreach (var property in typeMetadata.Properties)
  112. {
  113. if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaValue)) continue;
  114. if (SymbolEqualityComparer.Default.Equals(property.Type, typeMetadata.Symbol)) continue;
  115. var conversion = compilation.ClassifyConversion(property.Type, references.LuaValue);
  116. if (!conversion.Exists)
  117. {
  118. context.ReportDiagnostic(Diagnostic.Create(
  119. DiagnosticDescriptors.InvalidPropertyType,
  120. property.Symbol.Locations.FirstOrDefault(),
  121. property.Type.Name));
  122. isValid = false;
  123. }
  124. }
  125. foreach (var method in typeMetadata.Methods)
  126. {
  127. if (!method.Symbol.ReturnsVoid)
  128. {
  129. var typeSymbol = method.Symbol.ReturnType;
  130. if (method.IsAsync)
  131. {
  132. var namedType = (INamedTypeSymbol)typeSymbol;
  133. if (namedType.TypeArguments.Length == 0) goto PARAMETERS;
  134. typeSymbol = namedType.TypeArguments[0];
  135. }
  136. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue)) goto PARAMETERS;
  137. if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol)) goto PARAMETERS;
  138. var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
  139. if (!conversion.Exists)
  140. {
  141. context.ReportDiagnostic(Diagnostic.Create(
  142. DiagnosticDescriptors.InvalidReturnType,
  143. typeSymbol.Locations.FirstOrDefault(),
  144. typeSymbol.Name));
  145. isValid = false;
  146. }
  147. }
  148. PARAMETERS:
  149. foreach (var typeSymbol in method.Symbol.Parameters
  150. .Select(x => x.Type))
  151. {
  152. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue)) continue;
  153. if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol)) continue;
  154. var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
  155. if (!conversion.Exists)
  156. {
  157. context.ReportDiagnostic(Diagnostic.Create(
  158. DiagnosticDescriptors.InvalidParameterType,
  159. typeSymbol.Locations.FirstOrDefault(),
  160. typeSymbol.Name));
  161. isValid = false;
  162. }
  163. }
  164. }
  165. return isValid;
  166. }
  167. static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context)
  168. {
  169. builder.AppendLine("static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction((context, buffer, ct) =>");
  170. using (builder.BeginBlockScope())
  171. {
  172. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  173. builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
  174. builder.AppendLine("var result = key switch");
  175. using (builder.BeginBlockScope())
  176. {
  177. foreach (var propertyMetadata in typeMetadata.Properties)
  178. {
  179. if (propertyMetadata.IsStatic)
  180. {
  181. builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => new global::Lua.LuaValue({typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name}),");
  182. }
  183. else
  184. {
  185. builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => new global::Lua.LuaValue(userData.{propertyMetadata.Symbol.Name}),");
  186. }
  187. }
  188. foreach (var methodMetadata in typeMetadata.Methods
  189. .Where(x => x.HasMemberAttribute))
  190. {
  191. builder.AppendLine(@$"""{methodMetadata.LuaMemberName}"" => new global::Lua.LuaValue(__function_{methodMetadata.LuaMemberName}),");
  192. }
  193. builder.AppendLine(@$"_ => global::Lua.LuaValue.Nil,");
  194. }
  195. builder.AppendLine(";");
  196. builder.AppendLine("buffer.Span[0] = result;");
  197. builder.AppendLine("return new(1);");
  198. }
  199. builder.AppendLine(");");
  200. return true;
  201. }
  202. static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context)
  203. {
  204. builder.AppendLine("static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction((context, buffer, ct) =>");
  205. using (builder.BeginBlockScope())
  206. {
  207. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  208. builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
  209. builder.AppendLine("switch (key)");
  210. using (builder.BeginBlockScope())
  211. {
  212. foreach (var propertyMetadata in typeMetadata.Properties)
  213. {
  214. builder.AppendLine(@$"case ""{propertyMetadata.LuaMemberName}"":");
  215. using (builder.BeginIndentScope())
  216. {
  217. if (propertyMetadata.IsReadOnly)
  218. {
  219. builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
  220. }
  221. else if (propertyMetadata.IsStatic)
  222. {
  223. builder.AppendLine(@$"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
  224. builder.AppendLine("break;");
  225. }
  226. else
  227. {
  228. builder.AppendLine(@$"userData.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
  229. builder.AppendLine("break;");
  230. }
  231. }
  232. }
  233. foreach (var methodMetadata in typeMetadata.Methods
  234. .Where(x => x.HasMemberAttribute))
  235. {
  236. builder.AppendLine(@$"case ""{methodMetadata.LuaMemberName}"":");
  237. using (builder.BeginIndentScope())
  238. {
  239. builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' cannot overwrite."");");
  240. }
  241. }
  242. builder.AppendLine(@$"default:");
  243. using (builder.BeginIndentScope())
  244. {
  245. builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' not found."");");
  246. }
  247. }
  248. builder.AppendLine("return new(0);");
  249. }
  250. builder.AppendLine(");");
  251. return true;
  252. }
  253. static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, HashSet<LuaObjectMetamethod> metamethodSet, in SourceProductionContext context)
  254. {
  255. builder.AppendLine();
  256. foreach (var methodMetadata in typeMetadata.Methods)
  257. {
  258. string? functionName = null;
  259. if (methodMetadata.HasMemberAttribute)
  260. {
  261. functionName = $"__function_{methodMetadata.LuaMemberName}";
  262. EmitMethodFunction(functionName, typeMetadata, methodMetadata, builder);
  263. }
  264. if (methodMetadata.HasMetamethodAttribute)
  265. {
  266. if (!metamethodSet.Add(methodMetadata.Metamethod))
  267. {
  268. context.ReportDiagnostic(Diagnostic.Create(
  269. DiagnosticDescriptors.DuplicateMetamethod,
  270. methodMetadata.Symbol.Locations.FirstOrDefault(),
  271. typeMetadata.TypeName,
  272. methodMetadata.Metamethod
  273. ));
  274. continue;
  275. }
  276. if (functionName == null)
  277. {
  278. EmitMethodFunction($"__metamethod_{methodMetadata.Metamethod}", typeMetadata, methodMetadata, builder);
  279. }
  280. else
  281. {
  282. builder.AppendLine($"static global::Lua.LuaFunction __metamethod_{methodMetadata.Metamethod} => {functionName};");
  283. }
  284. }
  285. }
  286. return true;
  287. }
  288. static void EmitMethodFunction(string functionName, TypeMetadata typeMetadata, MethodMetadata methodMetadata, CodeBuilder builder)
  289. {
  290. builder.AppendLine($"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction({(methodMetadata.IsAsync ? "async" : "")} (context, buffer, ct) =>");
  291. using (builder.BeginBlockScope())
  292. {
  293. var index = 0;
  294. if (!methodMetadata.IsStatic)
  295. {
  296. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  297. index++;
  298. }
  299. foreach (var parameter in methodMetadata.Symbol.Parameters)
  300. {
  301. builder.AppendLine($"var arg{index} = context.GetArgument<{parameter.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index});");
  302. index++;
  303. }
  304. if (methodMetadata.HasReturnValue)
  305. {
  306. builder.Append("var result = ");
  307. }
  308. if (methodMetadata.IsAsync)
  309. {
  310. builder.Append("await ", false);
  311. }
  312. if (methodMetadata.IsStatic)
  313. {
  314. builder.Append($"{typeMetadata.FullTypeName}.{methodMetadata.Symbol.Name}(", false);
  315. builder.Append(string.Join(",", Enumerable.Range(0, index).Select(x => $"arg{x}")), false);
  316. builder.AppendLine(");", false);
  317. }
  318. else
  319. {
  320. builder.Append($"userData.{methodMetadata.Symbol.Name}(");
  321. builder.Append(string.Join(",", Enumerable.Range(1, index - 1).Select(x => $"arg{x}")), false);
  322. builder.AppendLine(");", false);
  323. }
  324. if (methodMetadata.HasReturnValue)
  325. {
  326. builder.AppendLine("buffer.Span[0] = new global::Lua.LuaValue(result);");
  327. builder.AppendLine($"return {(methodMetadata.IsAsync ? "1" : "new(1)")};");
  328. }
  329. else
  330. {
  331. builder.AppendLine($"return {(methodMetadata.IsAsync ? "0" : "new(0)")};");
  332. }
  333. }
  334. builder.AppendLine(");");
  335. builder.AppendLine();
  336. }
  337. static bool TryEmitMetatable(CodeBuilder builder, IEnumerable<LuaObjectMetamethod> metamethods, in SourceProductionContext context)
  338. {
  339. builder.AppendLine("global::Lua.LuaTable? global::Lua.ILuaUserData.Metatable");
  340. using (builder.BeginBlockScope())
  341. {
  342. builder.AppendLine("get");
  343. using (builder.BeginBlockScope())
  344. {
  345. builder.AppendLine("if (__metatable != null) return __metatable;");
  346. builder.AppendLine();
  347. builder.AppendLine("__metatable = new();");
  348. builder.AppendLine("__metatable[global::Lua.Runtime.Metamethods.Index] = __metamethod_index;");
  349. builder.AppendLine("__metatable[global::Lua.Runtime.Metamethods.NewIndex] = __metamethod_newindex;");
  350. foreach (var metamethod in metamethods)
  351. {
  352. builder.AppendLine($"__metatable[global::Lua.Runtime.Metamethods.{metamethod}] = __metamethod_{metamethod};");
  353. }
  354. builder.AppendLine("return __metatable;");
  355. }
  356. builder.AppendLine("set");
  357. using (builder.BeginBlockScope())
  358. {
  359. builder.AppendLine("__metatable = value;");
  360. }
  361. }
  362. builder.AppendLine("static global::Lua.LuaTable? __metatable;");
  363. builder.AppendLine();
  364. return true;
  365. }
  366. }