Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Public API support for function pointer types #44436

Merged
merged 13 commits into from
May 28, 2020
13 changes: 13 additions & 0 deletions eng/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ usage()
echo "Test actions:"
echo " --testCoreClr Run unit tests on .NET Core (short: --test, -t)"
echo " --testMono Run unit tests on Mono"
echo " --testIOperation Run unit tests with the IOperation test hook"
echo ""
echo "Advanced settings:"
echo " --ci Building in CI"
Expand Down Expand Up @@ -58,6 +59,7 @@ pack=false
publish=false
test_core_clr=false
test_mono=false
test_ioperation=false

configuration="Debug"
verbosity='minimal'
Expand Down Expand Up @@ -121,6 +123,9 @@ while [[ $# > 0 ]]; do
--testmono)
test_mono=true
;;
--testioperation)
test_ioperation=true
;;
--ci)
ci=true
;;
Expand Down Expand Up @@ -235,6 +240,14 @@ function BuildSolution {
disable_parallel_restore=true
fi

if [[ "$test_ioperation" == true ]]; then
export ROSLYN_TEST_IOPERATION="true"

if [[ "$test_mono" != true && "$test_core_clr" != true ]]; then
test_core_clr=true
333fred marked this conversation as resolved.
Show resolved Hide resolved
fi
fi

local test=false
local test_runtime=""
local mono_tool=""
Expand Down
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1690,6 +1690,7 @@ private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode
boundExpression,
analyzedArguments.Arguments.SelectAsArray((expr, args) => args.binder.BindToNaturalType(expr, args.diagnostics), (binder: this, diagnostics)),
analyzedArguments.RefKinds.ToImmutableOrNull(),
LookupResultKind.OverloadResolutionFailure,
funcPtr.Signature.ReturnType,
hasErrors: true);
}
Expand Down Expand Up @@ -1724,6 +1725,7 @@ private BoundFunctionPointerInvocation BindFunctionPointerInvocation(SyntaxNode
boundExpression,
args,
refKinds,
LookupResultKind.Viable,
funcPtr.Signature.ReturnType,
hasErrors);
}
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@
<Field Name="InvokedExpression" Type="BoundExpression"/>
<Field Name="Arguments" Type="ImmutableArray&lt;BoundExpression&gt;"/>
<Field Name="ArgumentRefKindsOpt" Type="ImmutableArray&lt;RefKind&gt;" Null="allow"/>
<Field Name="ResultKind" PropertyOverrides="true" Type="LookupResultKind" />
</Node>

<Node Name="BoundRefTypeOperator" Base="BoundExpression">
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,10 @@ internal partial class BoundIndexOrRangePatternIndexerAccess
{
protected override ImmutableArray<BoundNode?> Children => ImmutableArray.Create<BoundNode?>(Receiver, Argument);
}

internal partial class BoundFunctionPointerInvocation : IBoundInvalidNode
{
ImmutableArray<BoundNode> IBoundInvalidNode.InvalidNodeChildren => CSharpOperationFactory.CreateInvalidChildrenFromArgumentsExpression(receiverOpt: this.InvokedExpression, Arguments);
protected override ImmutableArray<BoundNode?> Children => StaticCast<BoundNode?>.From(((IBoundInvalidNode)this).InvalidNodeChildren);
}
}
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -6154,4 +6154,10 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_UnsupportedCallingConvention" xml:space="preserve">
<value>The calling convention of '{0}' is not supported by the language.</value>
</data>
<data name="NotSameNumberParameterTypesAndRefKinds" xml:space="preserve">
<value>Given {0} parameter types and {1} parameter ref kinds. These arrays must have the same length.</value>
</data>
<data name="OutIsNotValidForReturn" xml:space="preserve">
<value>'RefKind.Out' is not a valid ref kind for a return type.</value>
</data>
</root>
48 changes: 48 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3163,6 +3163,54 @@ protected override IPointerTypeSymbol CommonCreatePointerTypeSymbol(ITypeSymbol
return CreatePointerTypeSymbol(elementType.EnsureCSharpSymbolOrNull(nameof(elementType)), elementType.NullableAnnotation.ToInternalAnnotation()).GetPublicSymbol();
}

protected override IFunctionPointerTypeSymbol CommonCreateFunctionPointerTypeSymbol(
ITypeSymbol returnType,
RefKind returnRefKind,
ImmutableArray<ITypeSymbol> parameterTypes,
ImmutableArray<RefKind> parameterRefKinds)
{
if (returnType is null)
{
throw new ArgumentNullException(nameof(returnType));
}

if (parameterTypes.IsDefault)
{
throw new ArgumentNullException(nameof(parameterTypes));
}

for (int i = 0; i < parameterTypes.Length; i++)
{
if (parameterTypes[i] is null)
{
throw new ArgumentNullException($"{nameof(parameterTypes)}[{i}]");
}
}

if (parameterRefKinds.IsDefault)
{
throw new ArgumentNullException(nameof(parameterRefKinds));
}

if (parameterRefKinds.Length != parameterTypes.Length)
{
// Given {0} parameter types and {1} parameter ref kinds. These must be the same.
throw new ArgumentException(string.Format(CSharpResources.NotSameNumberParameterTypesAndRefKinds, parameterTypes.Length, parameterRefKinds.Length));
}

if (returnRefKind == RefKind.Out)
{
//'RefKind.Out' is not a valid ref kind for a return type.
throw new ArgumentException(CSharpResources.OutIsNotValidForReturn);
}

var returnTypeWithAnnotations = TypeWithAnnotations.Create(returnType.EnsureCSharpSymbolOrNull(nameof(returnType)), returnType.NullableAnnotation.ToInternalAnnotation());
var parameterTypesWithAnnotations = parameterTypes.SelectAsArray(
type => TypeWithAnnotations.Create(type.EnsureCSharpSymbolOrNull(nameof(parameterTypes)), type.NullableAnnotation.ToInternalAnnotation()));

return FunctionPointerTypeSymbol.CreateFromParts(returnTypeWithAnnotations, returnRefKind, parameterTypesWithAnnotations, parameterRefKinds, this).GetPublicSymbol();
}

protected override INamedTypeSymbol CommonCreateNativeIntegerTypeSymbol(bool signed)
{
return CreateNativeIntegerTypeSymbol(signed).GetPublicSymbol();
Expand Down
33 changes: 33 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2104,6 +2104,17 @@ internal CSharpTypeInfo GetTypeInfoForNode(
break;
}
}
else if (boundExpr is BoundConversion { ConversionKind: ConversionKind.MethodGroup, Conversion: var exprConversion, Type: { TypeKind: TypeKind.FunctionPointer }, SymbolOpt: var symbol })
{
// Because the method group is a separate syntax node from the &, the lowest bound node here is the BoundConversion. However,
// the conversion represents an implicit method group conversion from a typeless method group to a function pointer type, so
// we should reflect that in the types and conversion we return.
convertedType = type;
convertedNullability = nullability;
conversion = exprConversion;
type = null;
nullability = new NullabilityInfo(CodeAnalysis.NullableAnnotation.NotAnnotated, CodeAnalysis.NullableFlowState.NotNull);
}
else
{
convertedType = type;
Expand Down Expand Up @@ -3223,6 +3234,28 @@ private ImmutableArray<Symbol> GetSemanticSymbols(
}
break;

case BoundKind.FunctionPointerInvocation:
{
var invocation = (BoundFunctionPointerInvocation)boundNode;
symbols = ImmutableArray.Create<Symbol>(invocation.FunctionPointer);
resultKind = invocation.ResultKind;
break;
}

case BoundKind.UnconvertedAddressOfOperator:
{
// We try to match the results given for a similar piece of syntax here: bad invocations.
// A BoundUnconvertedAddressOfOperator represents this syntax: &M
// Similarly, a BoundCall for a bad invocation represents this syntax: M(args)
// Calling GetSymbolInfo on the syntax will return an array of candidate symbols that were
// looked up, but calling GetMemberGroup will return an empty array. So, we ignore the member
// group result in the call below.
symbols = GetMethodGroupSemanticSymbols(
((BoundUnconvertedAddressOfOperator)boundNode).Operand,
boundNodeForSyntacticParent, binderOpt, out resultKind, out isDynamic, methodGroup: out _);
break;
}

case BoundKind.IndexerAccess:
{
// As for BoundCall, pull out stashed candidates if overload resolution failed.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 44 additions & 4 deletions src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ internal IOperation CreateInternal(BoundNode boundNode)
return CreateBoundSwitchExpressionArmOperation((BoundSwitchExpressionArm)boundNode);
case BoundKind.ObjectOrCollectionValuePlaceholder:
return CreateCollectionValuePlaceholderOperation((BoundObjectOrCollectionValuePlaceholder)boundNode);
case BoundKind.FunctionPointerInvocation:
333fred marked this conversation as resolved.
Show resolved Hide resolved
return CreateBoundFunctionPointerInvocationOperation((BoundFunctionPointerInvocation)boundNode);
case BoundKind.UnconvertedAddressOfOperator:
return CreateBoundUnconvertedAddressOfOperatorOperation((BoundUnconvertedAddressOfOperator)boundNode);

case BoundKind.Attribute:
case BoundKind.ArgList:
Expand Down Expand Up @@ -334,7 +338,7 @@ internal IOperation CreateInternal(BoundNode boundNode)
}
}

return new CSharpLazyNoneOperation(this, boundNode, _semanticModel, boundNode.Syntax, constantValue, isImplicit: isImplicit);
return new CSharpLazyNoneOperation(this, boundNode, _semanticModel, boundNode.Syntax, constantValue, isImplicit: isImplicit, type: null);

default:
throw ExceptionUtilities.UnexpectedValue(boundNode.Kind);
Expand Down Expand Up @@ -434,6 +438,33 @@ private IOperation CreateBoundCallOperation(BoundCall boundCall)
return new CSharpLazyInvocationOperation(this, boundCall, targetMethod.GetPublicSymbol(), isVirtual, _semanticModel, syntax, type, constantValue, isImplicit);
}

private IOperation CreateBoundFunctionPointerInvocationOperation(BoundFunctionPointerInvocation boundFunctionPointerInvocation)
{
ITypeSymbol type = boundFunctionPointerInvocation.Type.GetPublicSymbol();
SyntaxNode syntax = boundFunctionPointerInvocation.Syntax;
Optional<object> constantValue = ConvertToOptional(boundFunctionPointerInvocation.ConstantValue);
bool isImplicit = boundFunctionPointerInvocation.WasCompilerGenerated;

if (boundFunctionPointerInvocation.ResultKind != LookupResultKind.Viable)
{
return new CSharpLazyInvalidOperation(this, boundFunctionPointerInvocation, _semanticModel, syntax, type, constantValue, isImplicit);
}

return new CSharpLazyNoneOperation(this, boundFunctionPointerInvocation, _semanticModel, syntax, constantValue, isImplicit, type);
}

private IOperation CreateBoundUnconvertedAddressOfOperatorOperation(BoundUnconvertedAddressOfOperator boundUnconvertedAddressOf)
{
return new CSharpLazyAddressOfOperation(
this,
boundUnconvertedAddressOf.Operand,
_semanticModel,
boundUnconvertedAddressOf.Syntax,
boundUnconvertedAddressOf.Type.GetPublicSymbol(),
ConvertToOptional(boundUnconvertedAddressOf.ConstantValue),
boundUnconvertedAddressOf.WasCompilerGenerated);
}

internal ImmutableArray<IOperation> CreateIgnoredDimensions(BoundNode declaration, SyntaxNode declarationSyntax)
{
switch (declaration.Kind)
Expand Down Expand Up @@ -900,12 +931,21 @@ private IOperation CreateBoundConversionOperation(BoundConversion boundConversio
BoundExpression boundOperand = boundConversion.Operand;
if (boundConversion.ConversionKind == CSharp.ConversionKind.MethodGroup)
{
// We don't check HasErrors on the conversion here because if we actually have a MethodGroup conversion,
// overload resolution succeeded. The resulting method could be invalid for other reasons, but we don't
// hide the resolved method.
SyntaxNode syntax = boundConversion.Syntax;
ITypeSymbol type = boundConversion.Type.GetPublicSymbol();
Optional<object> constantValue = ConvertToOptional(boundConversion.ConstantValue);

if (boundConversion.Type is FunctionPointerTypeSymbol)
{
Debug.Assert(boundConversion.Conversion.MethodSymbol is object);
return new AddressOfOperation(
CreateBoundMethodGroupSingleMethodOperation((BoundMethodGroup)boundConversion.Operand, boundConversion.SymbolOpt, suppressVirtualCalls: false),
_semanticModel, syntax, type, constantValue, boundConversion.WasCompilerGenerated);
}

// We don't check HasErrors on the conversion here because if we actually have a MethodGroup conversion,
// overload resolution succeeded. The resulting method could be invalid for other reasons, but we don't
// hide the resolved method.
return new CSharpLazyDelegateCreationOperation(this, boundConversion, _semanticModel, syntax, type, constantValue, isImplicit);
}
else
Expand Down
Loading