提交 a38c7ff2 编写于 作者: K Kevin Halverson

Merge pull request #6239 from KevinH-MS/return

[AskMode] Allow return statements in scripts...
......@@ -209,23 +209,40 @@ internal struct ProcessedFieldInitializers
DiagnosticBag diagnostics,
bool isLast)
{
BoundStatement boundStatement = binder.BindStatement(statementNode, diagnostics);
// the result of the last global expression is assigned to the result storage for submission result:
if (binder.Compilation.IsSubmission && isLast && !boundStatement.HasAnyErrors)
var statement = binder.BindStatement(statementNode, diagnostics);
if (isLast && !statement.HasAnyErrors)
{
// insert an implicit conversion for the submission return type (if needed):
var expression = InitializerRewriter.GetTrailingScriptExpression(boundStatement);
if (expression != null &&
((object)expression.Type == null || expression.Type.SpecialType != SpecialType.System_Void))
// the result of the last global expression is assigned to the result storage for submission result:
if (binder.Compilation.IsSubmission)
{
// insert an implicit conversion for the submission return type (if needed):
var expression = InitializerRewriter.GetTrailingScriptExpression(statement);
if (expression != null &&
((object)expression.Type == null || expression.Type.SpecialType != SpecialType.System_Void))
{
var submissionResultType = scriptInitializer.ResultType;
expression = binder.GenerateConversionForAssignment(submissionResultType, expression, diagnostics);
statement = new BoundExpressionStatement(statement.Syntax, expression, expression.HasErrors);
}
}
// don't allow trailing expressions after labels (as in regular C#, labels must be followed by a statement):
if (statement.Kind == BoundKind.LabeledStatement)
{
var submissionResultType = scriptInitializer.ResultType;
expression = binder.GenerateConversionForAssignment(submissionResultType, expression, diagnostics);
boundStatement = new BoundExpressionStatement(boundStatement.Syntax, expression, expression.HasErrors);
var labeledStatementBody = ((BoundLabeledStatement)statement).Body;
while (labeledStatementBody.Kind == BoundKind.LabeledStatement)
{
labeledStatementBody = ((BoundLabeledStatement)labeledStatementBody).Body;
}
if (InitializerRewriter.GetTrailingScriptExpression(labeledStatementBody) != null)
{
Error(diagnostics, ErrorCode.ERR_SemicolonExpected, ((ExpressionStatementSyntax)labeledStatementBody.Syntax).SemicolonToken);
}
}
}
return new BoundGlobalStatementInitializer(statementNode, boundStatement);
return new BoundGlobalStatementInitializer(statementNode, statement);
}
private static BoundFieldInitializer BindFieldInitializer(Binder binder, FieldSymbol fieldSymbol, EqualsValueClauseSyntax equalsValueClauseNode,
......
......@@ -213,6 +213,10 @@ private BoundStatement BindYieldReturnStatement(YieldStatementSyntax node, Diagn
{
Error(diagnostics, ErrorCode.ERR_BadYieldInCatch, node.YieldKeyword);
}
else if (BindingTopLevelScriptCode)
{
Error(diagnostics, ErrorCode.ERR_YieldNotAllowedInScript, node.YieldKeyword);
}
return new BoundYieldReturnStatement(node, argument);
}
......@@ -223,6 +227,10 @@ private BoundStatement BindYieldBreakStatement(YieldStatementSyntax node, Diagno
{
Error(diagnostics, ErrorCode.ERR_BadYieldInFinally, node.YieldKeyword);
}
else if (BindingTopLevelScriptCode)
{
Error(diagnostics, ErrorCode.ERR_YieldNotAllowedInScript, node.YieldKeyword);
}
GetIteratorElementType(node, diagnostics);
return new BoundYieldBreakStatement(node);
......@@ -2794,14 +2802,18 @@ private BoundReturnStatement BindReturn(ReturnStatementSyntax syntax, Diagnostic
{
arg = BindValue(expressionSyntax, diagnostics, BindValueKind.RValue);
}
bool hasErrors;
if (BindingTopLevelScriptCode)
else
{
diagnostics.Add(ErrorCode.ERR_ReturnNotAllowedInScript, syntax.ReturnKeyword.GetLocation());
hasErrors = true;
// If this is a void return statement in a script, return default(T).
var interactiveInitializerMethod = this.ContainingMemberOrLambda as SynthesizedInteractiveInitializerMethod;
if (interactiveInitializerMethod != null)
{
arg = new BoundDefaultOperator(interactiveInitializerMethod.GetNonNullSyntaxNode(), interactiveInitializerMethod.ResultType);
}
}
else if (IsDirectlyInIterator)
bool hasErrors;
if (IsDirectlyInIterator)
{
diagnostics.Add(ErrorCode.ERR_ReturnInIterator, syntax.ReturnKeyword.GetLocation());
hasErrors = true;
......
......@@ -5930,7 +5930,7 @@ internal class CSharpResources {
}
/// <summary>
/// Looks up a localized string similar to You cannot declare namespace in script code.
/// Looks up a localized string similar to Cannot declare namespace in script code.
/// </summary>
internal static string ERR_NamespaceNotAllowedInScript {
get {
......@@ -7567,15 +7567,6 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to You cannot use &apos;return&apos; in top-level script code.
/// </summary>
internal static string ERR_ReturnNotAllowedInScript {
get {
return ResourceManager.GetString("ERR_ReturnNotAllowedInScript", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot modify the return value of &apos;{0}&apos; because it is not a variable.
/// </summary>
......@@ -8629,6 +8620,15 @@ internal class CSharpResources {
}
}
/// <summary>
/// Looks up a localized string similar to Cannot use &apos;yield&apos; in top-level script code.
/// </summary>
internal static string ERR_YieldNotAllowedInScript {
get {
return ResourceManager.GetString("ERR_YieldNotAllowedInScript", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Algorithm &apos;{0}&apos; is not supported.
/// </summary>
......
......@@ -3701,11 +3701,11 @@ You should consider suppressing the warning only if you're sure that you don't w
<data name="ERR_OverloadRefOutCtor" xml:space="preserve">
<value>Cannot define overloaded constructor '{0}' because it differs from another constructor only on ref and out</value>
</data>
<data name="ERR_ReturnNotAllowedInScript" xml:space="preserve">
<value>You cannot use 'return' in top-level script code</value>
<data name="ERR_YieldNotAllowedInScript" xml:space="preserve">
<value>Cannot use 'yield' in top-level script code</value>
</data>
<data name="ERR_NamespaceNotAllowedInScript" xml:space="preserve">
<value>You cannot declare namespace in script code</value>
<value>Cannot declare namespace in script code</value>
</data>
<data name="ERR_GlobalAttributesNotAllowed" xml:space="preserve">
<value>Assembly and module attributes are not allowed in this context</value>
......
......@@ -539,56 +539,48 @@ internal override Compilation WithEventQueue(AsyncQueue<CompilationEvent> eventQ
internal CSharpCompilation PreviousSubmission => ScriptCompilationInfo?.PreviousScriptCompilation;
// TODO (tomat): consider moving this method to SemanticModel
/// <summary>
/// Returns the type of the submission return value.
/// </summary>
/// <returns>
/// The type of the last expression of the submission.
/// Null if the type of the last expression is unknown (null).
/// Void type if the type of the last expression statement is void or
/// the submission ends with a declaration or statement that is not an expression statement.
/// </returns>
/// <remarks>
/// Note that the return type is System.Void for both compilations "System.Console.WriteLine();" and "System.Console.WriteLine()",
/// and <paramref name="hasValue"/> is <c>False</c> for the former and <c>True</c> for the latter.
/// </remarks>
/// <param name="hasValue">True if the submission has value, i.e. if it ends with a statement that is an expression statement.</param>
/// <exception cref="InvalidOperationException">The compilation doesn't represent a submission (<see cref="Compilation.IsSubmission"/> return false).</exception>
internal override ITypeSymbol GetSubmissionResultType(out bool hasValue)
internal override bool HasSubmissionResult()
{
Debug.Assert(IsSubmission);
hasValue = false;
// A submission may be empty or comprised of a single script file.
var tree = _syntaxAndDeclarations.ExternalSyntaxTrees.SingleOrDefault();
if (tree == null)
{
return GetSpecialType(SpecialType.System_Void);
return false;
}
// TODO: look for return statements
// https://github.com/dotnet/roslyn/issues/5773
var root = tree.GetCompilationUnitRoot();
if (root.HasErrors)
{
return false;
}
var lastStatement = (GlobalStatementSyntax)tree.GetCompilationUnitRoot().Members.LastOrDefault(decl => decl.IsKind(SyntaxKind.GlobalStatement));
if (lastStatement == null || !lastStatement.Statement.IsKind(SyntaxKind.ExpressionStatement))
// Are there any top-level return statments?
if (root.DescendantNodes(n => n is GlobalStatementSyntax || n is StatementSyntax || n is CompilationUnitSyntax).Any(n => n.IsKind(SyntaxKind.ReturnStatement)))
{
return GetSpecialType(SpecialType.System_Void);
return true;
}
var expressionStatement = (ExpressionStatementSyntax)lastStatement.Statement;
if (!expressionStatement.SemicolonToken.IsMissing)
// Is there a trailing expression?
var lastGlobalStatement = (GlobalStatementSyntax)root.Members.LastOrDefault(m => m.IsKind(SyntaxKind.GlobalStatement));
if (lastGlobalStatement != null)
{
return GetSpecialType(SpecialType.System_Void);
var statement = lastGlobalStatement.Statement;
if (statement.IsKind(SyntaxKind.ExpressionStatement))
{
var expressionStatement = (ExpressionStatementSyntax)statement;
if (expressionStatement.SemicolonToken.IsMissing)
{
var model = GetSemanticModel(tree);
var expression = expressionStatement.Expression;
var info = model.GetTypeInfo(expression);
return info.ConvertedType?.SpecialType != SpecialType.System_Void;
}
}
}
var model = GetSemanticModel(tree);
hasValue = true;
var expression = expressionStatement.Expression;
var info = model.GetTypeInfo(expression);
return (TypeSymbol)info.ConvertedType;
return false;
}
#endregion
......
......@@ -837,6 +837,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
BoundStatementList analyzedInitializers = null;
ImportChain importChain;
var hasTrailingExpression = false;
if (methodSymbol.IsScriptConstructor)
{
......@@ -847,7 +848,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
else if (methodSymbol.IsScriptInitializer)
{
// rewrite top-level statements and script variable declarations to a list of statements and assignments, respectively:
var initializerStatements = InitializerRewriter.RewriteScriptInitializer(processedInitializers.BoundInitializers, (SynthesizedInteractiveInitializerMethod)methodSymbol);
var initializerStatements = InitializerRewriter.RewriteScriptInitializer(processedInitializers.BoundInitializers, (SynthesizedInteractiveInitializerMethod)methodSymbol, out hasTrailingExpression);
// the lowered script initializers should not be treated as initializers anymore but as a method body:
body = new BoundBlock(initializerStatements.Syntax, ImmutableArray<LocalSymbol>.Empty, initializerStatements.Statements) { WasCompilerGenerated = true };
......@@ -921,7 +922,7 @@ public override object VisitField(FieldSymbol symbol, TypeCompilationState argum
BoundBlock flowAnalyzedBody = null;
if (body != null)
{
flowAnalyzedBody = FlowAnalysisPass.Rewrite(methodSymbol, body, diagsForCurrentMethod);
flowAnalyzedBody = FlowAnalysisPass.Rewrite(methodSymbol, body, diagsForCurrentMethod, hasTrailingExpression);
}
bool hasErrors = _hasDeclarationErrors || diagsForCurrentMethod.HasAnyErrors() || processedInitializers.HasErrors;
......
......@@ -1151,7 +1151,7 @@ internal enum ErrorCode
ERR_GlobalDefinitionOrStatementExpected = 7017,
ERR_ExpectedSingleScript = 7018,
ERR_RecursivelyTypedVariable = 7019,
ERR_ReturnNotAllowedInScript = 7020,
ERR_YieldNotAllowedInScript = 7020,
ERR_NamespaceNotAllowedInScript = 7021,
WRN_MainIgnored = 7022,
ERR_StaticInAsOrIs = 7023,
......
......@@ -127,11 +127,11 @@ protected override ImmutableArray<PendingBranch> Scan(ref bool badRegion)
/// <summary>
/// Perform control flow analysis, reporting all necessary diagnostics. Returns true if the end of
/// the body might be reachable..
/// the body might be reachable...
/// </summary>
public static bool Analyze(CSharpCompilation compilation, Symbol member, BoundNode node, DiagnosticBag diagnostics)
public static bool Analyze(CSharpCompilation compilation, Symbol member, BoundBlock block, DiagnosticBag diagnostics)
{
var walker = new ControlFlowPass(compilation, member, node);
var walker = new ControlFlowPass(compilation, member, block);
if (diagnostics != null)
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using System.Linq;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
......@@ -17,12 +18,19 @@ internal class FlowAnalysisPass
/// <param name="method">the method to be analyzed</param>
/// <param name="block">the method's body</param>
/// <param name="diagnostics">the receiver of the reported diagnostics</param>
/// <param name="hasTrailingExpression">indicates whether this Script had a trailing expression</param>
/// <returns>the rewritten block for the method (with a return statement possibly inserted)</returns>
public static BoundBlock Rewrite(
MethodSymbol method,
BoundBlock block,
DiagnosticBag diagnostics)
DiagnosticBag diagnostics,
bool hasTrailingExpression)
{
#if DEBUG
// We should only see a trailingExpression if we're in a Script initializer.
Debug.Assert(!hasTrailingExpression || method.IsScriptInitializer);
var initialDiagnosticCount = diagnostics.ToReadOnly().Length;
#endif
var compilation = method.DeclaringCompilation;
if (method.ReturnsVoid || method.IsIterator ||
......@@ -41,9 +49,27 @@ internal class FlowAnalysisPass
// will be reported by the lambda binder.
Debug.Assert(method.MethodKind != MethodKind.AnonymousFunction);
// Add implicit "return default(T)" if this is a submission that does not have a trailing expression.
var submissionResultType = (method as SynthesizedInteractiveInitializerMethod)?.ResultType;
if (!hasTrailingExpression && ((object)submissionResultType != null))
{
Debug.Assert(submissionResultType.SpecialType != SpecialType.System_Void);
var trailingExpression = new BoundDefaultOperator(method.GetNonNullSyntaxNode(), submissionResultType);
var newStatements = block.Statements.Add(new BoundReturnStatement(trailingExpression.Syntax, trailingExpression));
block = new BoundBlock(block.Syntax, ImmutableArray<LocalSymbol>.Empty, newStatements) { WasCompilerGenerated = true };
#if DEBUG
// It should not be necessary to repeat analysis after adding this node, because adding a trailing
// return in cases where one was missing should never produce different Diagnostics.
var flowAnalysisDiagnostics = DiagnosticBag.GetInstance();
Debug.Assert(!Analyze(compilation, method, block, flowAnalysisDiagnostics));
Debug.Assert(flowAnalysisDiagnostics.ToReadOnly().SequenceEqual(diagnostics.ToReadOnly().Skip(initialDiagnosticCount)));
flowAnalysisDiagnostics.Free();
#endif
}
// If there's more than one location, then the method is partial and we
// have already reported a non-void partial method error.
if (method.Locations.Length == 1)
else if (method.Locations.Length == 1)
{
diagnostics.Add(ErrorCode.ERR_ReturnExpected, method.Locations[0], method);
}
......
......@@ -21,53 +21,58 @@ internal static BoundTypeOrInstanceInitializers RewriteConstructor(ImmutableArra
return new BoundTypeOrInstanceInitializers(syntax, boundInitializers.SelectAsArray(RewriteInitializersAsStatements));
}
internal static BoundTypeOrInstanceInitializers RewriteScriptInitializer(ImmutableArray<BoundInitializer> boundInitializers, SynthesizedInteractiveInitializerMethod method)
internal static BoundTypeOrInstanceInitializers RewriteScriptInitializer(ImmutableArray<BoundInitializer> boundInitializers, SynthesizedInteractiveInitializerMethod method, out bool hasTrailingExpression)
{
Debug.Assert(!boundInitializers.IsDefault);
var boundStatements = ArrayBuilder<BoundStatement>.GetInstance(boundInitializers.Length);
var submissionResultType = method.ResultType;
BoundExpression submissionResult = null;
var hasSubmissionResultType = (object)submissionResultType != null;
BoundStatement lastStatement = null;
BoundExpression trailingExpression = null;
foreach (var initializer in boundInitializers)
{
// The value of the last expression statement (if any) is returned from the submission initializer.
if (((object)submissionResultType != null) &&
// The value of the last expression statement (if any) is returned from the submission initializer,
// unless this is a #load'ed tree. I the #load'ed tree case, we'll execute the trailing expression
// but discard its result.
if (hasSubmissionResultType &&
(initializer == boundInitializers.Last()) &&
(initializer.Kind == BoundKind.GlobalStatementInitializer))
(initializer.Kind == BoundKind.GlobalStatementInitializer) &&
method.DeclaringCompilation.IsSubmissionSyntaxTree(initializer.SyntaxTree))
{
var expr = GetTrailingScriptExpression(((BoundGlobalStatementInitializer)initializer).Statement);
if (expr != null &&
(object)expr.Type != null &&
expr.Type.SpecialType != SpecialType.System_Void)
lastStatement = ((BoundGlobalStatementInitializer)initializer).Statement;
var expression = GetTrailingScriptExpression(lastStatement);
if (expression != null &&
(object)expression.Type != null &&
expression.Type.SpecialType != SpecialType.System_Void)
{
submissionResult = expr;
trailingExpression = expression;
continue;
}
}
boundStatements.Add(RewriteInitializersAsStatements(initializer));
}
var syntax = method.GetNonNullSyntaxNode();
if ((object)submissionResultType != null)
if (hasSubmissionResultType && (trailingExpression != null))
{
if (submissionResult == null)
{
// Return default(T) if submission does not have a trailing expression.
submissionResult = new BoundDefaultOperator(syntax, submissionResultType);
}
Debug.Assert(submissionResult.Type.SpecialType != SpecialType.System_Void);
Debug.Assert(submissionResultType.SpecialType != SpecialType.System_Void);
// The expression is converted to the submission result type when the initializer is bound.
boundStatements.Add(new BoundReturnStatement(submissionResult.Syntax, submissionResult));
// Note: The trailing expression was already converted to the submission result type in Binder.BindGlobalStatement.
boundStatements.Add(new BoundReturnStatement(lastStatement.Syntax, trailingExpression));
hasTrailingExpression = true;
}
return new BoundTypeOrInstanceInitializers(syntax, boundStatements.ToImmutableAndFree());
else
{
hasTrailingExpression = false;
}
return new BoundTypeOrInstanceInitializers(method.GetNonNullSyntaxNode(), boundStatements.ToImmutableAndFree());
}
/// <summary>
/// Returns the expression if the statement is actually an
/// expression (ExpressionStatementSyntax with no trailing semicolon).
/// Returns the expression if the statement is actually an expression (ExpressionStatementSyntax with no trailing semicolon).
/// </summary>
internal static BoundExpression GetTrailingScriptExpression(BoundStatement statement)
{
......
......@@ -1056,5 +1056,65 @@ public void Label_GetDeclaredSymbol_Error_Script()
var symbol = model.GetDeclaredSymbol(label);
Assert.Equal("C", symbol.Name);
}
[Fact]
public void TrailingExpression()
{
var source = @"
goto EOF;
EOF:";
var compilation = CreateCompilationWithMscorlib45(source, parseOptions: TestOptions.Script);
compilation.GetDiagnostics().Verify(
// (3,5): error CS1733: Expected expression
// EOF:
Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(3, 5));
compilation = CreateSubmission(source);
compilation.GetDiagnostics().Verify(
// (3,5): error CS1733: Expected expression
// EOF:
Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(3, 5));
source = @"
goto EOF;
EOF: 42";
compilation = CreateCompilationWithMscorlib45(source, parseOptions: TestOptions.Script);
compilation.GetDiagnostics().Verify(
// (3,8): error CS1002: ; expected
// EOF: 42
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(3, 8));
source = @"
var obj = new object();
goto L1;
L1:
L2:
EOF: obj.ToString()";
compilation = CreateCompilationWithMscorlib45(source, parseOptions: TestOptions.Script);
compilation.GetDiagnostics().Verify(
// (6,20): error CS1002: ; expected
// EOF: obj.ToString()
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(6, 20),
// (5,1): warning CS0164: This label has not been referenced
// L2:
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L2").WithLocation(5, 1),
// (6,1): warning CS0164: This label has not been referenced
// EOF: obj.ToString()
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "EOF").WithLocation(6, 1));
compilation = CreateSubmission(source);
compilation.GetDiagnostics().Verify(
// (6,20): error CS1002: ; expected
// EOF: obj.ToString()
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(6, 20),
// (5,1): warning CS0164: This label has not been referenced
// L2:
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L2").WithLocation(5, 1),
// (6,1): warning CS0164: This label has not been referenced
// EOF: obj.ToString()
Diagnostic(ErrorCode.WRN_UnreferencedLabel, "EOF").WithLocation(6, 1));
}
}
}
......@@ -26,7 +26,7 @@ internal ImmutableArray<Diagnostic> FlowDiagnostics(CSharpCompilation compilatio
var boundBody = MethodCompiler.BindMethodBody(sourceSymbol, new TypeCompilationState(sourceSymbol.ContainingType, compilation, null), new DiagnosticBag());
if (boundBody != null)
{
FlowAnalysisPass.Rewrite(sourceSymbol, boundBody, flowDiagnostics);
FlowAnalysisPass.Rewrite(sourceSymbol, boundBody, flowDiagnostics, hasTrailingExpression: false);
}
}
......
......@@ -294,13 +294,21 @@ private IEnumerable<int> Failure()
public void TopLevelYieldReturn()
{
// The imcomplete statement is intended
var text =
@"yield return int.";
var comp = CreateCompilationWithMscorlib(text, parseOptions: TestOptions.Script);
comp.VerifyDiagnostics(Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(1, 18),
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 18),
Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(1, 1),
Diagnostic(ErrorCode.ERR_NoSuchMember, "").WithArguments("int", "").WithLocation(1, 18));
var text = "yield return int.";
var comp = CreateCompilationWithMscorlib45(text, parseOptions: TestOptions.Script);
comp.VerifyDiagnostics(
// (1,18): error CS1001: Identifier expected
// yield return int.
Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(1, 18),
// (1,18): error CS1002: ; expected
// yield return int.
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 18),
// (1,18): error CS0117: 'int' does not contain a definition for ''
// yield return int.
Diagnostic(ErrorCode.ERR_NoSuchMember, "").WithArguments("int", "").WithLocation(1, 18),
// (1,1): error CS7020: You cannot use 'yield' in top-level script code
// yield return int.
Diagnostic(ErrorCode.ERR_YieldNotAllowedInScript, "yield").WithLocation(1, 1));
var tree = comp.SyntaxTrees[0];
var yieldNode = (YieldStatementSyntax)tree.GetRoot().DescendantNodes().Where(n => n is YieldStatementSyntax).SingleOrDefault();
......@@ -318,17 +326,18 @@ public void TopLevelYieldReturn()
[WorkItem(5390, "https://github.com/dotnet/roslyn/issues/5390")]
public void TopLevelYieldBreak()
{
var text =
@"yield break;";
var comp = CreateCompilationWithMscorlib(text, parseOptions: TestOptions.Script);
comp.VerifyDiagnostics(Diagnostic(ErrorCode.ERR_MissingPredefinedMember).WithArguments("System.Threading.Tasks.Task", "GetAwaiter").WithLocation(1, 1),
Diagnostic(ErrorCode.WRN_UnreachableCode, "yield").WithLocation(1, 1));
var text = "yield break;";
var comp = CreateCompilationWithMscorlib45(text, parseOptions: TestOptions.Script);
comp.VerifyDiagnostics(
// (1,1): error CS7020: You cannot use 'yield' in top-level script code
// yield break;
Diagnostic(ErrorCode.ERR_YieldNotAllowedInScript, "yield").WithLocation(1, 1));
var tree = comp.SyntaxTrees[0];
var yieldNode = (YieldStatementSyntax)tree.GetRoot().DescendantNodes().Where(n => n is YieldStatementSyntax).SingleOrDefault();
Assert.NotNull(yieldNode);
Assert.Equal(SyntaxKind.YieldBreakStatement, yieldNode.Kind());
Assert.Equal(SyntaxKind.YieldBreakStatement, yieldNode.Kind());
}
}
}
......@@ -749,32 +749,6 @@ public void ERR_VariableUsedBeforeDeclaration()
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(1, 22));
}
[Fact]
public void ERR_ReturnNotAllowedInScript_Void()
{
var c = CreateSubmission("return;");
c.VerifyDiagnostics(
// (1,1): error CS7020: You cannot use 'return' in top-level script code
// return;
Diagnostic(ErrorCode.ERR_ReturnNotAllowedInScript, "return").WithLocation(1, 1),
// (1,1): warning CS0162: Unreachable code detected
// return;
Diagnostic(ErrorCode.WRN_UnreachableCode, "return").WithLocation(1, 1));
}
[Fact]
public void ERR_ReturnNotAllowedInScript_Expr()
{
var c = CreateSubmission("return 17;");
c.VerifyDiagnostics(
// (1,1): error CS7020: You cannot use 'return' in top-level script code
// return 17;
Diagnostic(ErrorCode.ERR_ReturnNotAllowedInScript, "return").WithLocation(1, 1),
// (1,1): warning CS0162: Unreachable code detected
// return 17;
Diagnostic(ErrorCode.WRN_UnreachableCode, "return").WithLocation(1, 1));
}
[Fact]
public void ERR_FieldCantBeRefAny()
{
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
......@@ -10,6 +11,7 @@
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
......@@ -1947,31 +1949,19 @@ public void SubmissionCompilation_Errors()
Assert.Throws<ArgumentException>(() => CSharpCompilation.CreateScriptCompilation("a", options: TestOptions.ReleaseDll.WithDelaySign(false)));
}
private static void TestSubmissionResult(CSharpCompilation s, SpecialType? expectedType, bool expectedHasValue)
{
bool hasValue;
var type = s.GetSubmissionResultType(out hasValue);
Assert.Equal(expectedType, type != null ? type.SpecialType : (SpecialType?)null);
Assert.Equal(expectedHasValue, hasValue);
}
[Fact]
public void SubmissionResultType()
public void HasSubmissionResult()
{
var submission = CSharpCompilation.CreateScriptCompilation("sub");
bool hasValue;
Assert.Equal(SpecialType.System_Void, submission.GetSubmissionResultType(out hasValue).SpecialType);
Assert.False(hasValue);
TestSubmissionResult(CreateSubmission("1", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Int32, expectedHasValue: true);
TestSubmissionResult(CreateSubmission("1;", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: false);
TestSubmissionResult(CreateSubmission("void foo() { }", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: false);
TestSubmissionResult(CreateSubmission("using System;", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: false);
TestSubmissionResult(CreateSubmission("int i;", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: false);
TestSubmissionResult(CreateSubmission("System.Console.WriteLine();", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: false);
TestSubmissionResult(CreateSubmission("System.Console.WriteLine()", parseOptions: TestOptions.Script), expectedType: SpecialType.System_Void, expectedHasValue: true);
TestSubmissionResult(CreateSubmission("null", parseOptions: TestOptions.Script), expectedType: null, expectedHasValue: true);
TestSubmissionResult(CreateSubmission("System.Console.WriteLine", parseOptions: TestOptions.Script), expectedType: null, expectedHasValue: true);
Assert.False(CSharpCompilation.CreateScriptCompilation("sub").HasSubmissionResult());
Assert.True(CreateSubmission("1", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("1;", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("void foo() { }", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("using System;", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("int i;", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("System.Console.WriteLine();", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.False(CreateSubmission("System.Console.WriteLine()", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.True(CreateSubmission("null", parseOptions: TestOptions.Script).HasSubmissionResult());
Assert.True(CreateSubmission("System.Console.WriteLine", parseOptions: TestOptions.Script).HasSubmissionResult());
}
/// <summary>
......@@ -1987,5 +1977,254 @@ public void PreviousSubmissionWithError()
Assert.Throws<InvalidOperationException>(() => CreateSubmission("a + 1", previous: s0));
}
#region Script return values
[Fact]
public void ReturnNullAsObject()
{
var script = CreateSubmission("return null;", returnType: typeof(object));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnStringAsObject()
{
var script = CreateSubmission("return \"¡Hola!\";", returnType: typeof(object));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnIntAsObject()
{
var script = CreateSubmission("return 42;", returnType: typeof(object));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void TrailingReturnVoidAsObject()
{
var script = CreateSubmission("return", returnType: typeof(object));
script.VerifyDiagnostics(
// (1,7): error CS1733: Expected expression
// return
Diagnostic(ErrorCode.ERR_ExpressionExpected, "").WithLocation(1, 7),
// (1,7): error CS1002: ; expected
// return
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 7));
Assert.False(script.HasSubmissionResult());
}
[Fact]
public void ReturnIntAsInt()
{
var script = CreateSubmission("return 42;", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnNullResultType()
{
// test that passing null is the same as passing typeof(object)
var script = CreateSubmission("return 42;", returnType: null);
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnNoSemicolon()
{
var script = CreateSubmission("return 42", returnType: typeof(uint));
script.VerifyDiagnostics(
// (1,10): error CS1002: ; expected
// return 42
Diagnostic(ErrorCode.ERR_SemicolonExpected, "").WithLocation(1, 10));
Assert.False(script.HasSubmissionResult());
}
[Fact]
public void ReturnAwait()
{
var script = CreateSubmission("return await System.Threading.Tasks.Task.FromResult(42);", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
script = CreateSubmission("return await System.Threading.Tasks.Task.FromResult(42);", returnType: typeof(Task<int>));
script.VerifyDiagnostics(
// (1,8): error CS0029: Cannot implicitly convert type 'int' to 'System.Threading.Tasks.Task<int>'
// return await System.Threading.Tasks.Task.FromResult(42);
Diagnostic(ErrorCode.ERR_NoImplicitConv, "await System.Threading.Tasks.Task.FromResult(42)").WithArguments("int", "System.Threading.Tasks.Task<int>").WithLocation(1, 8));
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnTaskNoAwait()
{
var script = CreateSubmission("return System.Threading.Tasks.Task.FromResult(42);", returnType: typeof(int));
script.VerifyDiagnostics(
// (1,8): error CS4016: Since this is an async method, the return expression must be of type 'int' rather than 'Task<int>'
// return System.Threading.Tasks.Task.FromResult(42);
Diagnostic(ErrorCode.ERR_BadAsyncReturnExpression, "System.Threading.Tasks.Task.FromResult(42)").WithArguments("int").WithLocation(1, 8));
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedScopes()
{
var script = CreateSubmission(@"
bool condition = false;
if (condition)
{
return 1;
}
else
{
return -1;
}", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedScopeWithTrailingExpression()
{
var script = CreateSubmission(@"
if (true)
{
return 1;
}
System.Console.WriteLine();", returnType: typeof(object));
script.VerifyDiagnostics(
// (6,1): warning CS0162: Unreachable code detected
// System.Console.WriteLine();
Diagnostic(ErrorCode.WRN_UnreachableCode, "System").WithLocation(6, 1));
Assert.True(script.HasSubmissionResult());
script = CreateSubmission(@"
if (true)
{
return 1;
}
System.Console.WriteLine()", returnType: typeof(object));
script.VerifyDiagnostics(
// (6,1): warning CS0162: Unreachable code detected
// System.Console.WriteLine();
Diagnostic(ErrorCode.WRN_UnreachableCode, "System").WithLocation(6, 1));
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedScopeNoTrailingExpression()
{
var script = CreateSubmission(@"
bool condition = false;
if (condition)
{
return 1;
}
System.Console.WriteLine();", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedMethod()
{
var script = CreateSubmission(@"
int TopMethod()
{
return 42;
}", returnType: typeof(string));
script.VerifyDiagnostics();
Assert.False(script.HasSubmissionResult());
script = CreateSubmission(@"
object TopMethod()
{
return new System.Exception();
}
TopMethod().ToString()", returnType: typeof(string));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedLambda()
{
var script = CreateSubmission(@"
System.Func<object> f = () =>
{
return new System.Exception();
};
42", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
script = CreateSubmission(@"
System.Func<object> f = () => new System.Exception();
42", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnInNestedAnonymousMethod()
{
var script = CreateSubmission(@"
System.Func<object> f = delegate ()
{
return new System.Exception();
};
42", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void LoadedFileWithWrongReturnType()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "return \"Who returns a string?\";"));
var script = CreateSubmission(@"
#load ""a.csx""
42", returnType: typeof(int), options: TestOptions.DebugDll.WithSourceReferenceResolver(resolver));
script.VerifyDiagnostics(
// a.csx(1,8): error CS0029: Cannot implicitly convert type 'string' to 'int'
// return "Who returns a string?"
Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""Who returns a string?""").WithArguments("string", "int").WithLocation(1, 8),
// (3,1): warning CS0162: Unreachable code detected
// 42
Diagnostic(ErrorCode.WRN_UnreachableCode, "42").WithLocation(3, 1));
Assert.True(script.HasSubmissionResult());
}
[Fact]
public void ReturnVoidInNestedMethodOrLambda()
{
var script = CreateSubmission(@"
void M1()
{
return;
}
System.Action a = () => { return; };
42", returnType: typeof(int));
script.VerifyDiagnostics();
Assert.True(script.HasSubmissionResult());
var compilation = CreateCompilationWithMscorlib45(@"
void M1()
{
return;
}
System.Action a = () => { return; };
42", parseOptions: TestOptions.Script);
compilation.VerifyDiagnostics();
}
#endregion
}
}
......@@ -341,33 +341,7 @@ internal static bool IsValidHostObjectType(Type type)
return !(info.IsValueType || info.IsPointer || info.IsByRef || info.ContainsGenericParameters);
}
/// <summary>
/// Returns the type of the submission return value.
/// </summary>
/// <param name="hasValue">
/// True if the submission has a return value, i.e. if the submission
/// ends with an expression statement.
/// </param>
/// <exception cref="InvalidOperationException">
/// The compilation doesn't represent a submission
/// (<see cref="IsSubmission"/> return false).
/// </exception>
/// <returns>
/// Null if the type of the last expression is unknown,
/// <see cref="void"/> if the type of the last expression statement is
/// void or if the submission is not an expression statement, or
/// otherwise the type of the last expression.
/// </returns>
/// <remarks>
/// Note that the return type is <see cref="void"/> if the last
/// statement is a non-expression statement e.g.,
/// <code>System.Console.WriteLine();</code>
/// and if the statement is an expression statement of type void e.g,
/// <code>System.Console.WriteLine()</code>. However,
/// <paramref name="hasValue"/> is false in the former case and true
/// in the latter.
/// </remarks>
internal abstract ITypeSymbol GetSubmissionResultType(out bool hasValue);
internal abstract bool HasSubmissionResult();
public Compilation WithScriptCompilationInfo(ScriptCompilationInfo info) => CommonWithScriptCompilationInfo(info);
protected abstract Compilation CommonWithScriptCompilationInfo(ScriptCompilationInfo info);
......
......@@ -711,40 +711,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
' TODO (tomat): consider moving this method to SemanticModel
''' <summary>
''' Returns the type of the submission return value.
''' </summary>
''' <param name="hasValue">
''' Whether the submission is considered to have a value.
''' This information can be used for example in a REPL implementation to determine whether to print out the result of a submission execution.
''' </param>
''' <returns>
''' Returns a static type of the expression of the last expression or call statement if there is any,
''' a symbol for <see cref="Void"/> otherwise.
''' </returns>
''' <remarks>
''' Note that the return type is System.Void for both compilations "System.Console.WriteLine()" and "?System.Console.WriteLine()",
''' and <paramref name="hasValue"/> is <c>False</c> for the former and <c>True</c> for the latter.
''' </remarks>
Friend Overrides Function GetSubmissionResultType(<Out> ByRef hasValue As Boolean) As ITypeSymbol
Friend Overrides Function HasSubmissionResult() As Boolean
Debug.Assert(IsSubmission)
hasValue = False
' submission can be empty or comprise of a script file
Dim tree = SyntaxTrees.SingleOrDefault()
If tree Is Nothing Then
Return False
End If
Dim root = tree.GetCompilationUnitRoot()
If root.HasErrors Then
Return False
End If
' TODO: look for return statements
' https://github.com/dotnet/roslyn/issues/5773
' submission can be empty or comprise of a script file
If tree Is Nothing Then
Return GetSpecialType(SpecialType.System_Void)
End If
Dim lastStatement = tree.GetCompilationUnitRoot().Members.LastOrDefault()
Dim lastStatement = root.Members.LastOrDefault()
If lastStatement Is Nothing Then
Return GetSpecialType(SpecialType.System_Void)
Return False
End If
Dim model = GetSemanticModel(tree)
......@@ -752,23 +738,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Case SyntaxKind.PrintStatement
Dim expression = DirectCast(lastStatement, PrintStatementSyntax).Expression
Dim info = model.GetTypeInfo(expression)
hasValue = True ' always true, even for info.Type = Void
Return DirectCast(info.ConvertedType, TypeSymbol)
' always true, even for info.Type = Void
Return True
Case SyntaxKind.ExpressionStatement
Dim expression = DirectCast(lastStatement, ExpressionStatementSyntax).Expression
Dim info = model.GetTypeInfo(expression)
hasValue = info.Type.SpecialType <> SpecialType.System_Void
Return DirectCast(info.ConvertedType, TypeSymbol)
Return info.Type.SpecialType <> SpecialType.System_Void
Case SyntaxKind.CallStatement
Dim expression = DirectCast(lastStatement, CallStatementSyntax).Invocation
Dim info = model.GetTypeInfo(expression)
hasValue = info.Type.SpecialType <> SpecialType.System_Void
Return DirectCast(info.ConvertedType, TypeSymbol)
Return info.Type.SpecialType <> SpecialType.System_Void
Case Else
Return GetSpecialType(SpecialType.System_Void)
Return False
End Select
End Function
......
......@@ -1747,49 +1747,32 @@ End Namespace
End Sub
<Fact>
Public Sub SubmissionResultType()
Dim submission = VisualBasicCompilation.CreateScriptCompilation("sub")
Dim hasValue As Boolean
Assert.Equal(SpecialType.System_Void, submission.GetSubmissionResultType(hasValue).SpecialType)
Assert.False(hasValue)
Public Sub HasSubmissionResult()
Assert.False(VisualBasicCompilation.CreateScriptCompilation("sub").HasSubmissionResult())
TestSubmissionResult(CreateSubmission("?1", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Int32, expectedHasValue:=True)
Assert.True(CreateSubmission("?1", parseOptions:=TestOptions.Script).HasSubmissionResult())
TestSubmissionResult(CreateSubmission("1", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Void, expectedHasValue:=False)
Assert.False(CreateSubmission("1", parseOptions:=TestOptions.Script).HasSubmissionResult())
' TODO (https://github.com/dotnet/roslyn/issues/4763): '?' should be optional
' TestSubmissionResult(CreateSubmission("1", parseOptions:=TestOptions.Interactive), expectedType:=SpecialType.System_Int32, expectedHasValue:=True)
' TODO (https://github.com/dotnet/roslyn/issues/4766): ReturnType should not be ignored
' TestSubmissionResult(CreateSubmission("?1", parseOptions:=TestOptions.Interactive, returnType:=GetType(Double)), expectedType:=SpecialType.System_Double, expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("
Assert.False(CreateSubmission("
Sub Foo()
End Sub
"), expectedType:=SpecialType.System_Void, expectedHasValue:=False)
TestSubmissionResult(CreateSubmission("Imports System", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Void, expectedHasValue:=False)
TestSubmissionResult(CreateSubmission("Dim i As Integer", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Void, expectedHasValue:=False)
TestSubmissionResult(CreateSubmission("System.Console.WriteLine()", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Void, expectedHasValue:=False)
TestSubmissionResult(CreateSubmission("?System.Console.WriteLine()", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Void, expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("System.Console.ReadLine()", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_String, expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("?System.Console.ReadLine()", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_String, expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("?Nothing", parseOptions:=TestOptions.Script), expectedType:=SpecialType.System_Object, expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("?AddressOf System.Console.WriteLine", parseOptions:=TestOptions.Script), expectedType:=DirectCast(Nothing, SpecialType?), expectedHasValue:=True)
TestSubmissionResult(CreateSubmission("?Function(x) x", parseOptions:=TestOptions.Script), expectedType:=AddressOf IsDelegateType, expectedHasValue:=True)
End Sub
Private Shared Sub TestSubmissionResult(s As VisualBasicCompilation, expectedType As SpecialType?, expectedHasValue As Boolean)
Dim hasValue As Boolean
Dim type = s.GetSubmissionResultType(hasValue)
Assert.Equal(expectedType, If(type IsNot Nothing, type.SpecialType, DirectCast(Nothing, SpecialType?)))
Assert.Equal(expectedHasValue, hasValue)
End Sub
Private Shared Sub TestSubmissionResult(s As VisualBasicCompilation, expectedType As Func(Of TypeSymbol, Boolean), expectedHasValue As Boolean)
Dim hasValue As Boolean
Dim type = s.GetSubmissionResultType(hasValue)
Assert.True(expectedType(DirectCast(type, TypeSymbol)), "unexpected type")
Assert.Equal(expectedHasValue, hasValue)
").HasSubmissionResult())
Assert.False(CreateSubmission("Imports System", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.False(CreateSubmission("Dim i As Integer", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.False(CreateSubmission("System.Console.WriteLine()", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("?System.Console.WriteLine()", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("System.Console.ReadLine()", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("?System.Console.ReadLine()", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("?Nothing", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("?AddressOf System.Console.WriteLine", parseOptions:=TestOptions.Script).HasSubmissionResult())
Assert.True(CreateSubmission("?Function(x) x", parseOptions:=TestOptions.Script).HasSubmissionResult())
End Sub
''' <summary>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Roslyn.Test.Utilities;
......@@ -163,7 +165,7 @@ public void TestRunCreatedScriptWithUnexpectedGlobals()
public void TestRunCreatedScriptWithoutGlobals()
{
var script = CSharpScript.Create("X + Y", globalsType: typeof(Globals));
// The script requires access to global variables but none were given
AssertEx.ThrowsArgumentException("globals", () => script.RunAsync());
}
......@@ -238,7 +240,7 @@ public async Task ScriptVariables_Chain()
{
var globals = new Globals { X = 10, Y = 20 };
var script =
var script =
CSharpScript.Create(
"var a = '1';",
globalsType: globals.GetType()).
......@@ -313,5 +315,354 @@ public async Task TestBranchingSubscripts()
var state3 = await state1.ContinueWithAsync("M(5)");
Assert.Equal(10, state3.ReturnValue);
}
[Fact]
public async Task ReturnIntAsObject()
{
var expected = 42;
var script = CSharpScript.Create<object>($"return {expected};");
var result = await script.EvaluateAsync();
Assert.Equal(expected, result);
}
[Fact]
public async Task NoReturn()
{
var script = CSharpScript.Create<object>("System.Console.WriteLine();");
var result = await script.EvaluateAsync();
Assert.Null(result);
}
[Fact]
public async Task ReturnAwait()
{
var script = CSharpScript.Create<int>("return await System.Threading.Tasks.Task.FromResult(42);");
var result = await script.EvaluateAsync();
Assert.Equal(42, result);
}
[Fact]
public async Task ReturnInNestedScopeNoTrailingExpression()
{
var script = CSharpScript.Create(@"
bool condition = false;
if (condition)
{
return 1;
}");
var result = await script.EvaluateAsync();
Assert.Null(result);
}
[Fact]
public async Task ReturnInNestedScopeWithTrailingVoidExpression()
{
var script = CSharpScript.Create(@"
bool condition = false;
if (condition)
{
return 1;
}
System.Console.WriteLine();");
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create(@"
bool condition = true;
if (condition)
{
return 1;
}
System.Console.WriteLine();");
result = await script.EvaluateAsync();
Assert.Equal(1, result);
}
[Fact]
public async Task ReturnInNestedScopeWithTrailingVoidExpressionAsInt()
{
var script = CSharpScript.Create<int>(@"
bool condition = false;
if (condition)
{
return 1;
}
System.Console.WriteLine();");
var result = await script.EvaluateAsync();
Assert.Equal(0, result);
script = CSharpScript.Create<int>(@"
bool condition = false;
if (condition)
{
return 1;
}
System.Console.WriteLine()");
result = await script.EvaluateAsync();
Assert.Equal(0, result);
}
[Fact]
public async Task ReturnIntWithTrailingDoubleExpression()
{
var script = CSharpScript.Create(@"
bool condition = false;
if (condition)
{
return 1;
}
1.1");
var result = await script.EvaluateAsync();
Assert.Equal(1.1, result);
script = CSharpScript.Create(@"
bool condition = true;
if (condition)
{
return 1;
}
1.1");
result = await script.EvaluateAsync();
Assert.Equal(1, result);
}
[Fact]
public async Task ReturnGenericAsInterface()
{
var script = CSharpScript.Create<IEnumerable<int>>(@"
if (false)
{
return new System.Collections.Generic.List<int> { 1, 2, 3 };
}");
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create<IEnumerable<int>>(@"
if (true)
{
return new System.Collections.Generic.List<int> { 1, 2, 3 };
}");
result = await script.EvaluateAsync();
Assert.Equal(new List<int> { 1, 2, 3 }, result);
}
[Fact]
public async Task ReturnNullable()
{
var script = CSharpScript.Create<int?>(@"
if (false)
{
return 42;
}");
var result = await script.EvaluateAsync();
Assert.False(result.HasValue);
script = CSharpScript.Create<int?>(@"
if (true)
{
return 42;
}");
result = await script.EvaluateAsync();
Assert.Equal(42, result);
}
[Fact]
public async Task ReturnInLoadedFile()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "return 42;"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create("#load \"a.csx\"", options);
var result = await script.EvaluateAsync();
Assert.Equal(42, result);
script = CSharpScript.Create(@"
#load ""a.csx""
-1", options);
result = await script.EvaluateAsync();
Assert.Equal(42, result);
}
[Fact]
public async Task ReturnInLoadedFileTrailingExpression()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", @"
if (false)
{
return 42;
}
1"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create("#load \"a.csx\"", options);
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create(@"
#load ""a.csx""
2", options);
result = await script.EvaluateAsync();
Assert.Equal(2, result);
}
[Fact]
public async Task ReturnInLoadedFileTrailingVoidExpression()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", @"
if (false)
{
return 1;
}
System.Console.WriteLine(42)"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create("#load \"a.csx\"", options);
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create(@"
#load ""a.csx""
2", options);
result = await script.EvaluateAsync();
Assert.Equal(2, result);
}
[Fact]
public async Task MultipleLoadedFilesWithTrailingExpression()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "1"),
KeyValuePair.Create("b.csx", @"
#load ""a.csx""
2"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create("#load \"b.csx\"", options);
var result = await script.EvaluateAsync();
Assert.Null(result);
resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "1"),
KeyValuePair.Create("b.csx", "2"));
options = ScriptOptions.Default.WithSourceResolver(resolver);
script = CSharpScript.Create(@"
#load ""a.csx""
#load ""b.csx""", options);
result = await script.EvaluateAsync();
Assert.Null(result);
resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "1"),
KeyValuePair.Create("b.csx", "2"));
options = ScriptOptions.Default.WithSourceResolver(resolver);
script = CSharpScript.Create(@"
#load ""a.csx""
#load ""b.csx""
3", options);
result = await script.EvaluateAsync();
Assert.Equal(3, result);
}
[Fact]
public async Task MultipleLoadedFilesWithReturnAndTrailingExpression()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "return 1;"),
KeyValuePair.Create("b.csx", @"
#load ""a.csx""
2"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create("#load \"b.csx\"", options);
var result = await script.EvaluateAsync();
Assert.Equal(1, result);
resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "return 1;"),
KeyValuePair.Create("b.csx", "2"));
options = ScriptOptions.Default.WithSourceResolver(resolver);
script = CSharpScript.Create(@"
#load ""a.csx""
#load ""b.csx""", options);
result = await script.EvaluateAsync();
Assert.Equal(1, result);
resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", "return 1;"),
KeyValuePair.Create("b.csx", "2"));
options = ScriptOptions.Default.WithSourceResolver(resolver);
script = CSharpScript.Create(@"
#load ""a.csx""
#load ""b.csx""
return 3;", options);
result = await script.EvaluateAsync();
Assert.Equal(1, result);
}
[Fact]
public async Task LoadedFileWithReturnAndGoto()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", @"
goto EOF;
NEXT:
return 1;
EOF:;
2"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create(@"
#load ""a.csx""
goto NEXT;
return 3;
NEXT:;", options);
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create(@"
#load ""a.csx""
L1: goto EOF;
L2: return 3;
EOF:
EOF2: ;
4", options);
result = await script.EvaluateAsync();
Assert.Equal(4, result);
}
[Fact]
public async Task VoidReturn()
{
var script = CSharpScript.Create("return;");
var result = await script.EvaluateAsync();
Assert.Null(result);
script = CSharpScript.Create(@"
var b = true;
if (b)
{
return;
}
b");
result = await script.EvaluateAsync();
Assert.Null(result);
}
[Fact]
public async Task LoadedFileWithVoidReturn()
{
var resolver = TestSourceReferenceResolver.Create(
KeyValuePair.Create("a.csx", @"
var i = 42;
return;
i = -1;"));
var options = ScriptOptions.Default.WithSourceResolver(resolver);
var script = CSharpScript.Create<int>(@"
#load ""a.csx""
i", options);
var result = await script.EvaluateAsync();
Assert.Equal(0, result);
}
}
}
......@@ -171,13 +171,10 @@ private int RunScript(ScriptOptions options, string code, ErrorLogger errorLogge
var globals = new CommandLineScriptGlobals(_console.Out, _objectFormatter);
globals.Args.AddRange(_compiler.Arguments.ScriptArguments);
var script = Script.CreateInitialScript<object>(_scriptCompiler, code, options, globals.GetType(), assemblyLoaderOpt: null);
var script = Script.CreateInitialScript<int>(_scriptCompiler, code, options, globals.GetType(), assemblyLoaderOpt: null);
try
{
script.RunAsync(globals, cancellationToken).Wait();
// TODO: use the return value of the script https://github.com/dotnet/roslyn/issues/5773
return CommonCompiler.Succeeded;
return script.RunAsync(globals, cancellationToken).Result.ReturnValue;
}
catch (CompilationErrorException e)
{
......
......@@ -249,21 +249,7 @@ public Compilation GetCompilation()
// TODO: remove
internal bool HasReturnValue()
{
bool hasValue;
var resultType = GetCompilation().GetSubmissionResultType(out hasValue);
if (hasValue)
{
if (resultType != null && resultType.SpecialType == SpecialType.System_Void)
{
return false;
}
else
{
return true;
}
}
return false;
return GetCompilation().HasSubmissionResult();
}
}
......
......@@ -116,7 +116,6 @@
<DesignTime>True</DesignTime>
<DependentUpon>TestResource.resx</DependentUpon>
</Compile>
<Compile Include="TestSourceReferenceResolver.cs" />
<Compile Include="TraceListener.cs" />
<Compile Include="Traits.cs" />
<Compile Include="Win32Res.cs" />
......
......@@ -53,6 +53,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Metadata\PEModuleTestHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mocks\TestDocumentationCommentProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mocks\TestMissingMetadataReferenceResolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mocks\TestSourceReferenceResolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Mocks\VirtualizedRelativePathResolver.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Pdb\MockSymWriter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Pdb\PdbTestUtilities.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册