-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Diagnostic for Unsupported Version
Make Create follow same pattern
- Loading branch information
1 parent
d01a3a1
commit 73ba385
Showing
18 changed files
with
475 additions
and
280 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using Pretender.SourceGenerator.Parser; | ||
|
||
namespace Pretender.SourceGenerator | ||
{ | ||
internal class CreateInvocation | ||
{ | ||
public CreateInvocation(IInvocationOperation operation, ImmutableArray<ITypeSymbol>? typeArguments, InterceptsLocationInfo location) | ||
{ | ||
Operation = operation; | ||
TypeArguments = typeArguments; | ||
Location = location; | ||
} | ||
|
||
public IInvocationOperation Operation { get; } | ||
public ImmutableArray<ITypeSymbol>? TypeArguments { get; } | ||
public InterceptsLocationInfo Location { get; } | ||
|
||
public static bool IsCandidateSyntaxNode(SyntaxNode node) | ||
{ | ||
return node is InvocationExpressionSyntax | ||
{ | ||
Expression: MemberAccessExpressionSyntax | ||
{ | ||
Name.Identifier.ValueText: "Create" | ||
}, | ||
}; | ||
} | ||
|
||
public static CreateInvocation? Create(GeneratorSyntaxContext context, CancellationToken cancellationToken) | ||
{ | ||
Debug.Assert(IsCandidateSyntaxNode(context.Node)); | ||
return context.SemanticModel.GetOperation(context.Node, cancellationToken) is IInvocationOperation operation | ||
&& IsCreateOperation(operation, out var typeArguments) | ||
? new CreateInvocation(operation, typeArguments,new InterceptsLocationInfo(operation)) | ||
: null; | ||
} | ||
|
||
private static bool IsCreateOperation(IInvocationOperation operation, out ImmutableArray<ITypeSymbol>? typeArguments) | ||
{ | ||
typeArguments = null; | ||
if (operation.Instance is null | ||
|| operation.Instance.Type is not INamedTypeSymbol namedType | ||
|| !KnownTypeSymbols.IsPretend(namedType)) | ||
{ | ||
return false; | ||
} | ||
|
||
// Are they in the params overload? | ||
if (operation.TargetMethod.Parameters.Length == 1 | ||
&& operation.TargetMethod.Parameters[0].IsParams) | ||
{ | ||
return true; | ||
} | ||
|
||
typeArguments = operation.TargetMethod.TypeArguments; | ||
return true; | ||
} | ||
} | ||
|
||
public class CreateInvocationComparer : IEqualityComparer<CreateInvocation> | ||
{ | ||
public static CreateInvocationComparer Instance = new(); | ||
bool IEqualityComparer<CreateInvocation>.Equals(CreateInvocation x, CreateInvocation y) | ||
{ | ||
return SymbolEqualityComparer.Default.Equals(x.Operation.TargetMethod.ReturnType, y.Operation.TargetMethod.ReturnType) | ||
&& CompareTypeArguments(x.TypeArguments, y.TypeArguments); | ||
} | ||
|
||
private static bool CompareTypeArguments(ImmutableArray<ITypeSymbol>? x, ImmutableArray<ITypeSymbol>? y) | ||
{ | ||
if (!x.HasValue) | ||
{ | ||
return !y.HasValue; | ||
} | ||
|
||
if (!y.HasValue) | ||
{ | ||
// We've established x does have a value so y not having one is a non-match | ||
return false; | ||
} | ||
|
||
var xArray = x.Value; | ||
var yArray = y.Value; | ||
|
||
if (xArray.Length != yArray.Length) | ||
{ | ||
return false; | ||
} | ||
|
||
for (int i = 0; i < xArray.Length; i++) | ||
{ | ||
if (!SymbolEqualityComparer.IncludeNullability.Equals(xArray[i], yArray[i])) | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
int IEqualityComparer<CreateInvocation>.GetHashCode(CreateInvocation obj) | ||
{ | ||
unchecked | ||
{ | ||
var hash = 17; | ||
hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(obj.Operation.TargetMethod.ReturnType); | ||
|
||
if (!obj.TypeArguments.HasValue) | ||
{ | ||
// TODO: Is 5 an okay value? | ||
hash = hash * 31 + 5; | ||
} | ||
else | ||
{ | ||
var typeArguments = obj.TypeArguments.Value; | ||
foreach (var typeArgument in typeArguments) | ||
{ | ||
hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(typeArgument); | ||
} | ||
} | ||
|
||
return hash; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace Pretender.SourceGenerator.Emitter | ||
{ | ||
internal class CreateEmitter | ||
{ | ||
private readonly IInvocationOperation _originalOperation; | ||
private readonly ImmutableArray<ITypeSymbol>? _typeArguments; | ||
private readonly ImmutableArray<InterceptsLocationInfo> _locations; | ||
private readonly int _index; | ||
|
||
public CreateEmitter(IInvocationOperation originalOperation, ImmutableArray<ITypeSymbol>? typeArguments, ImmutableArray<InterceptsLocationInfo> locations, int index) | ||
{ | ||
_originalOperation = originalOperation; | ||
_typeArguments = typeArguments; | ||
_locations = locations; | ||
_index = index; | ||
} | ||
|
||
public IInvocationOperation Operation => _originalOperation; | ||
|
||
public MethodDeclarationSyntax Emit(CancellationToken cancellationToken) | ||
{ | ||
var returnType = _originalOperation.TargetMethod.ReturnType; | ||
|
||
var returnTypeSyntax = returnType.AsUnknownTypeSyntax(); | ||
|
||
TypeParameterSyntax[] typeParameters; | ||
ParameterSyntax[] methodParameters; | ||
ArgumentSyntax[] constructorArguments; | ||
|
||
if (_typeArguments.HasValue) | ||
{ | ||
typeParameters = new TypeParameterSyntax[_typeArguments.Value.Length]; | ||
|
||
// We always take the Pretend<T> argument first as a this parameter | ||
methodParameters = new ParameterSyntax[_typeArguments.Value.Length + 1]; | ||
constructorArguments = new ArgumentSyntax[_typeArguments.Value.Length + 1]; | ||
|
||
for (var i = 0; i < _typeArguments.Value.Length; i++) | ||
{ | ||
var typeName = $"T{i}"; | ||
var argName = $"arg{i}"; | ||
|
||
typeParameters[i] = TypeParameter(typeName); | ||
methodParameters[i + 1] = Parameter(Identifier(argName)) | ||
.WithType(ParseTypeName(typeName)); | ||
constructorArguments[i + 1] = Argument(IdentifierName(argName)); | ||
} | ||
} | ||
else | ||
{ | ||
typeParameters = []; | ||
methodParameters = new ParameterSyntax[1]; | ||
constructorArguments = new ArgumentSyntax[1]; | ||
} | ||
|
||
methodParameters[0] = Parameter(Identifier("pretend")) | ||
.WithType(GenericName("Pretend") | ||
.AddTypeArgumentListArguments(returnTypeSyntax)) | ||
.WithModifiers(TokenList(Token(SyntaxKind.ThisKeyword)) | ||
); | ||
|
||
constructorArguments[0] = Argument(IdentifierName("pretend")); | ||
|
||
var objectCreation = ObjectCreationExpression(ParseTypeName(returnType.ToPretendName())) | ||
.WithArgumentList(ArgumentList(SeparatedList(constructorArguments))); | ||
|
||
var method = MethodDeclaration(returnTypeSyntax, $"Create{_index}") | ||
.WithBody(Block(ReturnStatement(objectCreation))) | ||
.WithParameterList(ParameterList(SeparatedList(methodParameters))) | ||
.WithModifiers(TokenList(Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword))); | ||
|
||
method = method.WithAttributeLists(List(CreateInterceptsAttributes())); | ||
|
||
if (typeParameters.Length > 0) | ||
{ | ||
return method | ||
.WithTypeParameterList(TypeParameterList(SeparatedList(typeParameters))); | ||
} | ||
|
||
return method; | ||
} | ||
|
||
private ImmutableArray<AttributeListSyntax> CreateInterceptsAttributes() | ||
{ | ||
var builder = ImmutableArray.CreateBuilder<AttributeListSyntax>(_locations.Length); | ||
|
||
foreach (var location in _locations) | ||
{ | ||
var attribute = Create(location.ToAttributeSyntax()); | ||
builder.Add(attribute); | ||
} | ||
|
||
return builder.MoveToImmutable(); | ||
|
||
static AttributeListSyntax Create(AttributeSyntax attribute) | ||
{ | ||
return AttributeList(SingletonSeparatedList(attribute)); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Pretender.SourceGenerator.Emitter; | ||
using static Pretender.SourceGenerator.PretenderSourceGenerator; | ||
|
||
namespace Pretender.SourceGenerator.Parser | ||
{ | ||
internal class CreateParser | ||
{ | ||
private readonly CreateInvocation _createInvocation; | ||
private readonly ImmutableArray<InterceptsLocationInfo> _locations; | ||
private readonly int _index; | ||
private readonly bool _isLanguageVersionSupported; | ||
private readonly KnownTypeSymbols _knownTypeSymbols; | ||
|
||
public CreateParser(CreateInvocation createInvocation, ImmutableArray<InterceptsLocationInfo> locations, int index, CompilationData compilationData) | ||
{ | ||
_createInvocation = createInvocation; | ||
_locations = locations; | ||
_index = index; | ||
_isLanguageVersionSupported = compilationData.LanguageVersionIsSupported; | ||
_knownTypeSymbols = compilationData.TypeSymbols!; | ||
} | ||
|
||
public (CreateEmitter? Emitter, ImmutableArray<Diagnostic>? Diagnostics) Parse(CancellationToken cancellationToken) | ||
{ | ||
if (!_isLanguageVersionSupported) | ||
{ | ||
return (null, ImmutableArray.Create(Diagnostic.Create(DiagnosticDescriptors.UnsupportedLanguageVersion, null))); | ||
} | ||
|
||
// TODO:Do deeper introspection to make sure the supplied args match with supplied type arguments | ||
// and we should provide the constructor to use to the emitter maybe | ||
var emitter = new CreateEmitter( | ||
_createInvocation.Operation, | ||
_createInvocation.TypeArguments, | ||
_locations, | ||
_index); | ||
|
||
return (emitter, null); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.