private void CreateTypeInferenceMethod( DocumentIntermediateNode documentNode, ComponentExtensionNode node, Dictionary <string, GenericTypeNameRewriter.Binding> bindings) { var @namespace = documentNode.FindPrimaryNamespace().Content; @namespace = string.IsNullOrEmpty(@namespace) ? "__Blazor" : "__Blazor." + @namespace; @namespace += "." + documentNode.FindPrimaryClass().ClassName; var typeInferenceNode = new ComponentTypeInferenceMethodIntermediateNode() { Bindings = bindings, Component = node, // Method name is generated and guaranteed not to collide, since it's unique for each // component call site. MethodName = $"Create{node.TagName}_{_id++}", FullTypeName = @namespace + ".TypeInference", }; node.TypeInferenceNode = typeInferenceNode; // Now we need to insert the type inference node into the tree. var namespaceNode = documentNode.Children .OfType <NamespaceDeclarationIntermediateNode>() .Where(n => n.Annotations.Contains(new KeyValuePair <object, object>(BlazorMetadata.Component.GenericTypedKey, bool.TrueString))) .FirstOrDefault(); if (namespaceNode == null) { namespaceNode = new NamespaceDeclarationIntermediateNode() { Annotations = { { BlazorMetadata.Component.GenericTypedKey, bool.TrueString }, }, Content = @namespace, }; documentNode.Children.Add(namespaceNode); } var classNode = namespaceNode.Children .OfType <ClassDeclarationIntermediateNode>() .Where(n => n.ClassName == "TypeInference") .FirstOrDefault(); if (classNode == null) { classNode = new ClassDeclarationIntermediateNode() { ClassName = "TypeInference", Modifiers = { "internal", "static", }, }; namespaceNode.Children.Add(classNode); } classNode.Children.Add(typeInferenceNode); }
// Currently the same for design time and runtime public void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } // This is ugly because CodeWriter doesn't allow us to erase, but we need to comma-delimit. So we have to // materizalize something can iterate, or use string.Join. We'll need this multiple times, so materializing // it. var parameters = GetParameterDeclarations(); // This is really similar to the code in WriteComponentAttribute and WriteComponentChildContent - except simpler because // attributes and child contents look like variables. // // Looks like: // // public static void CreateFoo_0<T1, T2>(RenderTreeBuilder builder, int seq, int __seq0, T1 __arg0, int __seq1, global::System.Collections.Generic.List<T2> __arg1, int __seq2, string __arg2) // { // builder.OpenComponent<Foo<T1, T2>>(); // builder.AddAttribute(__seq0, "Attr0", __arg0); // builder.AddAttribute(__seq1, "Attr1", __arg1); // builder.AddAttribute(__seq2, "Attr2", __arg2); // builder.CloseComponent(); // } // // As a special case, we need to generate a thunk for captures in this block instead of using // them verbatim. // // The problem is that RenderTreeBuilder wants an Action<object>. The caller can't write the type // name if it contains generics, and we can't write the variable they want to assign to. var writer = context.CodeWriter; writer.Write("public static void "); writer.Write(node.MethodName); writer.Write("<"); writer.Write(string.Join(", ", node.Component.Component.GetTypeParameters().Select(a => a.Name))); writer.Write(">"); writer.Write("("); writer.Write("global::"); writer.Write(BlazorApi.RenderTreeBuilder.FullTypeName); writer.Write(" builder"); writer.Write(", "); writer.Write("int seq"); if (parameters.Count > 0) { writer.Write(", "); } for (var i = 0; i < parameters.Count; i++) { writer.Write("int "); writer.Write(parameters[i].seqName); writer.Write(", "); writer.Write(parameters[i].typeName); writer.Write(" "); writer.Write(parameters[i].parameterName); if (i < parameters.Count - 1) { writer.Write(", "); } } writer.Write(")"); writer.WriteLine(); writer.WriteLine("{"); // builder.OpenComponent<TComponent>(42); context.CodeWriter.Write("builder"); context.CodeWriter.Write("."); context.CodeWriter.Write(BlazorApi.RenderTreeBuilder.OpenComponent); context.CodeWriter.Write("<"); context.CodeWriter.Write(node.Component.TypeName); context.CodeWriter.Write(">("); context.CodeWriter.Write("seq"); context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); var index = 0; foreach (var attribute in node.Component.Attributes) { context.CodeWriter.WriteStartInstanceMethodInvocation("builder", BlazorApi.RenderTreeBuilder.AddAttribute); context.CodeWriter.Write(parameters[index].seqName); context.CodeWriter.Write(", "); context.CodeWriter.Write($"\"{attribute.AttributeName}\""); context.CodeWriter.Write(", "); context.CodeWriter.Write(parameters[index].parameterName); context.CodeWriter.WriteEndMethodInvocation(); index++; } foreach (var childContent in node.Component.ChildContents) { context.CodeWriter.WriteStartInstanceMethodInvocation("builder", BlazorApi.RenderTreeBuilder.AddAttribute); context.CodeWriter.Write(parameters[index].seqName); context.CodeWriter.Write(", "); context.CodeWriter.Write($"\"{childContent.AttributeName}\""); context.CodeWriter.Write(", "); context.CodeWriter.Write(parameters[index].parameterName); context.CodeWriter.WriteEndMethodInvocation(); index++; } foreach (var capture in node.Component.Captures) { context.CodeWriter.WriteStartInstanceMethodInvocation("builder", capture.IsComponentCapture ? BlazorApi.RenderTreeBuilder.AddComponentReferenceCapture : BlazorApi.RenderTreeBuilder.AddElementReferenceCapture); context.CodeWriter.Write(parameters[index].seqName); context.CodeWriter.Write(", "); var cast = capture.IsComponentCapture ? $"({capture.ComponentCaptureTypeName})" : string.Empty; context.CodeWriter.Write($"(__value) => {{ {parameters[index].parameterName}({cast}__value); }}"); context.CodeWriter.WriteEndMethodInvocation(); index++; } context.CodeWriter.WriteInstanceMethodInvocation("builder", BlazorApi.RenderTreeBuilder.CloseComponent); writer.WriteLine("}"); List <(string seqName, string typeName, string parameterName)> GetParameterDeclarations() { var p = new List <(string seqName, string typeName, string parameterName)>(); foreach (var attribute in node.Component.Attributes) { p.Add(($"__seq{p.Count}", attribute.TypeName, $"__arg{p.Count}")); } foreach (var childContent in node.Component.ChildContents) { p.Add(($"__seq{p.Count}", childContent.TypeName, $"__arg{p.Count}")); } foreach (var capture in node.Component.Captures) { p.Add(($"__seq{p.Count}", capture.TypeName, $"__arg{p.Count}")); } return(p); } }