LuaObjectGenerator.Emit.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. using Microsoft.CodeAnalysis;
  2. using Microsoft.CodeAnalysis.CSharp;
  3. using Microsoft.CodeAnalysis.CSharp.Syntax;
  4. namespace Lua.SourceGenerator;
  5. partial class LuaObjectGenerator
  6. {
  7. static string GetLuaValuePrefix(ITypeSymbol typeSymbol, SymbolReferences references, Compilation compilation)
  8. {
  9. return compilation.ClassifyCommonConversion(typeSymbol, references.LuaUserData).Exists
  10. ? "global::Lua.LuaValue.FromUserData("
  11. : "(";
  12. }
  13. static bool TryEmit(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context, Dictionary<INamedTypeSymbol, TypeMetadata> metaDict)
  14. {
  15. try
  16. {
  17. var error = false;
  18. // must be partial
  19. if (!typeMetadata.IsPartial())
  20. {
  21. context.ReportDiagnostic(Diagnostic.Create(
  22. DiagnosticDescriptors.MustBePartial,
  23. typeMetadata.Syntax.Identifier.GetLocation(),
  24. typeMetadata.Symbol.Name));
  25. error = true;
  26. }
  27. // nested is not allowed
  28. if (typeMetadata.IsNested())
  29. {
  30. context.ReportDiagnostic(Diagnostic.Create(
  31. DiagnosticDescriptors.NestedNotAllowed,
  32. typeMetadata.Syntax.Identifier.GetLocation(),
  33. typeMetadata.Symbol.Name));
  34. error = true;
  35. }
  36. // verify abstract/interface
  37. if (typeMetadata.Symbol.IsAbstract)
  38. {
  39. context.ReportDiagnostic(Diagnostic.Create(
  40. DiagnosticDescriptors.AbstractNotAllowed,
  41. typeMetadata.Syntax.Identifier.GetLocation(),
  42. typeMetadata.TypeName));
  43. error = true;
  44. }
  45. if (!ValidateMembers(typeMetadata, compilation, references, context, metaDict))
  46. {
  47. error = true;
  48. }
  49. if (error)
  50. {
  51. return false;
  52. }
  53. builder.AppendLine("// <auto-generated />");
  54. builder.AppendLine("#nullable enable");
  55. builder.AppendLine("#pragma warning disable CS0162 // Unreachable code");
  56. builder.AppendLine("#pragma warning disable CS0219 // Variable assigned but never used");
  57. builder.AppendLine("#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.");
  58. builder.AppendLine("#pragma warning disable CS8601 // Possible null reference assignment");
  59. builder.AppendLine("#pragma warning disable CS8602 // Possible null return");
  60. builder.AppendLine("#pragma warning disable CS8604 // Possible null reference argument for parameter");
  61. builder.AppendLine("#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method");
  62. builder.AppendLine();
  63. var ns = typeMetadata.Symbol.ContainingNamespace;
  64. if (!ns.IsGlobalNamespace)
  65. {
  66. builder.AppendLine($"namespace {ns}");
  67. builder.BeginBlock();
  68. }
  69. var typeDeclarationKeyword = (typeMetadata.Symbol.IsRecord, typeMetadata.Symbol.IsValueType) switch
  70. {
  71. (true, true) => "record struct",
  72. (true, false) => "record",
  73. (false, true) => "struct",
  74. (false, false) => "class"
  75. };
  76. using var _ = builder.BeginBlockScope($"partial {typeDeclarationKeyword} {typeMetadata.TypeName} : global::Lua.ILuaUserData");
  77. var metamethodSet = new HashSet<LuaObjectMetamethod>();
  78. if (!TryEmitMethods(typeMetadata, builder, references, compilation, metamethodSet, context))
  79. {
  80. return false;
  81. }
  82. if (!TryEmitIndexMetamethod(typeMetadata, builder, references, compilation, context))
  83. {
  84. return false;
  85. }
  86. if (!TryEmitNewIndexMetamethod(typeMetadata, builder, references, context))
  87. {
  88. return false;
  89. }
  90. if (!TryEmitMetatable(builder, metamethodSet, context))
  91. {
  92. return false;
  93. }
  94. // implicit operator
  95. builder.AppendLine($"public static implicit operator global::Lua.LuaValue({typeMetadata.FullTypeName} value)");
  96. using (builder.BeginBlockScope())
  97. {
  98. builder.AppendLine("return global::Lua.LuaValue.FromUserData(value);");
  99. }
  100. if (!ns.IsGlobalNamespace)
  101. {
  102. builder.EndBlock();
  103. }
  104. builder.AppendLine("#pragma warning restore CS0162 // Unreachable code");
  105. builder.AppendLine("#pragma warning restore CS0219 // Variable assigned but never used");
  106. builder.AppendLine("#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.");
  107. builder.AppendLine("#pragma warning restore CS8601 // Possible null reference assignment");
  108. builder.AppendLine("#pragma warning restore CS8602 // Possible null return");
  109. builder.AppendLine("#pragma warning restore CS8604 // Possible null reference argument for parameter");
  110. builder.AppendLine("#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method");
  111. return true;
  112. }
  113. catch (Exception)
  114. {
  115. return false;
  116. }
  117. }
  118. static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation, SymbolReferences references, in SourceProductionContext context, Dictionary<INamedTypeSymbol, TypeMetadata> metaDict)
  119. {
  120. var isValid = true;
  121. foreach (var property in typeMetadata.Properties)
  122. {
  123. if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaValue))
  124. {
  125. continue;
  126. }
  127. if (SymbolEqualityComparer.Default.Equals(property.Type, references.LuaUserData))
  128. {
  129. continue;
  130. }
  131. if (SymbolEqualityComparer.Default.Equals(property.Type, typeMetadata.Symbol))
  132. {
  133. continue;
  134. }
  135. if (compilation.ClassifyConversion(property.Type, references.LuaUserData).Exists)
  136. {
  137. continue;
  138. }
  139. var conversion = compilation.ClassifyConversion(property.Type, references.LuaValue);
  140. if (!conversion.Exists && (property.Type is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
  141. {
  142. context.ReportDiagnostic(Diagnostic.Create(
  143. DiagnosticDescriptors.InvalidPropertyType,
  144. property.Symbol.Locations.FirstOrDefault(),
  145. property.Type.Name));
  146. isValid = false;
  147. }
  148. }
  149. foreach (var method in typeMetadata.Methods)
  150. {
  151. if (!method.Symbol.ReturnsVoid)
  152. {
  153. var typeSymbol = method.Symbol.ReturnType;
  154. if (method.IsAsync)
  155. {
  156. var namedType = (INamedTypeSymbol)typeSymbol;
  157. if (namedType.TypeArguments.Length == 0)
  158. {
  159. goto PARAMETERS;
  160. }
  161. typeSymbol = namedType.TypeArguments[0];
  162. }
  163. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue))
  164. {
  165. goto PARAMETERS;
  166. }
  167. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaUserData))
  168. {
  169. goto PARAMETERS;
  170. }
  171. if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol))
  172. {
  173. goto PARAMETERS;
  174. }
  175. if (compilation.ClassifyConversion(typeSymbol, references.LuaUserData).Exists)
  176. {
  177. goto PARAMETERS;
  178. }
  179. var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
  180. if (!conversion.Exists && (typeSymbol is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
  181. {
  182. context.ReportDiagnostic(Diagnostic.Create(
  183. DiagnosticDescriptors.InvalidReturnType,
  184. typeSymbol.Locations.FirstOrDefault(),
  185. typeSymbol.Name));
  186. isValid = false;
  187. }
  188. }
  189. PARAMETERS:
  190. for (var index = 0; index < method.Symbol.Parameters.Length; index++)
  191. {
  192. var parameterSymbol = method.Symbol.Parameters[index];
  193. var typeSymbol = parameterSymbol.Type;
  194. if (index == method.Symbol.Parameters.Length - 1 && SymbolEqualityComparer.Default.Equals(typeSymbol, references.CancellationToken))
  195. {
  196. continue;
  197. }
  198. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaValue))
  199. {
  200. continue;
  201. }
  202. if (SymbolEqualityComparer.Default.Equals(typeSymbol, references.LuaUserData))
  203. {
  204. continue;
  205. }
  206. if (SymbolEqualityComparer.Default.Equals(typeSymbol, typeMetadata.Symbol))
  207. {
  208. continue;
  209. }
  210. if (compilation.ClassifyConversion(typeSymbol, references.LuaUserData).Exists)
  211. {
  212. continue;
  213. }
  214. var conversion = compilation.ClassifyConversion(typeSymbol, references.LuaValue);
  215. if (!conversion.Exists && (typeSymbol is not INamedTypeSymbol namedTypeSymbol || !metaDict.ContainsKey(namedTypeSymbol)))
  216. {
  217. context.ReportDiagnostic(Diagnostic.Create(
  218. DiagnosticDescriptors.InvalidParameterType,
  219. typeSymbol.Locations.FirstOrDefault(),
  220. typeSymbol.Name));
  221. isValid = false;
  222. }
  223. }
  224. }
  225. return isValid;
  226. }
  227. static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, in SourceProductionContext context)
  228. {
  229. builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, ct) =>");
  230. using (builder.BeginBlockScope())
  231. {
  232. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  233. builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
  234. builder.AppendLine("var result = key switch");
  235. using (builder.BeginBlockScope())
  236. {
  237. foreach (var propertyMetadata in typeMetadata.Properties)
  238. {
  239. var conversionPrefix = GetLuaValuePrefix(propertyMetadata.Type, references, compilation);
  240. if (propertyMetadata.IsStatic)
  241. {
  242. builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => {conversionPrefix}{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name}),");
  243. }
  244. else
  245. {
  246. builder.AppendLine(@$"""{propertyMetadata.LuaMemberName}"" => {conversionPrefix}userData.{propertyMetadata.Symbol.Name}),");
  247. }
  248. }
  249. foreach (var methodMetadata in typeMetadata.Methods
  250. .Where(x => x.HasMemberAttribute))
  251. {
  252. builder.AppendLine(@$"""{methodMetadata.LuaMemberName}"" => new global::Lua.LuaValue(__function_{methodMetadata.LuaMemberName}),");
  253. }
  254. builder.AppendLine(@$"_ => global::Lua.LuaValue.Nil,");
  255. }
  256. builder.AppendLine(";");
  257. builder.AppendLine("return new global::System.Threading.Tasks.ValueTask<int>(context.Return(result));");
  258. }
  259. builder.AppendLine(");");
  260. return true;
  261. }
  262. static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, in SourceProductionContext context)
  263. {
  264. builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, ct) =>");
  265. using (builder.BeginBlockScope())
  266. {
  267. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  268. builder.AppendLine($"var key = context.GetArgument<global::System.String>(1);");
  269. builder.AppendLine("switch (key)");
  270. using (builder.BeginBlockScope())
  271. {
  272. foreach (var propertyMetadata in typeMetadata.Properties)
  273. {
  274. builder.AppendLine(@$"case ""{propertyMetadata.LuaMemberName}"":");
  275. using (builder.BeginIndentScope())
  276. {
  277. if (propertyMetadata.IsReadOnly)
  278. {
  279. builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
  280. }
  281. else if (propertyMetadata.IsStatic)
  282. {
  283. if (SymbolEqualityComparer.Default.Equals(propertyMetadata.Type, references.LuaValue))
  284. {
  285. builder.AppendLine($"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument(2);");
  286. }
  287. else
  288. {
  289. builder.AppendLine($"{typeMetadata.FullTypeName}.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
  290. }
  291. builder.AppendLine("break;");
  292. }
  293. else
  294. {
  295. if (SymbolEqualityComparer.Default.Equals(propertyMetadata.Type, references.LuaValue))
  296. {
  297. builder.AppendLine($"userData.{propertyMetadata.Symbol.Name} = context.GetArgument(2);");
  298. }
  299. else
  300. {
  301. builder.AppendLine($"userData.{propertyMetadata.Symbol.Name} = context.GetArgument<{propertyMetadata.TypeFullName}>(2);");
  302. }
  303. builder.AppendLine("break;");
  304. }
  305. }
  306. }
  307. foreach (var methodMetadata in typeMetadata.Methods
  308. .Where(x => x.HasMemberAttribute))
  309. {
  310. builder.AppendLine(@$"case ""{methodMetadata.LuaMemberName}"":");
  311. using (builder.BeginIndentScope())
  312. {
  313. builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' cannot overwrite."");");
  314. }
  315. }
  316. builder.AppendLine(@$"default:");
  317. using (builder.BeginIndentScope())
  318. {
  319. builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State, $""'{{key}}' not found."");");
  320. }
  321. }
  322. builder.AppendLine("return new global::System.Threading.Tasks.ValueTask<int>(context.Return());");
  323. }
  324. builder.AppendLine(");");
  325. return true;
  326. }
  327. static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation, HashSet<LuaObjectMetamethod> metamethodSet, in SourceProductionContext context)
  328. {
  329. builder.AppendLine();
  330. foreach (var methodMetadata in typeMetadata.Methods)
  331. {
  332. string? functionName = null;
  333. if (methodMetadata.HasMemberAttribute)
  334. {
  335. functionName = $"__function_{methodMetadata.LuaMemberName}";
  336. EmitMethodFunction(functionName, methodMetadata.LuaMemberName, typeMetadata, methodMetadata, builder, references, compilation);
  337. }
  338. if (methodMetadata.HasMetamethodAttribute)
  339. {
  340. if (!metamethodSet.Add(methodMetadata.Metamethod))
  341. {
  342. context.ReportDiagnostic(Diagnostic.Create(
  343. DiagnosticDescriptors.DuplicateMetamethod,
  344. methodMetadata.Symbol.Locations.FirstOrDefault(),
  345. typeMetadata.TypeName,
  346. methodMetadata.Metamethod
  347. ));
  348. continue;
  349. }
  350. if (functionName == null)
  351. {
  352. EmitMethodFunction($"__metamethod_{methodMetadata.Metamethod}", methodMetadata.Metamethod.ToString().ToLower(), typeMetadata, methodMetadata, builder, references, compilation);
  353. }
  354. else
  355. {
  356. builder.AppendLine($"static global::Lua.LuaFunction __metamethod_{methodMetadata.Metamethod} => {functionName};");
  357. }
  358. }
  359. }
  360. return true;
  361. }
  362. static void EmitMethodFunction(string functionName, string chunkName, TypeMetadata typeMetadata, MethodMetadata methodMetadata, CodeBuilder builder, SymbolReferences references, Compilation compilation)
  363. {
  364. builder.AppendLine($@"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction(""{chunkName}"", {(methodMetadata.IsAsync ? "async" : "")} (context, ct) =>");
  365. using (builder.BeginBlockScope())
  366. {
  367. var index = 0;
  368. if (!methodMetadata.IsStatic)
  369. {
  370. builder.AppendLine($"var userData = context.GetArgument<{typeMetadata.FullTypeName}>(0);");
  371. index++;
  372. }
  373. var hasCancellationToken = false;
  374. for (var i = 0; i < methodMetadata.Symbol.Parameters.Length; i++)
  375. {
  376. var parameter = methodMetadata.Symbol.Parameters[i];
  377. var parameterType = parameter.Type;
  378. var isParameterLuaValue = SymbolEqualityComparer.Default.Equals(parameterType, references.LuaValue);
  379. if (i == methodMetadata.Symbol.Parameters.Length - 1 && SymbolEqualityComparer.Default.Equals(parameterType, references.CancellationToken))
  380. {
  381. hasCancellationToken = true;
  382. break;
  383. }
  384. if (parameter.HasExplicitDefaultValue)
  385. {
  386. var syntax = (ParameterSyntax)parameter.DeclaringSyntaxReferences[0].GetSyntax();
  387. if (isParameterLuaValue)
  388. {
  389. builder.AppendLine($"var arg{index} = context.HasArgument({index}) ? context.GetArgument({index}) : {syntax.Default!.Value.ToFullString()};");
  390. }
  391. else
  392. {
  393. builder.AppendLine($"var arg{index} = context.HasArgument({index}) ? context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index}) : {syntax.Default!.Value.ToFullString()};");
  394. }
  395. }
  396. else
  397. {
  398. if (isParameterLuaValue)
  399. {
  400. builder.AppendLine($"var arg{index} = context.GetArgument({index});");
  401. }
  402. else
  403. {
  404. builder.AppendLine($"var arg{index} = context.GetArgument<{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>({index});");
  405. }
  406. }
  407. index++;
  408. }
  409. if (methodMetadata.HasReturnValue)
  410. {
  411. builder.Append("var result = ");
  412. }
  413. if (methodMetadata.IsAsync)
  414. {
  415. builder.Append("await ", !methodMetadata.HasReturnValue);
  416. }
  417. if (methodMetadata.IsStatic)
  418. {
  419. builder.Append($"{typeMetadata.FullTypeName}.{methodMetadata.Symbol.Name}(", !(methodMetadata.HasReturnValue || methodMetadata.IsAsync));
  420. builder.Append(string.Join(",", Enumerable.Range(0, index).Select(x => $"arg{x}")), false);
  421. if (hasCancellationToken)
  422. {
  423. builder.Append(index > 0 ? ",ct" : "ct", false);
  424. }
  425. builder.AppendLine(");", false);
  426. }
  427. else
  428. {
  429. builder.Append($"userData.{methodMetadata.Symbol.Name}(", !(methodMetadata.HasReturnValue || methodMetadata.IsAsync));
  430. builder.Append(string.Join(",", Enumerable.Range(1, index - 1).Select(x => $"arg{x}")), false);
  431. if (hasCancellationToken)
  432. {
  433. builder.Append(index > 1 ? ",ct" : "ct", false);
  434. }
  435. builder.AppendLine(");", false);
  436. }
  437. builder.Append("return ");
  438. if (methodMetadata.HasReturnValue)
  439. {
  440. var returnType = methodMetadata.Symbol.ReturnType;
  441. if (methodMetadata.IsAsync)
  442. {
  443. var namedType = (INamedTypeSymbol)returnType;
  444. if (namedType.TypeArguments.Length == 1)
  445. {
  446. returnType = namedType.TypeArguments[0];
  447. }
  448. }
  449. var conversionPrefix = GetLuaValuePrefix(returnType, references, compilation);
  450. builder.AppendLine(methodMetadata.IsAsync ? $"context.Return({conversionPrefix}result));" : $"new global::System.Threading.Tasks.ValueTask<int>(context.Return({conversionPrefix}result)));", false);
  451. }
  452. else
  453. {
  454. builder.AppendLine(methodMetadata.IsAsync ? "context.Return();" : "new global::System.Threading.Tasks.ValueTask<int>(context.Return());", false);
  455. }
  456. }
  457. builder.AppendLine(");");
  458. builder.AppendLine();
  459. }
  460. static bool TryEmitMetatable(CodeBuilder builder, IEnumerable<LuaObjectMetamethod> metamethods, in SourceProductionContext context)
  461. {
  462. builder.AppendLine("global::Lua.LuaTable? global::Lua.ILuaUserData.Metatable");
  463. using (builder.BeginBlockScope())
  464. {
  465. builder.AppendLine("get");
  466. using (builder.BeginBlockScope())
  467. {
  468. builder.AppendLine("if (__metatable != null) return __metatable;");
  469. builder.AppendLine();
  470. builder.AppendLine("__metatable = new();");
  471. builder.AppendLine("__metatable[global::Lua.Runtime.Metamethods.Index] = __metamethod_index;");
  472. builder.AppendLine("__metatable[global::Lua.Runtime.Metamethods.NewIndex] = __metamethod_newindex;");
  473. foreach (var metamethod in metamethods)
  474. {
  475. builder.AppendLine($"__metatable[global::Lua.Runtime.Metamethods.{metamethod}] = __metamethod_{metamethod};");
  476. }
  477. builder.AppendLine("return __metatable;");
  478. }
  479. builder.AppendLine("set");
  480. using (builder.BeginBlockScope())
  481. {
  482. builder.AppendLine("__metatable = value;");
  483. }
  484. }
  485. builder.AppendLine("static global::Lua.LuaTable? __metatable;");
  486. builder.AppendLine();
  487. return true;
  488. }
  489. }