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

NullReferenceException when using switch expression with CSharpScript #49529

Closed
haacked opened this issue Nov 20, 2020 · 12 comments · Fixed by #55017
Closed

NullReferenceException when using switch expression with CSharpScript #49529

haacked opened this issue Nov 20, 2020 · 12 comments · Fixed by #55017
Labels
Area-Interactive Bug help wanted The issue is "up for grabs" - add a comment if you are interested in working on it
Milestone

Comments

@haacked
Copy link

haacked commented Nov 20, 2020

Version Used: Microsoft.CodeAnalysis.CSharp.Scripting 3.8.0.0

Steps to Reproduce:

  1. Use CSharpScript to create a script that contains a switch expression (see Unit test below for a full demo).
  2. Attempt to call Compile or RunAsync on the Script.

Expected Behavior:
It works

Actual Behavior:
It fails

Here's a failing unit test that should pass. It demonstrates the problem.

[Fact]
public async Task RoslynCrashTest()
{
    var options = ScriptOptions.Default
        .WithLanguageVersion(LanguageVersion.CSharp8);
    var code = @"
using System;
var reply = Arguments switch {
    null => ""Arguments are null"",
    """" => ""Arguments are empty"",
    _ => Arguments
};

return reply;
";
    
    var script = CSharpScript.Create<string>(code, options, typeof(ScriptGlobals));
    var compilation = script.Compile();
    var result = await script.RunAsync(new ScriptGlobals {Arguments = typeof(CSharpScript).AssemblyQualifiedName});
    Assert.Equal(
        "Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript, Microsoft.CodeAnalysis.CSharp.Scripting, Version=3.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
        result.ReturnValue);
}

public class ScriptGlobals
{
    public string Arguments { get; set; }
}

The script.Compile(); call throws an AggregateException. The inner exception is a NullReferenceException.

The stack trace for the NullReferenceException is

at Microsoft.CodeAnalysis.CSharp.Binder.BindSwitchExpression(SwitchExpressionSyntax node, DiagnosticBag diagnostics) at Microsoft.CodeAnalysis.CSharp.Binder.BindExpressionInternal(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed) at Microsoft.CodeAnalysis.CSharp.Binder.BindExpression(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed) at Microsoft.CodeAnalysis.CSharp.Binder.BindValue(ExpressionSyntax node, DiagnosticBag diagnostics, BindValueKind valueKind) at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, ExpressionSyntax initializer, BindValueKind valueKind, RefKind refKind, CSharpSyntaxNode errorSyntax) at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, RefKind refKind, EqualsValueClauseSyntax initializer, CSharpSyntaxNode errorSyntax) at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.GetFieldType(ConsList`1 fieldsBeingBound) at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_TypeWithAnnotations() at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_Type() at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics) at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.CheckSpecialMemberErrors(DiagnosticBag diagnostics) at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.AfterMembersChecks(DiagnosticBag diagnostics) at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.CSharp.Symbol.ForceCompleteMemberByLocation(SourceLocation locationOpt, Symbol member, CancellationToken cancellationToken) at Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamespaceSymbol.<>c__DisplayClass49_1.b__0(Int32 i) at Roslyn.Utilities.UICultureUtilities.<>c__DisplayClass6_0`1.b__0(T param) at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source) at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion) at System.Threading.Tasks.TaskReplicator.Replica`1.ExecuteAction(Boolean& yieldedBeforeCompletion) at System.Threading.Tasks.TaskReplicator.Replica.Execute()
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Nov 20, 2020
@haacked
Copy link
Author

haacked commented Nov 20, 2020

I also tried this with LanguageVersion.CSharp9 just in case and it still fails.

@jinujoseph jinujoseph added Bug help wanted The issue is "up for grabs" - add a comment if you are interested in working on it and removed untriaged Issues and PRs which have not yet been triaged by a lead labels Dec 9, 2020
@jinujoseph jinujoseph added this to the Backlog milestone Dec 9, 2020
@jinujoseph
Copy link
Contributor

cc @tmat

@haacked
Copy link
Author

haacked commented Feb 10, 2021

Hello! I opened a PR to start investigating this. I added a unit test that fails. It fails with a different stack trace when I run it in the dotnet/roslyn test suite against the master branch.

#51133

Here's the error and stack trace copied over from that PR. If this rings any bells for anyone, I'd appreciate a pointer in the right direction. Otherwise I will continue to flail around and do my best 😄.

dog at keyboard who has no idea what it's doing

System.AggregateException : One or more errors occurred. (Assertion failed) ---- System.InvalidOperationException : Assertion failed

Stack trace:
at System.Threading.Tasks.TaskReplicator.Run[TState](ReplicatableUserAction1 action, ParallelOptions options, Boolean stopOnFirstFailure) at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action1 body, Action2 bodyWithState, Func4 bodyWithLocal, Func1 localInit, Action1 localFinally)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
at System.Threading.Tasks.Parallel.ThrowSingleCancellationExceptionOrOtherException(ICollection exceptions, CancellationToken cancelToken, Exception otherException)
at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action1 body, Action2 bodyWithState, Func4 bodyWithLocal, Func1 localInit, Action1 localFinally) at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action1 body)
at Roslyn.Utilities.RoslynParallel.For(Int32 fromInclusive, Int32 toExclusive, Action1 body, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs:line 23 at Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamespaceSymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs:line 48 at Microsoft.CodeAnalysis.CSharp.Symbols.SourceModuleSymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceModuleSymbol.cs:line 263 at Microsoft.CodeAnalysis.CSharp.Symbols.SourceAssemblySymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceAssemblySymbol.cs:line 913 at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetSourceDeclarationDiagnostics(SyntaxTree syntaxTree, Nullable1 filterSpanWithinTree, Func4 locationFilterOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 2677 at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, DiagnosticBag diagnostics, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 2564 at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetDiagnostics(CompilationStage stage, Boolean includeEarlierStages, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 2493 at Microsoft.CodeAnalysis.CSharp.CSharpCompilation.CompileMethods(CommonPEModuleBuilder moduleBuilder, Boolean emittingPdb, Boolean emitMetadataOnly, Boolean emitTestCoverageData, DiagnosticBag diagnostics, Predicate1 filterOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs:line 2915
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream metadataPEStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable1 embeddedTexts, CompilationTestData testData, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/Compilation/Compilation.cs:line 2584
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable1 embeddedTexts, Stream metadataPEStream, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/Compilation/Compilation.cs:line 2527
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable1 manifestResources, EmitOptions options, IMethodSymbol debugEntryPoint, Stream sourceLinkStream, IEnumerable1 embeddedTexts, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/Compilation/Compilation.cs:line 2383
at Microsoft.CodeAnalysis.Compilation.Emit(Stream peStream, Stream pdbStream, Stream xmlDocumentationStream, Stream win32Resources, IEnumerable1 manifestResources, EmitOptions options, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/Compilation/Compilation.cs:line 2332 at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.Emit(Stream peStream, Stream pdbStreamOpt, Compilation compilation, EmitOptions options, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/ScriptBuilder.cs:line 182 at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.Build[T](Compilation compilation, DiagnosticBag diagnostics, Boolean emitDebugInformation, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/ScriptBuilder.cs:line 136 at Microsoft.CodeAnalysis.Scripting.ScriptBuilder.CreateExecutor[T](ScriptCompiler compiler, Compilation compilation, Boolean emitDebugInformation, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/ScriptBuilder.cs:line 88 at Microsoft.CodeAnalysis.Scripting.Script1.GetExecutor(CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/Script.cs:line 365
at Microsoft.CodeAnalysis.Scripting.Script1.CommonCompile(CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/Script.cs:line 338 at Microsoft.CodeAnalysis.Scripting.Script.Compile(CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Scripting/Core/Script.cs:line 232 at Microsoft.CodeAnalysis.CSharp.Scripting.UnitTests.ScriptTests.TestRunScriptWithScriptGlobalsAndSwitchExpression() in /Users/haacked/dev/third-party/roslyn/src/Scripting/CSharpTest/ScriptTests.cs:line 366 --- End of stack trace from previous location where exception was thrown --- ----- Inner Stack Trace ----- at Microsoft.CodeAnalysis.ThrowingTraceListener.Fail(String message, String detailMessage) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Test/Core/ThrowingTraceListener.cs:line 26 at System.Diagnostics.TraceInternal.Fail(String message, String detailMessage) at System.Diagnostics.TraceInternal.TraceProvider.Fail(String message, String detailMessage) at Roslyn.Utilities.RoslynDebug.Assert(Boolean b) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/InternalUtilities/Debug.cs:line 14 at Microsoft.CodeAnalysis.CSharp.Binder.BindSwitchExpression(SwitchExpressionSyntax node, DiagnosticBag diagnostics) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs:line 143 at Microsoft.CodeAnalysis.CSharp.Binder.BindExpressionInternal(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs:line 632 at Microsoft.CodeAnalysis.CSharp.Binder.BindExpression(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs:line 483 at Microsoft.CodeAnalysis.CSharp.Binder.BindValue(ExpressionSyntax node, DiagnosticBag diagnostics, BindValueKind valueKind) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs:line 232 at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, ExpressionSyntax initializer, BindValueKind valueKind, RefKind refKind, CSharpSyntaxNode errorSyntax) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs:line 870 at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, RefKind refKind, EqualsValueClauseSyntax initializer, CSharpSyntaxNode errorSyntax) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs:line 846 at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.GetFieldType(ConsList1 fieldsBeingBound) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs:line 494
at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_TypeWithAnnotations() in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs:line 60
at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_Type() in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/FieldSymbol.cs:line 69
at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberFieldSymbol.cs:line 604
at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.CheckSpecialMemberErrors(DiagnosticBag diagnostics) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs:line 1966
at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.AfterMembersChecks(DiagnosticBag diagnostics) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs:line 1536
at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs:line 540
at Microsoft.CodeAnalysis.CSharp.Symbol.ForceCompleteMemberByLocation(SourceLocation locationOpt, Symbol member, CancellationToken cancellationToken) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Symbol.cs:line 802
at Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamespaceSymbol.<>c__DisplayClass49_1.b__0(Int32 i) in /Users/haacked/dev/third-party/roslyn/src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol_Completion.cs:line 51
at Roslyn.Utilities.UICultureUtilities.<>c__DisplayClass6_01.<WithCurrentUICulture>b__0(T param) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/InternalUtilities/UICultureUtilities.cs:line 169 at Roslyn.Utilities.RoslynParallel.<>c__DisplayClass1_0.<For>g__errorHandlingBody|0(Int32 i) in /Users/haacked/dev/third-party/roslyn/src/Compilers/Core/Portable/InternalUtilities/RoslynParallel.cs:line 30 at System.Threading.Tasks.Parallel.<>c__DisplayClass19_01.b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
at System.Threading.Tasks.TaskReplicator.Replica.Execute()

@haacked
Copy link
Author

haacked commented Feb 10, 2021

Looks like the assertion on line 143 is failing: https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs#L142-L143

142: Binder? switchBinder = this.GetBinder(node);
143: RoslynDebug.Assert(switchBinder is { });

@haacked
Copy link
Author

haacked commented Feb 11, 2021

To add context why this issue is important to me: https://twitter.com/haacked/status/1359895904719753217?s=21

Switch expressions are perfect for bot skills but they’re broken in our product. If we can fix this, it makes for a nice showcase of C# as a great language for automation imho. 😊

@KathleenDollard
Copy link

We do not have any current plans to enhance C# Interactive, including fixing issues like this with newer language features.

It is useful for us to understand how people are using it/want to use it to consider future plans. Are you using C# Interactive within your own application?

@haacked
Copy link
Author

haacked commented Feb 11, 2021

Hi @KathleenDollard! 👋

We do not have any current plans to enhance C# Interactive, including fixing issues like this with newer language features.

Aw bummer. Was there an announcement I missed? I wouldn't be surprised as there's so much good stuff happening with C#.

It is useful for us to understand how people are using it/want to use it to consider future plans. Are you using C# Interactive within your own application?

To be clear, it's not the interactive part we care about, but the use of C# as a scripting language. I'm guessing the two are the same thing as far as Roslyn is concerned, correct?

But I'm happy to describe how we're using Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript to help understand why it's important to me and to maybe brainstorm alternatives.

I just announced a beta of a new product that is a hosted chat bot. The bot can be extended with "skills" written by customers in Python, JavaScript, and of course, C#! The code is written in the browser and we save it off and compile it.

We want the skill authoring experience to have as little boilerplate as possible. Here's an incomplete example of a skill you might right.

var (cmd, value) = Bot.Arguments;

if (cmd.Value is "foo") {
    await Bot.ReplyAsync("Be more original.");
    return;
}
await Bot.Brain.WriteAsync(new Bar { Baz = value.Value });
await Bot.ReplyAsync("This is really contrived.");

public class Bar {
    public string Baz { get; set; }
}

The important thing to note is the author doesn't have to declare a class or method. They just write code like a top level program. However, they can declare classes and we pass in a Bot as a script global object.

This is why we chose CSharpScript. Behind the scenes we're doing something like:

var script = CSharpScript.Create<string>(code, options, typeof(IBot));
var bot = ServiceProvider.Create<IBot>(); // Create the bot using DI.
var result = await script.RunAsync(bot);

Other options we considered were:

  1. Use the regular Roslyn compiler and simply wrap user code in a function. The problem with this is you can't declare a class in a function and we'd like to avoid manipulating user code to extract classes in a skill into their own classes.
  2. Use top level programs. We run the user code in an Azure Function and they don't support .NET 5 yet. Also, I'm not aware that there's a way to pass state into a top level program the way we can with CSharpScript.

Unfortunately we've invested a lot of time in our approach. I'm happy to switch if this is a dead-end, but not clear on the best path forward. Any suggestions or ideas are appreciated.

@KathleenDollard
Copy link

Phil,

Thanks for the additional information, particularly more information on what you doing!

For clarity, it looks like people can be successful today with the current state of scripting, they just have to work harder. Is that true?

You did clarify why pattern matching would be nice for these bots, and it might be confusing for C# programmers to reason about what features are available.

Kathleen

@haacked
Copy link
Author

haacked commented Feb 11, 2021

For clarity, it looks like people can be successful today with the current state of scripting, they just have to work harder. Is that true?

Absolutely! It's been working great. The issue is we support most of the features of C# 8, but some features fail and it's confusing to our end users. And in our case, the feature that would make writing bot skills really elegant causes a NullReferenceException.

You did clarify why pattern matching would be nice for these bots, and it might be confusing for C# programmers to reason about what features are available.

Exactly! Switch expressions make writing bot skills in C# a joy. And I don't use the word "joy" lightly here. The lack of that feature is painful for us.

There's also the long term support question we need to understand. If CSharpScript will no longer adopt newer features of C#, then we need to figure out another solution. We don't want to depend on an unsupported feature.

@ProIcons
Copy link
Contributor

So C#'s Server Discord bot is facing exactly the same issue on it's REPL

<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.9.0-5.final" />
var income = 900m;
var res = income switch
{
    < 9876   => 0 + (0.10m * (income - 0m)),
    < 40126  => 987.5m + (0.12m * (income - 9875m)),
    < 85126  => 4617.5m + (0.22m * (income - 40125m)),
    < 163301 => 14605.5m + (0.24m * (income - 85252m)),
    < 207351 => 33271.5m + (0.32m * (income - 163300m)),
    < 518401 => 47367.5m + (0.35m * (income - 207350m)),
    _        => 156235m + (0.37m * (income - 518400m))
};
Console.WriteLine($"Owned: {res}");

I get
"Object reference not set to an instance of an object."
On an aggregate exception with inner exception being

   at Microsoft.CodeAnalysis.CSharp.Binder.BindSwitchExpression(SwitchExpressionSyntax node, DiagnosticBag diagnostics)
   at Microsoft.CodeAnalysis.CSharp.Binder.BindExpressionInternal(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed)
   at Microsoft.CodeAnalysis.CSharp.Binder.BindExpression(ExpressionSyntax node, DiagnosticBag diagnostics, Boolean invoked, Boolean indexed)
   at Microsoft.CodeAnalysis.CSharp.Binder.BindValue(ExpressionSyntax node, DiagnosticBag diagnostics, BindValueKind valueKind)
   at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, ExpressionSyntax initializer, BindValueKind valueKind, RefKind refKind, CSharpSyntaxNode errorSyntax)
   at Microsoft.CodeAnalysis.CSharp.Binder.BindInferredVariableInitializer(DiagnosticBag diagnostics, RefKind refKind, EqualsValueClauseSyntax initializer, CSharpSyntaxNode errorSyntax)
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.GetFieldType(ConsList`1 fieldsBeingBound)
   at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_TypeWithAnnotations()
   at Microsoft.CodeAnalysis.CSharp.Symbols.FieldSymbol.get_Type()
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberFieldSymbolFromDeclarator.AfterAddingTypeMembersChecks(ConversionsBase conversions, DiagnosticBag diagnostics)
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.CheckSpecialMemberErrors(DiagnosticBag diagnostics)
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.AfterMembersChecks(DiagnosticBag diagnostics)
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceMemberContainerTypeSymbol.ForceComplete(SourceLocation locationOpt, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Symbol.ForceCompleteMemberByLocation(SourceLocation locationOpt, Symbol member, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Symbols.SourceNamespaceSymbol.<>c__DisplayClass49_1.<ForceComplete>b__0(Int32 i)
   at Roslyn.Utilities.UICultureUtilities.<>c__DisplayClass6_0`1.<WithCurrentUICulture>b__0(T param)
   at Roslyn.Utilities.RoslynParallel.<>c__DisplayClass1_0.<For>g__errorHandlingBody|0(Int32 i)
   at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.<ForWorker>b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw(Exception source)
   at System.Threading.Tasks.Parallel.<>c__DisplayClass19_0`1.<ForWorker>b__1(RangeWorker& currentWorker, Int32 timeout, Boolean& replicationDelegateYieldedBeforeCompletion)
   at System.Threading.Tasks.TaskReplicator.Replica`1.ExecuteAction(Boolean& yieldedBeforeCompletion)
   at System.Threading.Tasks.TaskReplicator.Replica.Execute()

While executing this line:

var compileResult = await compilation.GetAllDiagnosticsAsync();

Full code reference

This REPL is being used to demonstrate code examples with results to users need help in the Server.

@jcouv jcouv added the untriaged Issues and PRs which have not yet been triaged by a lead label Feb 28, 2021
@ProIcons
Copy link
Contributor

ProIcons commented Feb 28, 2021

So after some investigation i've noticed that on
Binder_Pattern.cs

Binder? switchBinder = this.GetBinder(node);

is getting called over and over again until we get into BuckStopsHereBinder.cs

internal override Binder? GetBinder(SyntaxNode node)
{
return null;
}

After some more investigation if i replace on the original code with

var income = 900m;
decimal res = income switch
{
   < 9876   => 0 + (0.10m * (income - 0m)),
   < 40126  => 987.5m + (0.12m * (income - 9875m)),
   < 85126  => 4617.5m + (0.22m * (income - 40125m)),
   < 163301 => 14605.5m + (0.24m * (income - 85252m)),
   < 207351 => 33271.5m + (0.32m * (income - 163300m)),
   < 518401 => 47367.5m + (0.35m * (income - 207350m)),
   _        => 156235m + (0.37m * (income - 518400m))
};
Console.WriteLine($"Owned: {res}");

so effectively replacing var with decimal it works just fine.

Also adding invalid code inside the switch doesn't throw any syntax error, it just crashes with NullReferenceException because of the "var"

var reply = args switch {
    null => argumasdgadgadhafjgfhk,mfgdhgsdfhdfjd
    423W^RYweafrr23nb w4 yh3ewrtgfbewsdfghbdfcvdsaghrt uy354erwt fq3wesge sdff as
    fhl.
    gjk
    jterd
    _ => args
};

If i replace var with dynamic or any given type i will get the syntax errors as expected.

@haacked
Copy link
Author

haacked commented Mar 1, 2021

@ProIcons oh nice! I confirmed this is the case. Still would like var to work obviously, but this does improve things for me. 😄

@jinujoseph jinujoseph removed the untriaged Issues and PRs which have not yet been triaged by a lead label Mar 11, 2021
ProIcons added a commit to ProIcons/roslyn that referenced this issue Jul 21, 2021
- Added ExecutableBinder on chain
333fred pushed a commit that referenced this issue Jul 23, 2021
…t determine the 'var' type on the new switch style (#55017)

* Fixes #49529
- Added ExecutableBinder on chain
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Interactive Bug help wanted The issue is "up for grabs" - add a comment if you are interested in working on it
Projects
None yet
7 participants