From cec7a9858f88c9c293c9e23039960d056696cda5 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Wed, 16 Sep 2015 16:39:52 -0700 Subject: [PATCH] Allow goto in scripts --- .../BinderFactory.BinderFactoryVisitor.cs | 6 +- .../Portable/Binder/Binder_Expressions.cs | 13 +- .../Portable/Binder/Binder_Initializers.cs | 40 +-- .../CSharp/Portable/Binder/Binder_Lookup.cs | 20 +- .../Portable/Binder/Binder_Statements.cs | 18 +- .../Portable/Binder/LocalBinderFactory.cs | 1 - .../Portable/Binder/LocalScopeBinder.cs | 27 +- .../CSharp/Portable/Binder/NameofBinder.cs | 10 +- .../Portable/Binder/ScriptLocalScopeBinder.cs | 70 +++++ .../CSharp/Portable/CSharpCodeAnalysis.csproj | 1 + .../Compilation/CSharpSemanticModel.cs | 7 +- .../Compilation/MethodBodySemanticModel.cs | 14 +- .../Compilation/SyntaxTreeSemanticModel.cs | 23 +- .../Portable/Syntax/SyntaxNodeExtensions.cs | 3 - .../CSharp/Test/Emit/CodeGen/GotoTest.cs | 257 ++++++++++++++++-- .../Semantics/ScriptSemanticsTests.cs | 32 +++ .../Test/Semantic/Semantics/SwitchTests.cs | 4 +- .../Symbol/Compilation/LoadDirectiveTests.cs | 23 +- .../Test/Emit/CodeGen/CodeGenScriptTests.vb | 14 + .../Desktop/TestSourceReferenceResolver.cs | 6 + 20 files changed, 467 insertions(+), 122 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/Binder/ScriptLocalScopeBinder.cs diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 8a8deefe242..df4b443133a 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -764,9 +764,9 @@ private InContainerBinder MakeNamespaceBinder(CSharpSyntaxNode node, NameSyntax public override Binder VisitCompilationUnit(CompilationUnitSyntax parent) { return VisitCompilationUnit( - parent, - inUsing: IsInUsing(parent), - inScript: InScript); + parent, + inUsing: IsInUsing(parent), + inScript: InScript); } internal InContainerBinder VisitCompilationUnit(CompilationUnitSyntax compilationUnit, bool inUsing, bool inScript) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index e1f87af1ebf..59359e07ef7 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -852,8 +852,9 @@ private BoundExpression BindDefaultExpression(DefaultExpressionSyntax node, Diag options |= LookupOptions.MustNotBeMethodTypeParameter; } + var name = node.Identifier.ValueText; HashSet useSiteDiagnostics = null; - this.LookupSymbolsWithFallback(lookupResult, node.Identifier.ValueText, arity: arity, useSiteDiagnostics: ref useSiteDiagnostics, options: options); + this.LookupSymbolsWithFallback(lookupResult, name, arity: arity, useSiteDiagnostics: ref useSiteDiagnostics, options: options); diagnostics.Add(node, useSiteDiagnostics); if (lookupResult.Kind != LookupResultKind.Empty) @@ -862,7 +863,7 @@ private BoundExpression BindDefaultExpression(DefaultExpressionSyntax node, Diag bool isError = false; bool wasError; var members = ArrayBuilder.GetInstance(); - Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, node.Identifier.ValueText, node.Arity, members, diagnostics, out wasError); // reports diagnostics in result. + Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, name, node.Arity, members, diagnostics, out wasError); // reports diagnostics in result. isError |= wasError; @@ -876,7 +877,7 @@ private BoundExpression BindDefaultExpression(DefaultExpressionSyntax node, Diag typeArgumentList, typeArguments, receiver, - node.Identifier.ValueText, + name, members, lookupResult, receiver != null ? BoundMethodGroupFlags.HasImplicitReceiver : BoundMethodGroupFlags.None, @@ -919,15 +920,15 @@ private BoundExpression BindDefaultExpression(DefaultExpressionSyntax node, Diag } else if (IsJoinRangeVariableInLeftKey(node)) { - Error(diagnostics, ErrorCode.ERR_QueryOuterKey, node, node.Identifier.ValueText); + Error(diagnostics, ErrorCode.ERR_QueryOuterKey, node, name); } else if (IsInJoinRightKey(node)) { - Error(diagnostics, ErrorCode.ERR_QueryInnerKey, node, node.Identifier.ValueText); + Error(diagnostics, ErrorCode.ERR_QueryInnerKey, node, name); } else { - Error(diagnostics, ErrorCode.ERR_NameNotInContext, node, node.Identifier.ValueText); + Error(diagnostics, ErrorCode.ERR_NameNotInContext, node, name); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs index 43f80fcc3f1..e55a89998a9 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -106,7 +107,7 @@ internal struct ProcessedFieldInitializers firstDebugImports = parentBinder.ImportChain; } - parentBinder = new LocalScopeBinder(parentBinder).WithAdditionalFlagsAndContainingMemberOrLambda(parentBinder.Flags | BinderFlags.FieldInitializer, fieldSymbol); + parentBinder = new LocalScopeBinder(parentBinder).WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.FieldInitializer, fieldSymbol); BoundFieldInitializer boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics); boundInitializers.Add(boundInitializer); @@ -137,6 +138,8 @@ internal struct ProcessedFieldInitializers // factory across siblings. Unfortunately, we cannot reuse the binder itself, because // individual fields might have their own binders (e.g. because of being declared unsafe). BinderFactory binderFactory = null; + // Label instances must be shared across all global statements. + ScriptLocalScopeBinder.Labels labels = null; for (int j = 0; j < siblingInitializers.Length; j++) { @@ -150,44 +153,47 @@ internal struct ProcessedFieldInitializers } var syntaxRef = initializer.Syntax; - Debug.Assert(syntaxRef.SyntaxTree.Options.Kind != SourceCodeKind.Regular); + var syntaxTree = syntaxRef.SyntaxTree; + Debug.Assert(syntaxTree.Options.Kind != SourceCodeKind.Regular); - var initializerNode = (CSharpSyntaxNode)syntaxRef.GetSyntax(); + var syntax = (CSharpSyntaxNode)syntaxRef.GetSyntax(); + var syntaxRoot = syntaxTree.GetCompilationUnitRoot(); if (binderFactory == null) { - binderFactory = compilation.GetBinderFactory(syntaxRef.SyntaxTree); + binderFactory = compilation.GetBinderFactory(syntaxTree); + labels = new ScriptLocalScopeBinder.Labels(scriptInitializer, syntaxRoot); } - Binder scriptClassBinder = binderFactory.GetBinder(initializerNode); - Debug.Assert(((ImplicitNamedTypeSymbol)scriptClassBinder.ContainingMemberOrLambda).IsScriptClass); + Binder scriptClassBinder = binderFactory.GetBinder(syntax); + Debug.Assert(((NamedTypeSymbol)scriptClassBinder.ContainingMemberOrLambda).IsScriptClass); if (firstDebugImports == null) { firstDebugImports = scriptClassBinder.ImportChain; } - Binder parentBinder = new ExecutableCodeBinder((CSharpSyntaxNode)syntaxRef.SyntaxTree.GetRoot(), scriptInitializer, scriptClassBinder); + Binder parentBinder = new ExecutableCodeBinder( + syntaxRoot, + scriptInitializer, + new ScriptLocalScopeBinder(labels, scriptClassBinder)); BoundInitializer boundInitializer; if ((object)fieldSymbol != null) { boundInitializer = BindFieldInitializer( - new LocalScopeBinder(parentBinder).WithAdditionalFlagsAndContainingMemberOrLambda(parentBinder.Flags | BinderFlags.FieldInitializer, fieldSymbol), + parentBinder.WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.FieldInitializer, fieldSymbol), fieldSymbol, - (EqualsValueClauseSyntax)initializerNode, + (EqualsValueClauseSyntax)syntax, diagnostics); } - else if (initializerNode.Kind() == SyntaxKind.LabeledStatement) - { - // TODO: labels in interactive - var boundStatement = new BoundBadStatement(initializerNode, ImmutableArray.Empty, true); - boundInitializer = new BoundGlobalStatementInitializer(initializerNode, boundStatement); - } else { - var collisionDetector = new LocalScopeBinder(parentBinder); - boundInitializer = BindGlobalStatement(collisionDetector, scriptInitializer, (StatementSyntax)initializerNode, diagnostics, + boundInitializer = BindGlobalStatement( + parentBinder, + scriptInitializer, + (StatementSyntax)syntax, + diagnostics, isLast: i == initializers.Length - 1 && j == siblingInitializers.Length - 1); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs index bf518743490..8b8a9d55d21 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs @@ -53,31 +53,36 @@ internal void LookupExtensionMethods(LookupResult result, string name, int arity /// /// Makes a second attempt if the results are not viable, in order to produce more detailed failure information (symbols and diagnostics). /// - private void LookupSymbolsWithFallback(LookupResult result, string name, int arity, ref HashSet useSiteDiagnostics, ConsList basesBeingResolved = null, LookupOptions options = LookupOptions.Default) + private Binder LookupSymbolsWithFallback(LookupResult result, string name, int arity, ref HashSet useSiteDiagnostics, ConsList basesBeingResolved = null, LookupOptions options = LookupOptions.Default) { Debug.Assert(options.AreValid()); // don't create diagnosis instances unless lookup fails - this.LookupSymbolsInternal(result, name, arity, basesBeingResolved, options, diagnose: false, useSiteDiagnostics: ref useSiteDiagnostics); + var binder = this.LookupSymbolsInternal(result, name, arity, basesBeingResolved, options, diagnose: false, useSiteDiagnostics: ref useSiteDiagnostics); + Debug.Assert((binder != null) || result.IsClear); + if (result.Kind != LookupResultKind.Viable && result.Kind != LookupResultKind.Empty) { result.Clear(); // retry to get diagnosis - this.LookupSymbolsInternal(result, name, arity, basesBeingResolved, options, diagnose: true, useSiteDiagnostics: ref useSiteDiagnostics); + var otherBinder = this.LookupSymbolsInternal(result, name, arity, basesBeingResolved, options, diagnose: true, useSiteDiagnostics: ref useSiteDiagnostics); + Debug.Assert(binder == otherBinder); } Debug.Assert(result.IsMultiViable || result.IsClear || result.Error != null); + return binder; } - private void LookupSymbolsInternal( + private Binder LookupSymbolsInternal( LookupResult result, string name, int arity, ConsList basesBeingResolved, LookupOptions options, bool diagnose, ref HashSet useSiteDiagnostics) { Debug.Assert(result.IsClear); Debug.Assert(options.AreValid()); + Binder binder = null; for (var scope = this; scope != null && !result.IsMultiViable; scope = scope.Next) { - if (!result.IsClear) + if (binder != null) { var tmp = LookupResult.GetInstance(); scope.LookupSymbolsInSingleBinder(tmp, name, arity, basesBeingResolved, options, this, diagnose, ref useSiteDiagnostics); @@ -87,8 +92,13 @@ private void LookupSymbolsWithFallback(LookupResult result, string name, int ari else { scope.LookupSymbolsInSingleBinder(result, name, arity, basesBeingResolved, options, this, diagnose, ref useSiteDiagnostics); + if (!result.IsClear) + { + binder = scope; + } } } + return binder; } internal virtual void LookupSymbolsInSingleBinder( diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 3f6a3114ab6..248b7787100 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -342,7 +342,7 @@ private BoundLabeledStatement BindLabeled(LabeledStatementSyntax node, Diagnosti var result = LookupResult.GetInstance(); HashSet useSiteDiagnostics = null; - this.LookupSymbolsWithFallback(result, node.Identifier.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, options: LookupOptions.LabelsOnly); + var binder = this.LookupSymbolsWithFallback(result, node.Identifier.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, options: LookupOptions.LabelsOnly); // result.Symbols can be empty in some malformed code, e.g. when a labeled statement is used an embedded statement in an if or foreach statement // In this case we create new label symbol on the fly, and an error is reported by parser @@ -357,14 +357,18 @@ private BoundLabeledStatement BindLabeled(LabeledStatementSyntax node, Diagnosti } // check to see if this label (illegally) hides a label from an enclosing scope - result.Clear(); - this.Next.LookupSymbolsWithFallback(result, node.Identifier.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, options: LookupOptions.LabelsOnly); - if (result.IsMultiViable) + if (binder != null) { - // The label '{0}' shadows another label by the same name in a contained scope - Error(diagnostics, ErrorCode.ERR_LabelShadow, node.Identifier, node.Identifier.ValueText); - hasError = true; + result.Clear(); + binder.Next.LookupSymbolsWithFallback(result, node.Identifier.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, options: LookupOptions.LabelsOnly); + if (result.IsMultiViable) + { + // The label '{0}' shadows another label by the same name in a contained scope + Error(diagnostics, ErrorCode.ERR_LabelShadow, node.Identifier, node.Identifier.ValueText); + hasError = true; + } } + diagnostics.Add(node, useSiteDiagnostics); result.Free(); diff --git a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs index d0f5a36b9b3..bfbbee4e2ae 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalBinderFactory.cs @@ -1,7 +1,6 @@ // 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.Diagnostics; -using Microsoft.CodeAnalysis.Collections; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Roslyn.Utilities; diff --git a/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs b/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs index 1bebe1f21d9..64472c968f0 100644 --- a/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs @@ -159,22 +159,25 @@ protected SourceLocalSymbol MakeLocal(VariableDeclarationSyntax declaration, Var protected void BuildLabels(SyntaxList statements, ref ArrayBuilder labels) { var containingMethod = (MethodSymbol)this.ContainingMemberOrLambda; - foreach (var statement in statements) { - var stmt = statement; - while (stmt.Kind() == SyntaxKind.LabeledStatement) - { - var labeledStatement = (LabeledStatementSyntax)stmt; - if (labels == null) - { - labels = ArrayBuilder.GetInstance(); - } + BuildLabels(containingMethod, statement, ref labels); + } + } - var labelSymbol = new SourceLabelSymbol(containingMethod, labeledStatement.Identifier); - labels.Add(labelSymbol); - stmt = labeledStatement.Statement; + internal static void BuildLabels(MethodSymbol containingMethod, StatementSyntax statement, ref ArrayBuilder labels) + { + while (statement.Kind() == SyntaxKind.LabeledStatement) + { + var labeledStatement = (LabeledStatementSyntax)statement; + if (labels == null) + { + labels = ArrayBuilder.GetInstance(); } + + var labelSymbol = new SourceLabelSymbol(containingMethod, labeledStatement.Identifier); + labels.Add(labelSymbol); + statement = labeledStatement.Statement; } } diff --git a/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs b/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs index 9dbfa3d0017..4953eb96222 100644 --- a/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/NameofBinder.cs @@ -1,16 +1,8 @@ // 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.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Threading; -using Microsoft.CodeAnalysis.CSharp.Symbols; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Roslyn.Utilities; - namespace Microsoft.CodeAnalysis.CSharp { - internal class NameofBinder : Binder + internal sealed class NameofBinder : Binder { private readonly SyntaxNode _nameofArgument; diff --git a/src/Compilers/CSharp/Portable/Binder/ScriptLocalScopeBinder.cs b/src/Compilers/CSharp/Portable/Binder/ScriptLocalScopeBinder.cs new file mode 100644 index 00000000000..6fbea6a7ee4 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Binder/ScriptLocalScopeBinder.cs @@ -0,0 +1,70 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed class ScriptLocalScopeBinder : LocalScopeBinder + { + private readonly Labels _labels; + + internal ScriptLocalScopeBinder(Labels labels, Binder next) : base(next) + { + _labels = labels; + } + + internal override Symbol ContainingMemberOrLambda + { + get { return _labels.ScriptInitializer; } + } + + protected override ImmutableArray BuildLabels() + { + return _labels.GetLabels(); + } + + // Labels potentially shared across multiple ScriptLocalScopeBinder instances. + new internal sealed class Labels + { + private readonly SynthesizedInteractiveInitializerMethod _scriptInitializer; + private readonly CompilationUnitSyntax _syntax; + private ImmutableArray _lazyLabels; + + internal Labels(SynthesizedInteractiveInitializerMethod scriptInitializer, CompilationUnitSyntax syntax) + { + _scriptInitializer = scriptInitializer; + _syntax = syntax; + } + + internal SynthesizedInteractiveInitializerMethod ScriptInitializer + { + get { return _scriptInitializer; } + } + + internal ImmutableArray GetLabels() + { + if (_lazyLabels == null) + { + ImmutableInterlocked.InterlockedInitialize(ref _lazyLabels, GetLabels(_scriptInitializer, _syntax)); + } + return _lazyLabels; + } + + private static ImmutableArray GetLabels(SynthesizedInteractiveInitializerMethod scriptInitializer, CompilationUnitSyntax syntax) + { + var builder = ArrayBuilder.GetInstance(); + foreach (var member in syntax.Members) + { + if (member.Kind() != SyntaxKind.GlobalStatement) + { + continue; + } + LocalScopeBinder.BuildLabels(scriptInitializer, ((GlobalStatementSyntax)member).Statement, ref builder); + } + return builder.ToImmutableAndFree(); + } + } + } +} diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index cc8389b53b5..3381a3c3613 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -141,6 +141,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 1aead3df0a2..2f2e83f2bda 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -1368,10 +1368,11 @@ private void CheckModelAndSyntaxNodeToSpeculate(CSharpSyntaxNode syntax) container = baseType; } - if (!binder.IsInMethodBody && (options & LookupOptions.NamespacesOrTypesOnly) == 0) + if (!binder.IsInMethodBody && + (options & (LookupOptions.NamespaceAliasesOnly | LookupOptions.NamespacesOrTypesOnly | LookupOptions.LabelsOnly)) == 0) { - // Method type parameters are not in scope outside a method body unless - // the position is either: + // Method type parameters are not in scope outside a method + // body unless the position is either: // a) in a type-only context inside an expression, or // b) inside of an XML name attribute in an XML doc comment. var parentExpr = token.Parent as ExpressionSyntax; diff --git a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs index 856a10eca66..5f7afa49ff4 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MethodBodySemanticModel.cs @@ -9,15 +9,13 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed class MethodBodySemanticModel : MemberSemanticModel { - private DiagnosticBag _ignoredDiagnostics = new DiagnosticBag(); - private MethodBodySemanticModel(CSharpCompilation compilation, Symbol owner, Binder rootBinder, CSharpSyntaxNode syntax, SyntaxTreeSemanticModel parentSemanticModelOpt = null, int speculatedPosition = 0) : base(compilation, syntax, owner, rootBinder, parentSemanticModelOpt, speculatedPosition) { Debug.Assert((object)owner != null); Debug.Assert(owner.Kind == SymbolKind.Method); Debug.Assert(syntax != null); - Debug.Assert(owner.ContainingType.IsScriptClass || syntax.Kind() != SyntaxKind.CompilationUnit); + Debug.Assert(syntax.Kind() != SyntaxKind.CompilationUnit); } /// @@ -29,16 +27,6 @@ internal static MethodBodySemanticModel Create(CSharpCompilation compilation, Me return new MethodBodySemanticModel(compilation, owner, executableCodeBinder, syntax); } - /// - /// Creates a SemanticModel for an ArrowExpressionClause, which includes - /// an ExecutableCodeBinder and a ScopedExpressionBinder. - /// - internal static MethodBodySemanticModel Create(CSharpCompilation compilation, MethodSymbol owner, Binder rootBinder, ArrowExpressionClauseSyntax syntax) - { - Binder binder = new ExecutableCodeBinder(syntax, owner, rootBinder); - return new MethodBodySemanticModel(compilation, owner, binder, syntax); - } - internal override BoundNode Bind(Binder binder, CSharpSyntaxNode node, DiagnosticBag diagnostics) { if (node.Kind() == SyntaxKind.ArrowExpressionClause) diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index 5a0c530ec75..575fe9c795a 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1,7 +1,6 @@ // 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.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -26,6 +25,7 @@ internal partial class SyntaxTreeSemanticModel : CSharpSemanticModel private readonly BinderFactory _binderFactory; private Func _createMemberModelFunction; private readonly bool _ignoresAccessibility; + private ScriptLocalScopeBinder.Labels _globalStatementLabels; private static readonly Func s_isMemberDeclarationFunction = IsMemberDeclaration; @@ -979,14 +979,27 @@ private MemberSemanticModel CreateMemberModel(CSharpSyntaxNode node) case SyntaxKind.GlobalStatement: { Debug.Assert(!this.IsRegularCSharp); + var parent = node.Parent; // TODO (tomat): handle misplaced global statements - if (node.Parent.Kind() == SyntaxKind.CompilationUnit) + if (parent.Kind() == SyntaxKind.CompilationUnit) { - var scriptConstructor = this.Compilation.ScriptClass.InstanceConstructors.First(); + var scriptInitializer = _compilation.ScriptClass.GetScriptInitializer(); + Debug.Assert((object)scriptInitializer != null); + if ((object)scriptInitializer == null) + { + return null; + } + + // Share labels across all global statements. + if (_globalStatementLabels == null) + { + Interlocked.CompareExchange(ref _globalStatementLabels, new ScriptLocalScopeBinder.Labels(scriptInitializer, (CompilationUnitSyntax)parent), null); + } + return MethodBodySemanticModel.Create( this.Compilation, - scriptConstructor, - outer, + scriptInitializer, + new ScriptLocalScopeBinder(_globalStatementLabels, outer), node); } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs index b0e95d06809..584444acf25 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxNodeExtensions.cs @@ -1,10 +1,7 @@ // 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.Diagnostics; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs index b6b27e46066..12cb20d3b69 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/GotoTest.cs @@ -3,11 +3,12 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Roslyn.Test.Utilities; +using Roslyn.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen { - public class GotoStatementTest : EmitMetadataTestBase + public class GotoTests : EmitMetadataTestBase { [Fact] public void Goto() @@ -804,34 +805,256 @@ public static int Main() CompileAndVerify(text, expectedOutput: "Catch"); } - [Fact(Skip = "3712"), WorkItem(3712)] - public void Goto_Script() + [Fact] + public void OutOfScriptBlock() { - string source = @" -using System; + string source = +@"bool b = true; +L0: ; +{ + { + System.Console.WriteLine(b); + if (b) b = !b; + else goto L1; + goto L0; + } + L1: ; +}"; + string expectedOutput = +@"True +False"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script, options: TestOptions.DebugExe); + CompileAndVerify(compilation, expectedOutput: expectedOutput, verify: false); + } -Console.WriteLine(""a""); -goto C; -Console.Write(""you won't see me""); -C: Console.WriteLine(""b""); -"; - string expectedOutput = @"a -b -"; - CompileAndVerify(source, parseOptions: new CSharpParseOptions(kind: SourceCodeKind.Script), expectedOutput: expectedOutput); + [Fact] + public void IntoScriptBlock() + { + string source = +@"goto L0; +{ + L0: goto L1; +} +{ + L1: ; +}"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script, options: TestOptions.DebugExe); + compilation.VerifyDiagnostics( + // (1,6): error CS0159: No such label 'L0' within the scope of the goto statement + // goto L0; + Diagnostic(ErrorCode.ERR_LabelNotFound, "L0").WithArguments("L0").WithLocation(1, 6), + // (3,14): error CS0159: No such label 'L1' within the scope of the goto statement + // L0: goto L1; + Diagnostic(ErrorCode.ERR_LabelNotFound, "L1").WithArguments("L1").WithLocation(3, 14), + // (3,5): warning CS0164: This label has not been referenced + // L0: goto L1; + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L0").WithLocation(3, 5), + // (6,5): warning CS0164: This label has not been referenced + // L1: ; + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L1").WithLocation(6, 5)); + } + + [Fact] + public void AcrossScriptDeclarations() + { + string source = +@"int P { get; } = G(""P""); +L: +int F = G(""F""); +int Q { get; } = G(""Q""); +static int x = 2; +static int G(string s) +{ + System.Console.WriteLine(""{0}: {1}"", x, s); + x++; + return x; +} +if (Q < 4) goto L;"; + string expectedOutput = +@"2: P +3: F +4: Q"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script, options: TestOptions.DebugExe); + CompileAndVerify(compilation, expectedOutput: expectedOutput, verify: false); + } + + [Fact] + public void AcrossSubmissions() + { + var references = new[] { MscorlibRef_v4_0_30316_17626, SystemCoreRef }; + var source0 = +@"bool b = false; +L: ; +if (b) +{ + goto L; +}"; + var source1 = +@"goto L;"; + var s0 = CSharpCompilation.CreateScriptCompilation("s0.dll", SyntaxFactory.ParseSyntaxTree(source0, options: TestOptions.Script), references); + s0.VerifyDiagnostics(); + var s1 = CSharpCompilation.CreateScriptCompilation("s1.dll", SyntaxFactory.ParseSyntaxTree(source1, options: TestOptions.Script), references, previousScriptCompilation: s0); + s1.VerifyDiagnostics( + // (1,6): error CS0159: No such label 'L' within the scope of the goto statement + // goto L; + Diagnostic(ErrorCode.ERR_LabelNotFound, "L").WithArguments("L").WithLocation(1, 6)); + } + + [Fact] + public void OutOfScriptMethod() + { + string source = +@"static void F(bool b) +{ + if (b) goto L; +} +L: +F(true);"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script); + compilation.VerifyDiagnostics( + // (5,1): warning CS0164: This label has not been referenced + // L: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L").WithLocation(5, 1), + // (3,17): error CS0159: No such label 'L' within the scope of the goto statement + // if (b) goto L; + Diagnostic(ErrorCode.ERR_LabelNotFound, "L").WithArguments("L").WithLocation(3, 17)); + } + + [Fact] + public void IntoScriptMethod() + { + string source = +@"static void F() +{ +L: + return; +} +goto L;"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script); + compilation.VerifyDiagnostics( + // (6,6): error CS0159: No such label 'L' within the scope of the goto statement + // goto L; + Diagnostic(ErrorCode.ERR_LabelNotFound, "L").WithArguments("L").WithLocation(6, 6), + // (3,1): warning CS0164: This label has not been referenced + // L: + Diagnostic(ErrorCode.WRN_UnreferencedLabel, "L").WithLocation(3, 1)); + } + + [Fact] + public void InScriptSwitch() + { + string source = +@"int x = 3; +switch (x) +{ +case 1: + break; +case 2: + System.Console.WriteLine(x); + break; +default: + goto case 2; +}"; + string expectedOutput = +@"3"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script, options: TestOptions.DebugExe); + CompileAndVerify(compilation, expectedOutput: expectedOutput, verify: false); + } + + [Fact] + public void DuplicateLabelInScript() + { + string source = +@"bool b = false; +L: ; +if (b) +{ + goto L; +} +else +{ + b = !b; + if (b) goto L; +L: ; +}"; + var compilation = CreateCompilationWithMscorlib45(source, references: new[] { SystemCoreRef }, parseOptions: TestOptions.Script, options: TestOptions.DebugExe); + compilation.VerifyDiagnostics( + // (11,1): error CS0158: The label 'L' shadows another label by the same name in a contained scope + // L: ; + Diagnostic(ErrorCode.ERR_LabelShadow, "L").WithArguments("L").WithLocation(11, 1)); + } + + [Fact] + public void DuplicateLabelInSeparateSubmissions() + { + var references = new[] { MscorlibRef_v4_0_30316_17626, SystemCoreRef }; + var source0 = +@"bool b = false; +L: ; +if (b) +{ + goto L; +}"; + var source1 = +@"if (!b) +{ + b = !b; + if (b) goto L; +L: ; +}"; + var s0 = CSharpCompilation.CreateScriptCompilation("s0.dll", SyntaxFactory.ParseSyntaxTree(source0, options: TestOptions.Script), references); + s0.VerifyDiagnostics(); + var s1 = CSharpCompilation.CreateScriptCompilation("s1.dll", SyntaxFactory.ParseSyntaxTree(source1, options: TestOptions.Script), references, previousScriptCompilation: s0); + s1.VerifyDiagnostics(); + } + + [Fact] + public void LoadedFile() + { + var sourceA = +@"goto A; +A: goto B;"; + var sourceB = +@"#load ""a.csx"" +goto B; +B: goto A;"; + var resolver = TestSourceReferenceResolver.Create(KeyValuePair.Create("a.csx", sourceA)); + var options = TestOptions.DebugDll.WithSourceReferenceResolver(resolver); + var compilation = CreateCompilationWithMscorlib45(sourceB, options: options, parseOptions: TestOptions.Script); + compilation.GetDiagnostics().Verify( + // a.csx(2,9): error CS0159: No such label 'B' within the scope of the goto statement + // A: goto B; + Diagnostic(ErrorCode.ERR_LabelNotFound, "B").WithArguments("B").WithLocation(2, 9), + // (3,9): error CS0159: No such label 'A' within the scope of the goto statement + // B: goto A; + Diagnostic(ErrorCode.ERR_LabelNotFound, "A").WithArguments("A").WithLocation(3, 9)); + } + + [Fact, WorkItem(3712)] + public void Label_GetDeclaredSymbol_Script() + { + string source = +@"L0: goto L1; +static void F() { } +L1: goto L0;"; + var tree = Parse(source, options: TestOptions.Script); + var model = CreateCompilationWithMscorlib45(new[] { tree }).GetSemanticModel(tree, ignoreAccessibility: false); + var label = (LabeledStatementSyntax)tree.FindNodeOrTokenByKind(SyntaxKind.LabeledStatement); + var symbol = model.GetDeclaredSymbol(label); + Assert.Equal("L0", symbol.Name); } - [Fact(Skip = "3712"), WorkItem(3712)] + [Fact, WorkItem(3712)] public void Label_GetDeclaredSymbol_Error_Script() { string source = @" C: \a\b\ "; - var tree = Parse(source, options: new CSharpParseOptions(kind: SourceCodeKind.Script)); + var tree = Parse(source, options: TestOptions.Script); var model = CreateCompilationWithMscorlib45(new[] { tree }).GetSemanticModel(tree, ignoreAccessibility: false); var label = (LabeledStatementSyntax)tree.FindNodeOrTokenByKind(SyntaxKind.LabeledStatement); var symbol = model.GetDeclaredSymbol(label); - // TODO: Add some verification for symbol... + Assert.Equal("C", symbol.Name); } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ScriptSemanticsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ScriptSemanticsTests.cs index e24379acac5..eb41a233de9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ScriptSemanticsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ScriptSemanticsTests.cs @@ -298,6 +298,38 @@ public void LabelLookup() Assert.Empty(model.LookupLabels(source.Length - 1)); // Used to assert. } + [Fact] + public void Labels() + { + string source = +@"L0: ; +goto L0;"; + var tree = Parse(source, options: TestOptions.Script); + var model = CreateCompilationWithMscorlib45(new[] { tree }).GetSemanticModel(tree, ignoreAccessibility: false); + var root = tree.GetCompilationUnitRoot(); + var statements = root.ChildNodes().Select(n => ((GlobalStatementSyntax)n).Statement).ToArray(); + var symbol0 = model.GetDeclaredSymbol((LabeledStatementSyntax)statements[0]); + Assert.NotNull(symbol0); + var symbol1 = model.GetSymbolInfo(((GotoStatementSyntax)statements[1]).Expression).Symbol; + Assert.Same(symbol0, symbol1); + } + + [Fact] + public void Variables() + { + string source = +@"int x = 1; +object y = x;"; + var tree = Parse(source, options: TestOptions.Script); + var model = CreateCompilationWithMscorlib45(new[] { tree }).GetSemanticModel(tree, ignoreAccessibility: false); + var root = tree.GetCompilationUnitRoot(); + var declarations = root.ChildNodes().Select(n => ((FieldDeclarationSyntax)n).Declaration.Variables[0]).ToArray(); + var symbol0 = model.GetDeclaredSymbol(declarations[0]); + Assert.NotNull(symbol0); + var symbol1 = model.GetSymbolInfo(declarations[1].Initializer.Value).Symbol; + Assert.Same(symbol0, symbol1); + } + [WorkItem(543890)] [Fact] public void ThisIndexerAccessInSubmission() diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SwitchTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SwitchTests.cs index 227e9204505..e31c51c5d85 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SwitchTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SwitchTests.cs @@ -2115,7 +2115,7 @@ static int Main() } [Fact] - public void SwitchFallOut_Script1() + public void SwitchFallOut_Script() { var source = @"using System; @@ -2137,7 +2137,7 @@ public void SwitchFallOut_Script1() } [Fact] - public void SwitchFallOut_Script2() + public void SwitchFallOut_Submission() { var source = @"using System; diff --git a/src/Compilers/CSharp/Test/Symbol/Compilation/LoadDirectiveTests.cs b/src/Compilers/CSharp/Test/Symbol/Compilation/LoadDirectiveTests.cs index 98ff5628628..d3f4deefd9e 100644 --- a/src/Compilers/CSharp/Test/Symbol/Compilation/LoadDirectiveTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Compilation/LoadDirectiveTests.cs @@ -1,9 +1,9 @@ // 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.Generic; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; -using Xunit; using Roslyn.Test.Utilities; +using Roslyn.Utilities; +using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests { @@ -39,8 +39,8 @@ public void MissingFile() public void FileWithErrors() { var code = "#load \"a.csx\""; - var resolver = CreateResolver( - Script("a.csx", @" + var resolver = TestSourceReferenceResolver.Create( + KeyValuePair.Create("a.csx", @" #load ""b.csx"" asdf();")); var options = TestOptions.DebugDll.WithSourceReferenceResolver(resolver); @@ -72,20 +72,5 @@ public void NoSourceReferenceResolver() // #load "test" Diagnostic(ErrorCode.ERR_SourceFileReferencesNotSupported, @"#load ""test""").WithLocation(1, 1)); } - - private static SourceReferenceResolver CreateResolver(params KeyValuePair[] scripts) - { - var sources = new Dictionary(); - foreach (var script in scripts) - { - sources.Add(script.Key, script.Value); - } - return TestSourceReferenceResolver.Create(sources); - } - - private static KeyValuePair Script(string path, string source) - { - return new KeyValuePair(path, source); - } } } diff --git a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenScriptTests.vb b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenScriptTests.vb index c69d1400901..58bbf3981b4 100644 --- a/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenScriptTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/CodeGen/CodeGenScriptTests.vb @@ -398,6 +398,20 @@ System.Console.Write("complete") }") End Sub + + Public Sub ScriptEntryPoint_MissingMethods() + Dim comp = CreateCompilationWithMscorlib( + + + , + parseOptions:=TestOptions.Script, + options:=TestOptions.DebugExe) + comp.VerifyDiagnostics( + Diagnostic(ERRID.ERR_MissingRuntimeHelper).WithArguments("Task.GetAwaiter").WithLocation(1, 1)) + End Sub + End Class End Namespace diff --git a/src/Test/Utilities/Desktop/TestSourceReferenceResolver.cs b/src/Test/Utilities/Desktop/TestSourceReferenceResolver.cs index 2df85330249..956f4c68d9d 100644 --- a/src/Test/Utilities/Desktop/TestSourceReferenceResolver.cs +++ b/src/Test/Utilities/Desktop/TestSourceReferenceResolver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Text; using Microsoft.CodeAnalysis; @@ -12,6 +13,11 @@ public sealed class TestSourceReferenceResolver : SourceReferenceResolver { public static readonly SourceReferenceResolver Default = new TestSourceReferenceResolver(sources: null); + public static SourceReferenceResolver Create(params KeyValuePair[] sources) + { + return TestSourceReferenceResolver.Create(sources.ToDictionary(p => p.Key, p => p.Value)); + } + public static SourceReferenceResolver Create(Dictionary sources = null) { return (sources == null || sources.Count == 0) ? Default : new TestSourceReferenceResolver(sources); -- GitLab