Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b87a9f4
Begin adding test for type parameter cascading
SteveSandersonMS Jan 28, 2021
0377b32
Cascade explicit ancestor type args
SteveSandersonMS Jan 29, 2021
e3d57e1
Switch to a push model for cascading generic types
SteveSandersonMS Jan 29, 2021
78fc1da
Refactoring to simplify next change
SteveSandersonMS Jan 29, 2021
8b8898b
Further refactoring to simplify next change
SteveSandersonMS Jan 29, 2021
46b0113
Make inferred generic types actually cascade.
SteveSandersonMS Jan 29, 2021
d371491
Minor refactoring (name change etc.)
SteveSandersonMS Jan 29, 2021
8a20d8c
Avoid multiple evaluations of the expression used for generic type in…
SteveSandersonMS Jan 29, 2021
fcebf92
Make cascaded type inference work with generic child content
SteveSandersonMS Jan 29, 2021
bac0a64
Handle lambdas on both provider and receiver sides
SteveSandersonMS Jan 29, 2021
219f78a
Handle inference of multiple generic parameters without duplicate eva…
SteveSandersonMS Jan 29, 2021
d035c9c
Update design time to match previous
SteveSandersonMS Jan 29, 2021
23004f8
Steps towards handling further cases
SteveSandersonMS Feb 1, 2021
e0d0ec5
Refactoring that will simplify subsequent updates
SteveSandersonMS Feb 1, 2021
db33c8f
Revert some changes I no longer want
SteveSandersonMS Feb 1, 2021
0edc442
Use the refactorings to simplify and ensure consistency about ordering
SteveSandersonMS Feb 1, 2021
2c91775
Emit _CaptureParameters variant of type inference method
SteveSandersonMS Feb 1, 2021
fb3e773
Call the _CaptureParameters method to ensure single evaluation
SteveSandersonMS Feb 1, 2021
c0cd58f
Supply captured variables to descendants for cascading generic type i…
SteveSandersonMS Feb 1, 2021
6ac9f1c
Update new baselines
SteveSandersonMS Feb 1, 2021
875f009
Update comments
SteveSandersonMS Feb 1, 2021
2be20d0
Step towards being able to handle unrelated generic types
SteveSandersonMS Feb 1, 2021
e698841
Handle provision of unrelated generic types via diagnostic
SteveSandersonMS Feb 1, 2021
105dbb4
Remove obsolete comment
SteveSandersonMS Feb 1, 2021
fa6b428
Begin filtering which type params cascade
SteveSandersonMS Feb 1, 2021
980c987
Now actually filter provided cascading generic types
SteveSandersonMS Feb 1, 2021
ee71f8a
Eliminate unnecessary parameter capturing for non-cascading-components
SteveSandersonMS Feb 1, 2021
24de67f
Update baselines
SteveSandersonMS Feb 1, 2021
fb6d02a
More test cases
SteveSandersonMS Feb 1, 2021
5feebaa
Show we can have siblings
SteveSandersonMS Feb 1, 2021
9b49599
Show type cascading can pass through multiple levels
SteveSandersonMS Feb 1, 2021
1de1e1d
Clarify how we're retaining back-compat in a very obscure case
SteveSandersonMS Feb 1, 2021
8c362ef
Better comments
SteveSandersonMS Feb 1, 2021
c897740
Clean up APIs
SteveSandersonMS Feb 1, 2021
46466d5
CR: Null check
SteveSandersonMS Feb 3, 2021
59df23c
Rename attribute for consistency with other framework terminology
SteveSandersonMS Feb 3, 2021
e46c9d8
Add CascadingTypeParameterAttribute
SteveSandersonMS Feb 3, 2021
78d7097
Update tests to match new attribute name
SteveSandersonMS Feb 5, 2021
99830e6
CR: More tests and simplification
SteveSandersonMS Feb 16, 2021
70fd168
CR: Rename
SteveSandersonMS Feb 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Microsoft.AspNetCore.Components
{
/// <summary>
/// Denotes the generic type parameter as cascading. This allows generic type inference
/// to use this type parameter value automatically on descendants that also have a type
/// parameter with the same name.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public sealed class CascadingTypeParameterAttribute : Attribute
{
/// <summary>
/// Constructs an instance of <see cref="CascadingTypeParameterAttribute"/>.
/// </summary>
/// <param name="name">The name of the type parameter.</param>
public CascadingTypeParameterAttribute(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}

Name = name;
}

/// <summary>
/// Gets the name of the type parameter.
/// </summary>
public string Name { get; }
}
}
3 changes: 3 additions & 0 deletions src/Components/Components/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ Microsoft.AspNetCore.Components.DynamicComponent.Parameters.set -> void
Microsoft.AspNetCore.Components.DynamicComponent.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Components.DynamicComponent.Type.get -> System.Type!
Microsoft.AspNetCore.Components.DynamicComponent.Type.set -> void
Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute
Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute.CascadingTypeParameterAttribute(string! name) -> void
Microsoft.AspNetCore.Components.CascadingTypeParameterAttribute.Name.get -> string!
static Microsoft.AspNetCore.Components.ParameterView.FromDictionary(System.Collections.Generic.IDictionary<string!, object?>! parameters) -> Microsoft.AspNetCore.Components.ParameterView
virtual Microsoft.AspNetCore.Components.RenderTree.Renderer.DispatchEventAsync(ulong eventHandlerId, Microsoft.AspNetCore.Components.RenderTree.EventFieldInfo? fieldInfo, System.EventArgs! eventArgs) -> System.Threading.Tasks.Task!
Original file line number Diff line number Diff line change
Expand Up @@ -418,22 +418,49 @@ public override void WriteComponent(CodeRenderingContext context, ComponentInter
}
else
{
var parameters = GetTypeInferenceMethodParameters(node.TypeInferenceNode);

// If this component is going to cascade any of its generic types, we have to split its type inference
// into two parts. First we call an inference method that captures all the parameters in local variables,
// then we use those to call the real type inference method that emits the component. The reason for this
// is so the captured variables can be used by descendants without re-evaluating the expressions.
CodeWriterExtensions.CSharpCodeWritingScope? typeInferenceCaptureScope = null;
if (node.Component.SuppliesCascadingGenericParameters())
{
typeInferenceCaptureScope = context.CodeWriter.BuildScope();
context.CodeWriter.Write(node.TypeInferenceNode.FullTypeName);
context.CodeWriter.Write(".");
context.CodeWriter.Write(node.TypeInferenceNode.MethodName);
context.CodeWriter.Write("_CaptureParameters(");
var isFirst = true;
foreach (var parameter in parameters.Where(p => p.UsedForTypeInference))
{
if (isFirst)
{
isFirst = false;
}
else
{
context.CodeWriter.Write(", ");
}

WriteTypeInferenceMethodParameterInnards(context, parameter);
context.CodeWriter.Write(", out var ");

var variableName = $"__typeInferenceArg_{_scopeStack.Depth}_{parameter.ParameterName}";
context.CodeWriter.Write(variableName);

UseCapturedCascadingGenericParameterVariable(node, parameter, variableName);
}
context.CodeWriter.WriteLine(");");
}

// When we're doing type inference, we can't write all of the code inline to initialize
// the component on the builder. We generate a method elsewhere, and then pass all of the information
// to that method. We pass in all of the attribute values + the sequence numbers.
//
// __Blazor.MyComponent.TypeInference.CreateMyComponent_0(__builder, 0, 1, ..., 2, ..., 3, ....);

// Preserve order of attributes + splats
var attributes = node.Children.Where(s =>
{
return s is ComponentAttributeIntermediateNode || s is SplatIntermediateNode;
}).ToList();
var childContents = node.ChildContents.ToList();
var captures = node.Captures.ToList();
var setKeys = node.SetKeys.ToList();
var remaining = attributes.Count + childContents.Count + captures.Count + setKeys.Count;

context.CodeWriter.Write(node.TypeInferenceNode.FullTypeName);
context.CodeWriter.Write(".");
context.CodeWriter.Write(node.TypeInferenceNode.MethodName);
Expand All @@ -443,76 +470,27 @@ public override void WriteComponent(CodeRenderingContext context, ComponentInter
context.CodeWriter.Write(", ");

context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");

for (var i = 0; i < attributes.Count; i++)
foreach (var parameter in parameters)
{
context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");

// Don't type check generics, since we can't actually write the type name.
// The type checking with happen anyway since we defined a method and we're generating
// a call to it.
if (attributes[i] is ComponentAttributeIntermediateNode attribute)
{
WriteComponentAttributeInnards(context, attribute, canTypeCheck: false);
}
else if (attributes[i] is SplatIntermediateNode splat)
{
WriteSplatInnards(context, splat, canTypeCheck: false);
}

remaining--;
if (remaining > 0)
if (!string.IsNullOrEmpty(parameter.SeqName))
{
context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");
}
}

for (var i = 0; i < childContents.Count; i++)
{
context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");

WriteComponentChildContentInnards(context, childContents[i]);

remaining--;
if (remaining > 0)
{
context.CodeWriter.Write(", ");
}
WriteTypeInferenceMethodParameterInnards(context, parameter);
}

for (var i = 0; i < setKeys.Count; i++)
{
context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");

WriteSetKeyInnards(context, setKeys[i]);

remaining--;
if (remaining > 0)
{
context.CodeWriter.Write(", ");
}
}
context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();

for (var i = 0; i < captures.Count; i++)
if (typeInferenceCaptureScope.HasValue)
{
context.CodeWriter.Write("-1");
context.CodeWriter.Write(", ");

WriteReferenceCaptureInnards(context, captures[i], shouldTypeCheck: false);

remaining--;
if (remaining > 0)
{
context.CodeWriter.Write(", ");
}
typeInferenceCaptureScope.Value.Dispose();
}

context.CodeWriter.Write(");");
context.CodeWriter.WriteLine();
}

// We want to generate something that references the Component type to avoid
Expand Down Expand Up @@ -540,6 +518,42 @@ public override void WriteComponent(CodeRenderingContext context, ComponentInter
}
}

private void WriteTypeInferenceMethodParameterInnards(CodeRenderingContext context, TypeInferenceMethodParameter parameter)
{
switch (parameter.Source)
{
case ComponentAttributeIntermediateNode attribute:
// Don't type check generics, since we can't actually write the type name.
// The type checking with happen anyway since we defined a method and we're generating
// a call to it.
WriteComponentAttributeInnards(context, attribute, canTypeCheck: false);
break;
case SplatIntermediateNode splat:
WriteSplatInnards(context, splat, canTypeCheck: false);
break;
case ComponentChildContentIntermediateNode childNode:
WriteComponentChildContentInnards(context, childNode);
break;
case SetKeyIntermediateNode setKey:
WriteSetKeyInnards(context, setKey);
break;
case ReferenceCaptureIntermediateNode capture:
WriteReferenceCaptureInnards(context, capture, shouldTypeCheck: false);
break;
case CascadingGenericTypeParameter syntheticArg:
// The value should be populated before we use it, because we emit code for creating ancestors
// first, and that's where it's populated. However if this goes wrong somehow, we don't want to
// throw, so use a fallback
context.CodeWriter.Write(syntheticArg.ValueExpression ?? "default");
break;
case TypeInferenceCapturedVariable capturedVariable:
context.CodeWriter.Write(capturedVariable.VariableName);
break;
default:
throw new InvalidOperationException($"Not implemented: type inference method parameter from source {parameter.Source}");
}
}

public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeIntermediateNode node)
{
if (context == null)
Expand Down
Loading