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);
            }
        }
Ejemplo n.º 3
0
 public virtual void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node)
 {
     VisitDefault(node);
 }
Ejemplo n.º 4
0
 void IExtensionIntermediateNodeVisitor <ComponentTypeInferenceMethodIntermediateNode> .VisitExtension(ComponentTypeInferenceMethodIntermediateNode node)
 {
     WriteContentNode(node, node.FullTypeName, node.MethodName);
 }
Ejemplo n.º 5
0
 public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node)
 {
     Context.NodeWriter.WriteComponentTypeInferenceMethod(Context, node);
 }
Ejemplo n.º 6
0
 public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node)
 {
     WriteContentNode(node, node.FullTypeName, node.MethodName);
 }
Ejemplo n.º 7
0
 public virtual void WriteComponentTypeInferenceMethod(CodeRenderingContext context, ComponentTypeInferenceMethodIntermediateNode node)
 {
     throw new NotSupportedException("This writer does not support components.");
 }
Ejemplo n.º 8
0
        // 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("}");
            }
        }
Ejemplo n.º 9
0
        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);
        }