diff --git a/src/Microsoft.DotNet.Interactive.Tests/KeyValueStoreKernelTests.cs b/src/Microsoft.DotNet.Interactive.Tests/KeyValueStoreKernelTests.cs index 9372033b2c..67a3e2d7c0 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/KeyValueStoreKernelTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/KeyValueStoreKernelTests.cs @@ -445,6 +445,33 @@ public async Task Completions_show_value_options() .Contain("--name", "--from-url", "--from-file", "--mime-type"); } + [Fact] + public async Task Canceled_input_request_does_not_record_input_token_as_value() + { + using var kernel = CreateKernel(); + + kernel.RegisterCommandHandler((requestInput, context) => + { + context.Fail(requestInput); + return Task.CompletedTask; + }); + + kernel.SetDefaultTargetKernelNameForCommand(typeof(RequestInput), kernel.Name); + + var result = await kernel.SubmitCodeAsync( + """ + #!value --from-value @input:{"type": "file"} --name file + """); + + result.Events.Should().ContainSingle(); + + var keyValueStoreKernel = kernel.FindKernelByName("value"); + + var (_, valueInfosProduced) = await keyValueStoreKernel.TryRequestValueInfosAsync(); + + valueInfosProduced.ValueInfos.Should().BeEmpty(); + } + private static CompositeKernel CreateKernel() => new() { diff --git a/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs b/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs index c5123c18e9..a8521d0bdf 100644 --- a/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs +++ b/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs @@ -95,6 +95,12 @@ Task TryGetKernelCommandAsync( ExpressionBindingResult expressionBindingResult, Kernel keyValueStoreKernel) { + if (expressionBindingResult.Diagnostics.FirstOrDefault(d => d.Severity == DiagnosticSeverity.Error) is { } diagnostic) + { + directiveNode.AddDiagnostic(diagnostic); + return Task.FromResult(null); + } + var parameterValues = directiveNode .GetParameterValues(directive, expressionBindingResult.BoundValues) .ToDictionary(t => t.Name, t => (t.Value, t.ParameterNode)); diff --git a/src/Microsoft.DotNet.Interactive/Parsing/DirectiveExpressionNode.cs b/src/Microsoft.DotNet.Interactive/Parsing/DirectiveExpressionNode.cs index 3d739bdde7..214dac33a4 100644 --- a/src/Microsoft.DotNet.Interactive/Parsing/DirectiveExpressionNode.cs +++ b/src/Microsoft.DotNet.Interactive/Parsing/DirectiveExpressionNode.cs @@ -16,6 +16,9 @@ internal DirectiveExpressionNode(SourceText sourceText, SyntaxTree syntaxTree) : public DirectiveExpressionParametersNode? ParametersNode { get; private set; } + public bool IsInputExpression => + TypeNode?.Type is "input" or "password"; + public void Add(DirectiveExpressionTypeNode node) { AddInternal(node); diff --git a/src/Microsoft.DotNet.Interactive/Parsing/PolyglotSyntaxParser.cs b/src/Microsoft.DotNet.Interactive/Parsing/PolyglotSyntaxParser.cs index 5bde8153eb..b9b664c6d2 100644 --- a/src/Microsoft.DotNet.Interactive/Parsing/PolyglotSyntaxParser.cs +++ b/src/Microsoft.DotNet.Interactive/Parsing/PolyglotSyntaxParser.cs @@ -193,6 +193,9 @@ void AppendNode(SyntaxNode node) case DirectiveParameterValueNode valueNode when subcommandNode is null: directiveNode.Add(valueNode); break; + case DirectiveParameterValueNode valueNode when subcommandNode is not null: + subcommandNode.Add(valueNode); + break; case DirectiveParameterValueNode valueNode when subcommandNode is not null: subcommandNode.Add(valueNode); diff --git a/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs b/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs index 823fd4cc39..bcf1e934ab 100644 --- a/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs +++ b/src/Microsoft.DotNet.Interactive/Parsing/SubmissionParser.cs @@ -784,7 +784,8 @@ await RequestSingleValueOrInputAsync( sourceCommand, directiveNode.TargetKernelName); - if (expressionNode.Parent is { Parent: DirectiveParameterNode { NameNode.Text: { } parameterName } }) + if (bindingResult?.IsSuccessful == true && + expressionNode.Parent is { Parent: DirectiveParameterNode { NameNode.Text: { } parameterName } }) { if (inputProduced is not null) { @@ -798,6 +799,14 @@ await RequestSingleValueOrInputAsync( valuesProduced.Add(parameterName, valueProduced); } } + else + { + if (directiveNode.DescendantNodesAndTokens().OfType().Any(node => node.IsInputExpression) && + KernelInvocationContext.Current is { Command: SubmitCode } context) + { + context.Fail(sourceCommand, message: "Input not provided."); + } + } return bindingResult; });