private IType GetTypeFromReferenced() { var root = Ancestors.OfType <RDomRoot>().FirstOrDefault(); if (root != null) { return(root.FindByMetadataName(MetadataName)); } return(null); }
private IDetailBlockStart FindBlockStart(Func <IDetailBlockStart, bool> predicate) { var parentContainers = Ancestors.OfType <IContainer>(); foreach (var container in parentContainers) { // TODO: I'm pretty sure you just need predicate, not the extra lambda, but want to complete testing before I check. var ret = container.GetMembers() .OfType <IDetailBlockStart>() .Where(x => predicate(x)) .SingleOrDefault(); if (ret != null) { return(ret); } } throw new InvalidOperationException("Matching start region not found"); }
public T GetParent <T>() where T : AstNode { return(Ancestors.OfType <T>().FirstOrDefault()); }
private void Process(ComponentIntermediateNode node) { // First collect all of the information we have about each type parameter // // Listing all type parameters that exist var bindings = new Dictionary <string, Binding>(); var componentTypeParameters = node.Component.GetTypeParameters().ToList(); var supplyCascadingTypeParameters = componentTypeParameters .Where(p => p.IsCascadingTypeParameterProperty()) .Select(p => p.Name) .ToList(); foreach (var attribute in componentTypeParameters) { bindings.Add(attribute.Name, new 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); // Offer this explicit type argument to descendants too if (supplyCascadingTypeParameters.Contains(typeArgumentNode.TypeParameterName)) { node.ProvidesCascadingGenericTypes ??= new(); node.ProvidesCascadingGenericTypes[typeArgumentNode.TypeParameterName] = new CascadingGenericTypeParameter { GenericTypeNames = new[] { typeArgumentNode.TypeParameterName }, ValueType = typeArgumentNode.TypeParameterName, ValueExpression = $"default({binding.Content})", }; } } 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)) { var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content); RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), 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(_pass.TypeNameFeature.CreateGlobalQualifiedTypeNameRewriter(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. foreach (var attribute in node.Attributes) { if (attribute != null && TryFindGenericTypeNames(attribute.BoundAttribute, out var typeParameters)) { var attributeValueIsLambda = _pass.TypeNameFeature.IsLambda(GetContent(attribute)); var provideCascadingGenericTypes = new CascadingGenericTypeParameter { GenericTypeNames = typeParameters, ValueType = attribute.BoundAttribute.TypeName, ValueSourceNode = attribute, }; foreach (var typeName in typeParameters) { if (supplyCascadingTypeParameters.Contains(typeName)) { // Advertise that this particular inferred generic type is available to descendants. // There might be multiple sources for each generic type, so pick the one that has the // fewest other generic types on it. For example if we could infer from either List<T> // or Dictionary<T, U>, we prefer List<T>. node.ProvidesCascadingGenericTypes ??= new(); if (!node.ProvidesCascadingGenericTypes.TryGetValue(typeName, out var existingValue) || existingValue.GenericTypeNames.Count > typeParameters.Count) { node.ProvidesCascadingGenericTypes[typeName] = provideCascadingGenericTypes; } } if (attributeValueIsLambda) { // For attributes whose values are lambdas, we don't know whether or not the value // covers the generic type - it depends on the content of the lambda. // For example, "() => 123" can cover Func<T>, but "() => null" cannot. So we'll // accept cascaded generic types from ancestors if they are compatible with the lambda, // hence we don't remove it from the list of uncovered generic types until after // we try matching against ancestor cascades. if (bindings.TryGetValue(typeName, out var binding)) { binding.CoveredByLambda = true; } } else { bindings.Remove(typeName); } } } } // For any remaining bindings, scan up the hierarchy of ancestor components and try to match them // with a cascaded generic parameter that can cover this one List <CascadingGenericTypeParameter> receivesCascadingGenericTypes = null; foreach (var uncoveredBindingKey in bindings.Keys.ToList()) { var uncoveredBinding = bindings[uncoveredBindingKey]; foreach (var candidateAncestor in Ancestors.OfType <ComponentIntermediateNode>()) { if (candidateAncestor.ProvidesCascadingGenericTypes != null && candidateAncestor.ProvidesCascadingGenericTypes.TryGetValue(uncoveredBindingKey, out var genericTypeProvider)) { // If the parameter value is an expression that includes multiple generic types, we only want // to use it if we want *all* those generic types. That is, a parameter of type MyType<T0, T1> // can supply types to a Child<T0, T1>, but not to a Child<T0>. // This is purely to avoid blowing up the complexity of the implementation here and could be // overcome in the future if we want. We'd need to figure out which extra types are unwanted, // and rewrite them to some unique name, and add that to the generic parameters list of the // inference methods. if (genericTypeProvider.GenericTypeNames.All(GenericTypeIsUsed)) { bindings.Remove(uncoveredBindingKey); receivesCascadingGenericTypes ??= new(); receivesCascadingGenericTypes.Add(genericTypeProvider); // It's sufficient to identify the closest provider for each type parameter break; } bool GenericTypeIsUsed(string typeName) => componentTypeParameters .Select(t => t.Name) .Contains(typeName, StringComparer.Ordinal); } } } // There are two remaining sources of possible generic type info which we consider // lower-priority than cascades from ancestors. Since these two sources *may* actually // resolve generic type ambiguities in some cases, we treat them as covering. // // [1] Attributes given as lambda expressions. These are lower priority than ancestor // cascades because in most cases, lambdas don't provide type info foreach (var entryToRemove in bindings.Where(e => e.Value.CoveredByLambda).ToList()) { // Treat this binding as covered, because it's possible that the lambda does provide // enough info for type inference to succeed. bindings.Remove(entryToRemove.Key); } // [2] Child content parameters, which are nearly always defined as untyped lambdas // (at least, that's what the Razor compiler produces), but can technically be // hardcoded as a RenderFragment<Something> and hence actually give type info. foreach (var attribute in node.ChildContents) { if (TryFindGenericTypeNames(attribute.BoundAttribute, out var typeParameters)) { foreach (var typeName in typeParameters) { bindings.Remove(typeName); } } } // 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. var mappings = bindings.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Content); RewriteTypeNames(_pass.TypeNameFeature.CreateGenericTypeRewriter(mappings), node); node.Diagnostics.Add(ComponentDiagnosticFactory.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, receivesCascadingGenericTypes); }