Skip to content
This repository was archived by the owner on Sep 26, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -96,4 +96,14 @@ public static string GetPropertyName(this BoundAttributeParameterDescriptorBuild

return null;
}

public static void SetGloballyQualifiedTypeName(this BoundAttributeDescriptorBuilder builder, string globallyQualifiedTypeName)
{
builder.Metadata[TagHelperMetadata.Common.GloballyQualifiedTypeName] = globallyQualifiedTypeName;
}

public static string GetGloballyQualifiedTypeName(this BoundAttributeDescriptor descriptor)
{
return descriptor?.Metadata[TagHelperMetadata.Common.GloballyQualifiedTypeName];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,15 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon
{
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
context.CodeWriter.Write("<");
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
var explicitType = (bool?)node.Annotations[ComponentMetadata.Component.ExplicitTypeNameKey];
if (explicitType == true)
{
context.CodeWriter.Write(node.TypeName);
}
else
{
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
}
context.CodeWriter.Write(">");
context.CodeWriter.Write("(");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,11 +337,11 @@ private void RewriteTypeNames(TypeNameRewriter rewriter, ComponentIntermediateNo

foreach (var attribute in node.Attributes)
{
string globallyQualifiedTypeName = null;
var globallyQualifiedTypeName = attribute.BoundAttribute.GetGloballyQualifiedTypeName();

if (attribute.TypeName != null)
{
globallyQualifiedTypeName = rewriter.Rewrite(attribute.TypeName);
globallyQualifiedTypeName = rewriter.Rewrite(globallyQualifiedTypeName ?? attribute.TypeName);
attribute.GloballyQualifiedTypeName = globallyQualifiedTypeName;
}

Expand All @@ -350,6 +350,18 @@ private void RewriteTypeNames(TypeNameRewriter rewriter, ComponentIntermediateNo
// If we know the type name, then replace any generic type parameter inside it with
// the known types.
attribute.TypeName = globallyQualifiedTypeName;
// This is a special case in which we are dealing with a property TItem.
// Given that TItem can have been defined explicitly by the user to a partially
// qualified type, (like MyType), we check if the globally qualified type name
// contains "global::" which will be the case in all cases as we've computed
// this information from the Roslyn symbol except for when the symbol is a generic
// type parameter. In which case, we mark it with an additional annotation to
// acount for that during code generation and avoid trying to fully qualify
// the type name.
if (!globallyQualifiedTypeName.StartsWith("global::", StringComparison.Ordinal))
{
attribute.Annotations.Add(ComponentMetadata.Component.ExplicitTypeNameKey, true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use this metadata to signal that we replaced something like TItem with potential user input, like MyType (which we can't qualify) to avoid prepending global:: incorrectly.

}
}
else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsDelegateProperty() ?? false))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public static class Component

public const string GenericTypedKey = "Components.GenericTyped";

public const string ExplicitTypeNameKey = "Components.ExplicitTypeName";

public const string TypeParameterKey = "Components.TypeParameter";

public const string TypeParameterIsCascadingKey = "Components.TypeParameterIsCascading";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ protected List<TypeInferenceMethodParameter> GetTypeInferenceMethodParameters(Co
typeName = attribute.TypeName;
if (attribute.BoundAttribute != null && !attribute.BoundAttribute.IsGenericTypedProperty())
{
typeName = "global::" + typeName;
typeName = typeName.StartsWith("global::", StringComparison.Ordinal) ? typeName : $"global::{typeName}";
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,15 @@ private void WriteComponentAttributeInnards(CodeRenderingContext context, Compon
{
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
context.CodeWriter.Write("<");
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
var explicitType = (bool?)node.Annotations[ComponentMetadata.Component.ExplicitTypeNameKey];
if (explicitType == true)
{
context.CodeWriter.Write(node.TypeName);
}
else
{
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
}
context.CodeWriter.Write(">");
context.CodeWriter.Write("(");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable
Expand All @@ -13,6 +13,8 @@ public static class Common

public static readonly string TypeName = "Common.TypeName";

public static readonly string GloballyQualifiedTypeName = "Common.GloballyQualifiedTypeName";

public static readonly string ClassifyAttributesOnly = "Common.ClassifyAttributesOnly";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3710,6 +3710,36 @@ public class MyComponent<TItem> : ComponentBase
CompileToAssembly(generated);
}

[Fact]
public void GenericComponent_NonPrimitiveType()
{
// Arrange
AdditionalSyntaxTrees.Add(Parse(@"
using Microsoft.AspNetCore.Components;

namespace Test
{
public class MyComponent<TItem> : ComponentBase
{
[Parameter] public TItem Item { get; set; }
}

public class CustomType
{
}
}
"));

// Act
var generated = CompileToCSharp(@"
<MyComponent TItem=""CustomType"" Item=""new CustomType()""/>");

// Assert
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
CompileToAssembly(generated);
}

[Fact]
public void ChildComponent_Generic_TypeInference()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Test
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
}
#pragma warning restore 219
#pragma warning disable 0414
private static System.Object __o = null;
#pragma warning restore 0414
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__o = typeof(
#nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
CustomType

#line default
#line hidden
#nullable disable
);
__o = global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<CustomType>(
#nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
new CustomType()

#line default
#line hidden
#nullable disable
);
__builder.AddAttribute(-1, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {
}
));
#nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
__o = typeof(global::Test.MyComponent<>);

#line default
#line hidden
#nullable disable
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Document -
NamespaceDeclaration - - Test
UsingDirective - (3:1,1 [12] ) - System
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
UsingDirective - (53:3,1 [17] ) - System.Linq
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
DesignTimeDirective -
CSharpCode -
IntermediateToken - - CSharp - #pragma warning disable 0414
CSharpCode -
IntermediateToken - - CSharp - private static System.Object __o = null;
CSharpCode -
IntermediateToken - - CSharp - #pragma warning restore 0414
MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [57] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentTypeArgument - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - TItem
LazyIntermediateToken - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CustomType
ComponentAttribute - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item - AttributeStructure.DoubleQuotes
LazyIntermediateToken - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - new CustomType()
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Source Location: (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|CustomType|
Generated Location: (915:25,20 [10] )
|CustomType|

Source Location: (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml)
|new CustomType()|
Generated Location: (1215:34,38 [16] )
|new CustomType()|

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// <auto-generated/>
#pragma warning disable 1591
namespace Test
{
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenComponent<global::Test.MyComponent<CustomType>>(0);
__builder.AddAttribute(1, "Item", global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<CustomType>(
#nullable restore
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
new CustomType()

#line default
#line hidden
#nullable disable
));
__builder.CloseComponent();
}
#pragma warning restore 1998
}
}
#pragma warning restore 1591
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Document -
NamespaceDeclaration - - Test
UsingDirective - (3:1,1 [14] ) - System
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
UsingDirective - (53:3,1 [19] ) - System.Linq
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
MethodDeclaration - - protected override - void - BuildRenderTree
Component - (0:0,0 [57] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
ComponentTypeArgument - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - TItem
LazyIntermediateToken - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CustomType
ComponentAttribute - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item - AttributeStructure.DoubleQuotes
LazyIntermediateToken - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - new CustomType()
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private void CreateProperty(TagHelperDescriptorBuilder builder, IPropertySymbol
pb.TypeName = property.Type.ToDisplayString(FullNameTypeDisplayFormat);
pb.SetPropertyName(property.Name);
pb.IsEditorRequired = property.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == "Microsoft.AspNetCore.Components.EditorRequiredAttribute");

pb.SetGloballyQualifiedTypeName(property.Type.ToDisplayString(GloballyQualifiedFullNameTypeDisplayFormat));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key is here, where we capture the globally qualified type name for the type. This comes from the symbol, and we re-use it later on.

if (kind == PropertyKind.Enum)
{
pb.IsEnum = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,21 @@ public override SyntaxNode VisitQualifiedName(QualifiedNameSyntax node)
node = (QualifiedNameSyntax)base.VisitQualifiedName(node);

// Rewriting these is complicated, best to just tostring and parse again.
return SyntaxFactory.ParseTypeName("global::" + node.ToString());
return SyntaxFactory.ParseTypeName(IsGloballyQualified(node) ? node.ToString() : "global::" + node.ToString());

static bool IsGloballyQualified(QualifiedNameSyntax node)
{
var candidate = node;
while (candidate != null)
{
if (candidate.Left is AliasQualifiedNameSyntax)
{
return true;
}
candidate = candidate.Left as QualifiedNameSyntax;
}
return false;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rewriter needs to account for the fact that the types might already be globally qualified

}

public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ public Task SetParametersAsync(ParameterView parameters)
// which is trivial. Verifying it once in detail and then ignoring it.
Assert.Collection(
attribute.Metadata.OrderBy(kvp => kvp.Key),
kvp => { Assert.Equal(TagHelperMetadata.Common.GloballyQualifiedTypeName, kvp.Key); Assert.Equal("global::System.String", kvp.Value); },
kvp => { Assert.Equal(TagHelperMetadata.Common.PropertyName, kvp.Key); Assert.Equal("MyProperty", kvp.Value); });
}

Expand Down