private void CreateTypeInferenceMethod(DocumentIntermediateNode documentNode, ComponentIntermediateNode node) { var @namespace = documentNode.FindPrimaryNamespace().Content; @namespace = string.IsNullOrEmpty(@namespace) ? "__Blazor" : "__Blazor." + @namespace; @namespace += "." + documentNode.FindPrimaryClass().ClassName; var typeInferenceNode = new ComponentTypeInferenceMethodIntermediateNode() { Component = node, // Method name is generated and guaranteed not to collide, since it's unique for each // component call site. MethodName = $"Create{CSharpIdentifier.SanitizeIdentifier(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>(ComponentMetadata.Component.GenericTypedKey, bool.TrueString))) .FirstOrDefault(); if (namespaceNode == null) { namespaceNode = new NamespaceDeclarationIntermediateNode() { Annotations = { { ComponentMetadata.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 override 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(ComponentsApi.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(ComponentsApi.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", ComponentsApi.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", ComponentsApi.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 ? ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture : ComponentsApi.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", ComponentsApi.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) { var typeName = attribute.TypeName; if (attribute.BoundAttribute != null && !attribute.BoundAttribute.IsGenericTypedProperty()) { typeName = "global::" + typeName; } p.Add(($"__seq{p.Count}", typeName, $"__arg{p.Count}")); } foreach (var childContent in node.Component.ChildContents) { var typeName = childContent.TypeName; if (childContent.BoundAttribute != null && !childContent.BoundAttribute.IsGenericTypedProperty()) { typeName = "global::" + typeName; } p.Add(($"__seq{p.Count}", typeName, $"__arg{p.Count}")); } foreach (var capture in node.Component.Captures) { // The capture type name should already contain the global:: prefix. p.Add(($"__seq{p.Count}", capture.TypeName, $"__arg{p.Count}")); } return(p); } }
public virtual void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node) { VisitDefault(node); }
void IExtensionIntermediateNodeVisitor <ComponentTypeInferenceMethodIntermediateNode> .VisitExtension(ComponentTypeInferenceMethodIntermediateNode node) { WriteContentNode(node, node.FullTypeName, node.MethodName); }
public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node) { Context.NodeWriter.WriteComponentTypeInferenceMethod(Context, node); }
public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node) { WriteContentNode(node, node.FullTypeName, node.MethodName); }
public virtual void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node) { throw new NotSupportedException("This writer does not support components."); }
// Currently the same for design time and runtime public override void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } var parameters = GetTypeInferenceMethodParameters(node); // 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(ComponentsApi.RenderTreeBuilder.FullTypeName); writer.Write(" "); writer.Write(ComponentsApi.RenderTreeBuilder.BuilderParameter); writer.Write(", "); writer.Write("int seq"); if (parameters.Count > 0) { writer.Write(", "); } for (var i = 0; i < parameters.Count; i++) { if (!string.IsNullOrEmpty(parameters[i].SeqName)) { 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(ComponentsApi.RenderTreeBuilder.BuilderParameter); context.CodeWriter.Write("."); context.CodeWriter.Write(ComponentsApi.RenderTreeBuilder.OpenComponent); context.CodeWriter.Write("<"); context.CodeWriter.Write(node.Component.TypeName); context.CodeWriter.Write(">("); context.CodeWriter.Write("seq"); context.CodeWriter.Write(");"); context.CodeWriter.WriteLine(); foreach (var parameter in parameters) { switch (parameter.Source) { case ComponentAttributeIntermediateNode attribute: context.CodeWriter.WriteStartInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, ComponentsApi.RenderTreeBuilder.AddAttribute); context.CodeWriter.Write(parameter.SeqName); context.CodeWriter.Write(", "); context.CodeWriter.Write($"\"{attribute.AttributeName}\""); context.CodeWriter.Write(", "); context.CodeWriter.Write(parameter.ParameterName); context.CodeWriter.WriteEndMethodInvocation(); break; case SplatIntermediateNode: context.CodeWriter.WriteStartInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, ComponentsApi.RenderTreeBuilder.AddMultipleAttributes); context.CodeWriter.Write(parameter.SeqName); context.CodeWriter.Write(", "); context.CodeWriter.Write(parameter.ParameterName); context.CodeWriter.WriteEndMethodInvocation(); break; case ComponentChildContentIntermediateNode childContent: context.CodeWriter.WriteStartInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, ComponentsApi.RenderTreeBuilder.AddAttribute); context.CodeWriter.Write(parameter.SeqName); context.CodeWriter.Write(", "); context.CodeWriter.Write($"\"{childContent.AttributeName}\""); context.CodeWriter.Write(", "); context.CodeWriter.Write(parameter.ParameterName); context.CodeWriter.WriteEndMethodInvocation(); break; case SetKeyIntermediateNode: context.CodeWriter.WriteStartInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, ComponentsApi.RenderTreeBuilder.SetKey); context.CodeWriter.Write(parameter.ParameterName); context.CodeWriter.WriteEndMethodInvocation(); break; case ReferenceCaptureIntermediateNode capture: context.CodeWriter.WriteStartInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, capture.IsComponentCapture ? ComponentsApi.RenderTreeBuilder.AddComponentReferenceCapture : ComponentsApi.RenderTreeBuilder.AddElementReferenceCapture); context.CodeWriter.Write(parameter.SeqName); context.CodeWriter.Write(", "); var cast = capture.IsComponentCapture ? $"({capture.ComponentCaptureTypeName})" : string.Empty; context.CodeWriter.Write($"(__value) => {{ {parameter.ParameterName}({cast}__value); }}"); context.CodeWriter.WriteEndMethodInvocation(); break; case CascadingGenericTypeParameter: // We only use the synthetic cascading parameters for type inference break; default: throw new InvalidOperationException($"Not implemented: type inference method parameter from source {parameter.Source}"); } } context.CodeWriter.WriteInstanceMethodInvocation(ComponentsApi.RenderTreeBuilder.BuilderParameter, ComponentsApi.RenderTreeBuilder.CloseComponent); writer.WriteLine("}"); if (node.Component.Component.SuppliesCascadingGenericParameters()) { // If this component cascades any generic parameters, we'll need to be able to capture its type inference // args at the call site. The point of this is to ensure that: // // [1] We only evaluate each expression once // [2] We evaluate them in the correct order matching the developer's source // [3] We can even make variables for lambdas or other expressions that can't just be assigned to implicitly-typed vars. // // We do that by emitting a method like the following. It has exactly the same generic type inference // characteristics as the regular CreateFoo_0 method emitted earlier // // public static void CreateFoo_0_CaptureParameters<T1, T2>(T1 __arg0, out T1 __arg0_out, global::System.Collections.Generic.List<T2> __arg1, out global::System.Collections.Generic.List<T2> __arg1_out, int __seq2, string __arg2, out string __arg2_out) // { // __arg0_out = __arg0; // __arg1_out = __arg1; // __arg2_out = __arg2; // } // writer.WriteLine(); writer.Write("public static void "); writer.Write(node.MethodName); writer.Write("_CaptureParameters<"); writer.Write(string.Join(", ", node.Component.Component.GetTypeParameters().Select(a => a.Name))); writer.Write(">"); writer.Write("("); var isFirst = true; foreach (var parameter in parameters.Where(p => p.UsedForTypeInference)) { if (isFirst) { isFirst = false; } else { writer.Write(", "); } writer.Write(parameter.TypeName); writer.Write(" "); writer.Write(parameter.ParameterName); writer.Write(", out "); writer.Write(parameter.TypeName); writer.Write(" "); writer.Write(parameter.ParameterName); writer.Write("_out"); } writer.WriteLine(")"); writer.WriteLine("{"); foreach (var parameter in parameters.Where(p => p.UsedForTypeInference)) { writer.Write(" "); writer.Write(parameter.ParameterName); writer.Write("_out = "); writer.Write(parameter.ParameterName); writer.WriteLine(";"); } writer.WriteLine("}"); } }
protected List <TypeInferenceMethodParameter> GetTypeInferenceMethodParameters(ComponentTypeInferenceMethodIntermediateNode node) { var p = new List <TypeInferenceMethodParameter>(); // Preserve order between attributes and splats foreach (var child in node.Component.Children) { if (child is ComponentAttributeIntermediateNode attribute) { string typeName; if (attribute.GloballyQualifiedTypeName != null) { typeName = attribute.GloballyQualifiedTypeName; } else { typeName = attribute.TypeName; if (attribute.BoundAttribute != null && !attribute.BoundAttribute.IsGenericTypedProperty()) { typeName = "global::" + typeName; } } p.Add(new TypeInferenceMethodParameter($"__seq{p.Count}", typeName, $"__arg{p.Count}", usedForTypeInference: true, attribute)); } else if (child is SplatIntermediateNode splat) { var typeName = ComponentsApi.AddMultipleAttributesTypeFullName; p.Add(new TypeInferenceMethodParameter($"__seq{p.Count}", typeName, $"__arg{p.Count}", usedForTypeInference: false, splat)); } } foreach (var childContent in node.Component.ChildContents) { var typeName = childContent.TypeName; if (childContent.BoundAttribute != null && !childContent.BoundAttribute.IsGenericTypedProperty()) { typeName = "global::" + typeName; } p.Add(new TypeInferenceMethodParameter($"__seq{p.Count}", typeName, $"__arg{p.Count}", usedForTypeInference: false, childContent)); } foreach (var capture in node.Component.SetKeys) { p.Add(new TypeInferenceMethodParameter($"__seq{p.Count}", "object", $"__arg{p.Count}", usedForTypeInference: false, capture)); } foreach (var capture in node.Component.Captures) { // The capture type name should already contain the global:: prefix. p.Add(new TypeInferenceMethodParameter($"__seq{p.Count}", capture.TypeName, $"__arg{p.Count}", usedForTypeInference: false, capture)); } // Insert synthetic args for cascaded type inference at the start of the list // We do this last so that the indices above aren't affected if (node.ReceivesCascadingGenericTypes != null) { var i = 0; foreach (var cascadingGenericType in node.ReceivesCascadingGenericTypes) { p.Insert(i, new TypeInferenceMethodParameter(null, cascadingGenericType.ValueType, $"__syntheticArg{i}", usedForTypeInference: true, cascadingGenericType)); i++; } } return(p); }