private void Process(ComponentExtensionNode node) { // First collect all of the information we have about each type parameter // // Listing all type parameters that exist var bindings = new Dictionary <string, GenericTypeNameRewriter.Binding>(); foreach (var attribute in node.Component.GetTypeParameters()) { bindings.Add(attribute.Name, new GenericTypeNameRewriter.Binding() { Attribute = attribute, }); } // Listing all type arguments that have been specified. var hasTypeArgumentSpecified = false; foreach (var typeArgumentNode in node.TypeArguments) { hasTypeArgumentSpecified = true; var binding = bindings[typeArgumentNode.TypeParameterName]; binding.Node = typeArgumentNode; binding.Content = GetContent(typeArgumentNode); } if (hasTypeArgumentSpecified) { // OK this means that the developer has specified at least one type parameter. // Either they specified everything and its OK to rewrite, or its an error. if (ValidateTypeArguments(node, bindings)) { RewriteTypeNames(new GenericTypeNameRewriter(bindings), node); } return; } // OK if we get here that means that no type arguments were specified, so we will try to infer // the type. // // The actual inference is done by the C# compiler, we just emit an a method that represents the // use of this component. // Since we're generating code in a different namespace, we need to 'global qualify' all of the types // to avoid clashes with our generated code. RewriteTypeNames(new GlobalQualifiedTypeNameRewriter(bindings.Keys), node); // // We need to verify that an argument was provided that 'covers' each type parameter. // // For example, consider a repeater where the generic type is the 'item' type, but the developer has // not set the items. We won't be able to do type inference on this and so it will just be nonsense. var attributes = node.Attributes.Select(a => a.BoundAttribute).Concat(node.ChildContents.Select(c => c.BoundAttribute)); foreach (var attribute in attributes) { if (attribute == null) { // Will be null for attributes set on the component that don't match a declared component parameter continue; } // Now we need to parse the type name and extract the generic parameters. // // Two cases; // 1. name is a simple identifier like TItem // 2. name contains type parameters like Dictionary<string, TItem> if (!attribute.IsGenericTypedProperty()) { continue; } var parsed = SyntaxFactory.ParseTypeName(attribute.TypeName); if (parsed is IdentifierNameSyntax identifier) { bindings.Remove(identifier.ToString()); } else { var typeParameters = parsed.DescendantNodesAndSelf().OfType <TypeArgumentListSyntax>().SelectMany(arg => arg.Arguments); foreach (var typeParameter in typeParameters) { bindings.Remove(typeParameter.ToString()); } } } // If any bindings remain then this means we would never be able to infer the arguments of this // component usage because the user hasn't set properties that include all of the types. if (bindings.Count > 0) { // However we still want to generate 'type inference' code because we want the errors to be as // helpful as possible. So let's substitute 'object' for all of those type parameters, and add // an error. RewriteTypeNames(new GenericTypeNameRewriter(bindings), node); node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters())); } // Next we need to generate a type inference 'method' node. This represents a method that we will codegen that // contains all of the operations on the render tree building. Calling a method to operate on the builder // will allow the C# compiler to perform type inference. var documentNode = (DocumentIntermediateNode)Ancestors[Ancestors.Count - 1]; CreateTypeInferenceMethod(documentNode, node, bindings); }