Skip to content

Commit

Permalink
Start work on property setter support
Browse files Browse the repository at this point in the history
  • Loading branch information
justindbaur committed Sep 25, 2023
1 parent eb9baae commit 72bdf60
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static bool IsSetupCall(this SyntaxNode node)
Expression: MemberAccessExpressionSyntax
{
// pretend.Setup(i => i.Something());
Name.Identifier.ValueText: "Setup",
Name.Identifier.ValueText: "Setup" or "SetupSet",
},
ArgumentList.Arguments.Count: 1
};
Expand Down
58 changes: 36 additions & 22 deletions src/Pretender.SourceGenerator/SetupEntrypoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ public SetupEntrypoint(IInvocationOperation invocationOperation)

var pretendType = ((INamedTypeSymbol)invocationOperation.Type!).TypeArguments[0];

var setupMethod = SimplifyOperation(setupExpressionArg.Value, pretendType);

PretendType = pretendType;

var setupMethod = SimplifyOperation(setupExpressionArg.Value);


if (setupMethod == default)
{
// Make sure one diagnostic at least is made about the failure to find
Expand Down Expand Up @@ -350,11 +351,11 @@ private bool ValidateInvocationOperation(IInvocationOperation operation)
return true;
}

private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyBlockOperation(IBlockOperation operation, ITypeSymbol pretendType)
private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyBlockOperation(IBlockOperation operation)
{
foreach (var childOperation in operation.Operations)
{
var method = SimplifyOperation(childOperation, pretendType);
var method = SimplifyOperation(childOperation);
if (method != default)
{
return method;
Expand All @@ -364,50 +365,63 @@ private bool ValidateInvocationOperation(IInvocationOperation operation)
return default;
}

private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyReturnOperation(IReturnOperation operation, ITypeSymbol pretendType)
private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyReturnOperation(IReturnOperation operation)
{
return operation.ReturnedValue switch
{
not null => SimplifyOperation(operation.ReturnedValue, pretendType),
not null => SimplifyOperation(operation.ReturnedValue),
// If there is not returned value, this is a dead end.
_ => default,
};
}

private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyOperation(IOperation operation, ITypeSymbol pretendType)
private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) SimplifyOperation(IOperation operation)
{
// TODO: Support more operations
return operation.Kind switch
{
OperationKind.Return => SimplifyReturnOperation((IReturnOperation)operation, pretendType),
OperationKind.Conversion => SimplifyOperation(((IConversionOperation)operation).Operand, pretendType),
OperationKind.Block => SimplifyBlockOperation((IBlockOperation)operation, pretendType),
OperationKind.AnonymousFunction => SimplifyOperation(((IAnonymousFunctionOperation)operation).Body, pretendType),
OperationKind.Invocation => TryMethod((IInvocationOperation)operation, pretendType),
OperationKind.Return => SimplifyReturnOperation((IReturnOperation)operation),
OperationKind.Conversion => SimplifyOperation(((IConversionOperation)operation).Operand),
OperationKind.Block => SimplifyBlockOperation((IBlockOperation)operation),
OperationKind.AnonymousFunction => SimplifyOperation(((IAnonymousFunctionOperation)operation).Body),
OperationKind.Invocation => TryMethod((IInvocationOperation)operation),
// ExpressionStatement is probably a dead path now but who cares
OperationKind.ExpressionStatement => SimplifyOperation(((IExpressionStatementOperation)operation).Operation, pretendType),
OperationKind.DelegateCreation => SimplifyOperation(((IDelegateCreationOperation)operation).Target, pretendType),
OperationKind.ExpressionStatement => SimplifyOperation(((IExpressionStatementOperation)operation).Operation),
OperationKind.DelegateCreation => SimplifyOperation(((IDelegateCreationOperation)operation).Target),
// TODO: Do something for SetupSet to get the set method instead
OperationKind.PropertyReference => TryProperty((IPropertyReferenceOperation)operation),
_ => default,
};
}

private static (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) TryProperty(IPropertyReferenceOperation propertyReference)
private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) TryProperty(IPropertyReferenceOperation propertyReference)
{
var getMethod = propertyReference.Property.GetMethod;
if (propertyReference.Instance == null)
{
return default;
}

if (!SymbolEqualityComparer.Default.Equals(propertyReference.Instance.Type, PretendType))
{
return default;
}

var method = OriginalInvocation.TargetMethod.Name == "SetupSet"
? propertyReference.Property.SetMethod
: propertyReference.Property.GetMethod;

// TODO: Validate the get method exists on the type we are pretending
// and that the return type it's returning is the expected one
if (getMethod != null)
if (method == null)
{
return (getMethod, []);
return default;
}

return default;
// I still don't return arguments for a property setter right?
return (method, []);
}

private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) TryMethod(IInvocationOperation operation, ITypeSymbol pretendType)
private (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Arguments) TryMethod(IInvocationOperation operation)
{
var instance = operation.Instance;
var method = operation.TargetMethod;
Expand All @@ -421,14 +435,14 @@ private static (IMethodSymbol Method, ImmutableArray<IArgumentOperation> Argumen

if (instance is IParameterReferenceOperation parameter)
{
if (!SymbolEqualityComparer.Default.Equals(parameter.Type, pretendType))
if (!SymbolEqualityComparer.Default.Equals(parameter.Type, PretendType))
{
return default;
}
}
// TODO: Should we allow any other instance operation types?

var pretendMethods = pretendType.GetMembers()
var pretendMethods = PretendType.GetMembers()
.OfType<IMethodSymbol>();

if (!pretendMethods.Contains(method, SymbolEqualityComparer.Default))
Expand Down
5 changes: 5 additions & 0 deletions src/Pretender/Pretend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ public IPretendSetup<T, TReturn> Setup<TReturn>(Func<T, TReturn> setupExpression
throw new InvalidProgramException("This method should have been intercepted via a source generator.");
}

public IPretendSetup<T> SetupSet<TReturn>(Func<T, TReturn> setupExpression)
{
throw new InvalidProgramException("This method should have been intercepted via a source generator.");
}

public IPretendSetup<T> Setup(Action<T> setupExpression)
{
throw new InvalidProgramException("This method should have been intercepted via a source generator.");
Expand Down
10 changes: 1 addition & 9 deletions test/SourceGeneratorTests/MainTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,8 @@ public async Task Test1()
var (result, compilation) = await RunGeneratorAsync($$"""
var pretendSimpleInterface = Pretend.For<ISimpleInterface>();
var local = "1";
var local2 = 2;
pretendSimpleInterface
.Setup(i => i.Foo(local, It.Is<int>(i =>
{
var myValue = 0;
return i < local2 + myValue;
})))
.Returns("4");
.SetupSet(i => i.Bar);
var simpleInterface = pretendSimpleInterface.Create();
""");
Expand Down

0 comments on commit 72bdf60

Please sign in to comment.