diff --git a/docs/features/outvar.md b/docs/features/outvar.md index 40abac585ef04..43c378988926f 100644 --- a/docs/features/outvar.md +++ b/docs/features/outvar.md @@ -25,3 +25,41 @@ An *out variable* may not be referenced before the close parenthesis of the invo ``` > **Note**: There is a discussion thread for this feature at https://github.com/dotnet/roslyn/issues/6183 + + +**ArgumentSyntax node is extended as follows to accommodate for the new syntax:** + +- ```Expression``` field is made optional. +- Two new optional fields are added. +``` + + +``` + + +**SemanticModel changes:** + +Added new API +``` + /// + /// Given an argument syntax, get symbol for an out var that it declares, if any. + /// + /// The syntax node that declares a variable. + /// The cancellation token. + /// The symbol that was declared. + public ISymbol GetDeclaredSymbol(ArgumentSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)); +``` + + +**Open issues:** + +- Syntax model is still in flux. +- Need to get confirmation from LDM that we really want to make these variables read-only. For now they are just regular, writable, variables. +- Need to get confirmation from LDM that we really want to disallow referencing out variables the declaring argument list. It seems nothing prevents us from allowing such references for explicitly typed variables. Allowing them for now. + + +**TODO:** + +[ ] Add tests for scope rules. Given that currently scoping rules match the rules for pattern variables, and implementation takes advantage of existing infrastructure added for pattern variables, the priority of adding these tests is low. We have pretty good suite of tests for pattern variables. + +[ ] Need to get an approval for the new SemanticModel.GetDeclaredSymbol API. diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index c388ef2e7e0b8..1a2f34a324a07 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -289,10 +289,9 @@ private AnalyzedAttributeArguments BindAttributeArguments(AttributeArgumentListS diagnostics, hadError, argument, - argument.Expression, + BindArgumentExpression(diagnostics, argument.Expression, RefKind.None, allowArglist: false), argument.NameColon, - refKind: RefKind.None, - allowArglist: false); + refKind: RefKind.None); if (boundNamedArgumentsBuilder != null) { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 02ae0764d79ce..465e2856a274a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -2023,6 +2023,11 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn return true; } + if (argumentSyntax.Expression == null) + { + return true; + } + switch (argumentSyntax.Expression.Kind()) { // The next 3 cases should never be allowed as they cannot be ref/out. Assuming a bug in legacy compiler. @@ -2056,15 +2061,16 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn // Note: Some others should still be rejected when ref/out present. See RefMustBeObeyed. RefKind refKind = origRefKind == RefKind.None || RefMustBeObeyed(isDelegateCreation, argumentSyntax) ? origRefKind : RefKind.None; + BoundExpression boundArgument = BindArgumentValue(diagnostics, argumentSyntax, allowArglist, refKind); + hadError |= BindArgumentAndName( result, diagnostics, hadError, argumentSyntax, - argumentSyntax.Expression, + boundArgument, argumentSyntax.NameColon, - refKind, - allowArglist); + refKind); // check for ref/out property/indexer, only needed for 1 parameter version if (!hadError && isDelegateCreation && origRefKind != RefKind.None && result.Arguments.Count == 1) @@ -2081,6 +2087,48 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn return hadError; } + private BoundExpression BindArgumentValue(DiagnosticBag diagnostics, ArgumentSyntax argumentSyntax, bool allowArglist, RefKind refKind) + { + if (argumentSyntax.Expression != null) + { + return BindArgumentExpression(diagnostics, argumentSyntax.Expression, refKind, allowArglist); + } + + var typeSyntax = argumentSyntax.Type; + + bool isConst = false; + bool isVar; + AliasSymbol alias; + TypeSymbol declType = BindVariableType(argumentSyntax, diagnostics, typeSyntax, ref isConst, out isVar, out alias); + + SourceLocalSymbol localSymbol = this.LookupLocal(argumentSyntax.Identifier); + + // In error scenarios with misplaced code, it is possible we can't bind the local declaration. + // This occurs through the semantic model. In that case concoct a plausible result. + if ((object)localSymbol == null) + { + localSymbol = SourceLocalSymbol.MakeLocal( + ContainingMemberOrLambda, + this, + RefKind.None, + typeSyntax, + argumentSyntax.Identifier, + LocalDeclarationKind.RegularVariable, + initializer: null); + } + else + { + this.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); + } + + if (isVar) + { + // PROTOTYPE(outvar): + throw new NotImplementedException(); + } + + return new BoundLocal(argumentSyntax, localSymbol, constantValueOpt: null, type: declType); + } // Bind a named/positional argument. // Prevent cascading diagnostic by considering the previous @@ -2090,26 +2138,12 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn DiagnosticBag diagnostics, bool hadError, CSharpSyntaxNode argumentSyntax, - ExpressionSyntax argumentExpression, + BoundExpression boundArgumentExpression, NameColonSyntax nameColonSyntax, - RefKind refKind, - bool allowArglist) + RefKind refKind) { Debug.Assert(argumentSyntax is ArgumentSyntax || argumentSyntax is AttributeArgumentSyntax); - BindValueKind valueKind = refKind == RefKind.None ? BindValueKind.RValue : BindValueKind.RefOrOut; - - // Bind argument and verify argument matches rvalue or out param requirements. - BoundExpression argument; - if (allowArglist) - { - argument = this.BindValueAllowArgList(argumentExpression, diagnostics, valueKind); - } - else - { - argument = this.BindValue(argumentExpression, diagnostics, valueKind); - } - bool hasRefKinds = result.RefKinds.Any(); if (refKind != RefKind.None) { @@ -2171,7 +2205,7 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn hadError = true; } - argument = ToBadExpression(argument); + boundArgumentExpression = ToBadExpression(boundArgumentExpression); } result.Names.Add(nameColonSyntax.Name); @@ -2186,16 +2220,36 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn hadError = true; } - argument = ToBadExpression(argument); + boundArgumentExpression = ToBadExpression(boundArgumentExpression); result.Names.Add(null); } - result.Arguments.Add(argument); + result.Arguments.Add(boundArgumentExpression); return hadError; } + /// + /// Bind argument and verify argument matches rvalue or out param requirements. + /// + private BoundExpression BindArgumentExpression(DiagnosticBag diagnostics, ExpressionSyntax argumentExpression, RefKind refKind, bool allowArglist) + { + BindValueKind valueKind = refKind == RefKind.None ? BindValueKind.RValue : BindValueKind.RefOrOut; + + BoundExpression argument; + if (allowArglist) + { + argument = this.BindValueAllowArgList(argumentExpression, diagnostics, valueKind); + } + else + { + argument = this.BindValue(argumentExpression, diagnostics, valueKind); + } + + return argument; + } + private void CoerceArguments( MemberResolutionResult methodResult, ArrayBuilder arguments, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 81833fd729b45..acf2070eed90b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -593,7 +593,7 @@ private BoundStatement BindDeclarationStatementParts(LocalDeclarationStatementSy private TypeSymbol BindVariableType(CSharpSyntaxNode declarationNode, DiagnosticBag diagnostics, TypeSyntax typeSyntax, ref bool isConst, out bool isVar, out AliasSymbol alias) { - Debug.Assert(declarationNode.Kind() == SyntaxKind.LocalDeclarationStatement); + Debug.Assert(declarationNode.Kind() == SyntaxKind.LocalDeclarationStatement || declarationNode.Kind() == SyntaxKind.Argument); // If the type is "var" then suppress errors when binding it. "var" might be a legal type // or it might not; if it is not then we do not want to report an error. If it is, then diff --git a/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs b/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs index ecd25d010a4ab..ad514cb9dfb5a 100644 --- a/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp internal class PatternVariableFinder : CSharpSyntaxWalker { private Binder _binder; - private ArrayBuilder _declarationPatterns; + private ArrayBuilder _localsBuilder; internal static void FindPatternVariables( Binder binder, @@ -27,12 +27,12 @@ internal class PatternVariableFinder : CSharpSyntaxWalker var finder = s_poolInstance.Allocate(); finder._binder = binder; - finder._declarationPatterns = builder; + finder._localsBuilder = builder; finder.Visit(node); finder._binder = null; - finder._declarationPatterns = null; + finder._localsBuilder = null; s_poolInstance.Free(finder); } @@ -48,7 +48,7 @@ internal class PatternVariableFinder : CSharpSyntaxWalker var finder = s_poolInstance.Allocate(); finder._binder = binder; - finder._declarationPatterns = builder; + finder._localsBuilder = builder; foreach (var n in nodes) { @@ -56,7 +56,7 @@ internal class PatternVariableFinder : CSharpSyntaxWalker } finder._binder = null; - finder._declarationPatterns = null; + finder._localsBuilder = null; s_poolInstance.Free(finder); } @@ -101,7 +101,7 @@ public override void VisitLockStatement(LockStatementSyntax node) public override void VisitDeclarationPattern(DeclarationPatternSyntax node) { - _declarationPatterns.Add(SourceLocalSymbol.MakeLocal(_binder.ContainingMemberOrLambda, _binder, RefKind.None, node.Type, node.Identifier, LocalDeclarationKind.PatternVariable)); + _localsBuilder.Add(SourceLocalSymbol.MakeLocal(_binder.ContainingMemberOrLambda, _binder, RefKind.None, node.Type, node.Identifier, LocalDeclarationKind.PatternVariable)); base.VisitDeclarationPattern(node); } public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node) { } @@ -157,6 +157,17 @@ public override void VisitBinaryExpression(BinaryExpressionSyntax node) operands.Free(); } + public override void VisitArgument(ArgumentSyntax node) + { + if (node.Expression != null) + { + base.VisitArgument(node); + return; + } + + _localsBuilder.Add(SourceLocalSymbol.MakeLocal(_binder.ContainingMemberOrLambda, _binder, RefKind.None, node.Type, node.Identifier, LocalDeclarationKind.RegularVariable)); + } + #region pool private static readonly ObjectPool s_poolInstance = CreatePool(); diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index f0f124c137c78..2344c59c6562e 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -739,6 +739,7 @@ + diff --git a/src/Compilers/CSharp/Portable/CSharpExtensions.cs b/src/Compilers/CSharp/Portable/CSharpExtensions.cs index 876d97bf2cec4..9bfbf01b8dd2d 100644 --- a/src/Compilers/CSharp/Portable/CSharpExtensions.cs +++ b/src/Compilers/CSharp/Portable/CSharpExtensions.cs @@ -1210,6 +1210,15 @@ public static ISymbol GetDeclaredSymbol(this SemanticModel semanticModel, Variab return csmodel?.GetDeclaredSymbol(declarationSyntax, cancellationToken); } + /// + /// Given an argument syntax, get symbol for out var that it declares, if any. + /// + public static ISymbol GetDeclaredSymbol(this SemanticModel semanticModel, ArgumentSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)) + { + var csmodel = semanticModel as CSharpSemanticModel; + return csmodel?.GetDeclaredSymbol(declarationSyntax, cancellationToken); + } + /// /// Given a declaration pattern syntax, get the corresponding symbol. /// diff --git a/src/Compilers/CSharp/Portable/CSharpParseOptions.cs b/src/Compilers/CSharp/Portable/CSharpParseOptions.cs index 5e882e7850167..b5fad96626c92 100644 --- a/src/Compilers/CSharp/Portable/CSharpParseOptions.cs +++ b/src/Compilers/CSharp/Portable/CSharpParseOptions.cs @@ -220,6 +220,7 @@ internal bool IsFeatureEnabled(MessageID feature) case MessageID.IDS_FeaturePatternMatching: case MessageID.IDS_FeatureTuples: case MessageID.IDS_FeatureReplace: + case MessageID.IDS_FeatureOutVar: // in "demo" mode enable proposed new C# 7 language features. if (PreprocessorSymbols.Contains("__DEMO__")) { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index be6f596a5e50f..5ce0040bb7d69 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -9701,6 +9701,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to out var. + /// + internal static string IDS_FeatureOutVar { + get { + return ResourceManager.GetString("IDS_FeatureOutVar", resourceCulture); + } + } + /// /// Looks up a localized string similar to partial method. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index b79a6b5f7320c..86c044720bd67 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4872,4 +4872,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ tuples + + out var + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index 7971d88e8f8ac..998fda1194951 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -2657,6 +2657,14 @@ internal Conversion ClassifyConversionForCast(int position, ExpressionSyntax exp /// The symbol that was declared. public abstract ISymbol GetDeclaredSymbol(VariableDeclaratorSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)); + /// + /// Given an argument syntax, get symbol for an out var that it declares, if any. + /// + /// The syntax node that declares a variable. + /// The cancellation token. + /// The symbol that was declared. + public abstract ISymbol GetDeclaredSymbol(ArgumentSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)); + /// /// Given a declaration pattern syntax, get the corresponding symbol. /// @@ -4551,6 +4559,8 @@ protected sealed override ISymbol GetDeclaredSymbolCore(SyntaxNode declaration, return this.GetDeclaredSymbol((AnonymousObjectMemberDeclaratorSyntax)node, cancellationToken); case SyntaxKind.VariableDeclarator: return this.GetDeclaredSymbol((VariableDeclaratorSyntax)node, cancellationToken); + case SyntaxKind.Argument: + return this.GetDeclaredSymbol((ArgumentSyntax)node, cancellationToken); case SyntaxKind.DeclarationPattern: return this.GetDeclaredSymbol((DeclarationPatternSyntax)node, cancellationToken); case SyntaxKind.NamespaceDeclaration: diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 15834d5391dd1..2078548dfe0c6 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -569,6 +569,18 @@ private LocalSymbol GetDeclaredLocal(CSharpSyntaxNode declarationSyntax, SyntaxT return null; } + public override ISymbol GetDeclaredSymbol(ArgumentSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)) + { + CheckSyntaxNode(declarationSyntax); + + if (declarationSyntax.Expression != null) + { + return null; + } + + return GetDeclaredLocal(declarationSyntax, declarationSyntax.Identifier); + } + public override ISymbol GetDeclaredSymbol(DeclarationPatternSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)) { CheckSyntaxNode(declarationSyntax); diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index c32b38be2b89b..07c4898ff7060 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -1643,6 +1643,18 @@ public override ISymbol GetDeclaredSymbol(VariableDeclaratorSyntax declarationSy return memberModel == null ? null : memberModel.GetDeclaredSymbol(declarationSyntax, cancellationToken); } + /// + /// Given an argument syntax, get symbol for an out var that it declares, if any. + /// + /// The syntax node that declares a variable. + /// The cancellation token. + /// The symbol that was declared. + public override ISymbol GetDeclaredSymbol(ArgumentSyntax declarationSyntax, CancellationToken cancellationToken = default(CancellationToken)) + { + var memberModel = this.GetMemberModel(declarationSyntax); + return memberModel == null ? null : memberModel.GetDeclaredSymbol(declarationSyntax, cancellationToken); + } + /// /// Given a declaration pattern syntax, get the corresponding symbol. /// diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 3ce1c3fabbfe9..734d7760783c0 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -120,6 +120,7 @@ internal enum MessageID IDS_FeatureRefLocalsReturns = MessageBase + 12710, IDS_FeatureTuples = MessageBase + 12711, IDS_FeatureReplace = MessageBase + 12712, + IDS_FeatureOutVar = MessageBase + 12713, } // Message IDs may refer to strings that need to be localized. @@ -177,6 +178,8 @@ internal static string RequiredFeature(this MessageID feature) return "tuples"; case MessageID.IDS_FeatureReplace: return "replace"; + case MessageID.IDS_FeatureOutVar: + return "outVar"; default: return null; } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 64e22faed0b30..fabf2bd5fda61 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -505,6 +505,9 @@ protected void VisitLvalue(BoundExpression node) break; case BoundKind.Local: + VisitLvalue((BoundLocal)node); + break; + case BoundKind.ThisReference: case BoundKind.BaseReference: // no need for it to be previously assigned: it is on the left. @@ -547,6 +550,11 @@ protected void VisitLvalue(BoundExpression node) if (_trackRegions && node == this.lastInRegion && this.regionPlace == RegionPlace.Inside) LeaveRegion(); } + protected virtual void VisitLvalue(BoundLocal node) + { + // no need for it to be previously assigned: it is on the left. + } + /// /// Visit a boolean condition expression. /// diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs index 5871997ddd766..86d307dd19ba6 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs @@ -137,5 +137,16 @@ public override BoundNode VisitQueryClause(BoundQueryClause node) return base.VisitQueryClause(node); } + + protected override void VisitLvalue(BoundLocal node) + { + if (!node.WasCompilerGenerated && node.Syntax.Kind() == SyntaxKind.Argument && + ((ArgumentSyntax)node.Syntax).Identifier == node.LocalSymbol.IdentifierToken) + { + _variablesDeclared.Add(node.LocalSymbol); + } + + base.VisitLvalue(node); + } } } diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 70be22b9dacb7..6720a01309e39 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -4942,7 +4942,8 @@ private static bool CanReuseVariableDeclarator(CSharp.Syntax.VariableDeclaratorS var expression = item as ExpressionSyntax; if (expression != null) { - args.Add(_syntaxFactory.Argument(null, default(SyntaxToken), expression)); + args.Add(_syntaxFactory.Argument(null, default(SyntaxToken), expression, + type: null, identifier: default(SyntaxToken))); } else { @@ -9469,6 +9470,8 @@ private ArgumentSyntax ParseArgumentExpression(bool isIndexer) } ExpressionSyntax expression; + TypeSyntax typeSyntax = null; + SyntaxToken identifier = default(SyntaxToken); if (isIndexer && (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.CurrentToken.Kind == SyntaxKind.CloseBracketToken)) { @@ -9480,10 +9483,43 @@ private ArgumentSyntax ParseArgumentExpression(bool isIndexer) } else { - expression = this.ParseSubExpression(Precedence.Expression); + if (refOrOutKeyword != null && refOrOutKeyword.Kind == SyntaxKind.OutKeyword && + IsPossibleOutVarDeclaration()) + { + expression = null; + typeSyntax = ParseType(parentIsParameter: false); + identifier = CheckFeatureAvailability(this.ParseIdentifierToken(), MessageID.IDS_FeatureOutVar); + } + else + { + expression = this.ParseSubExpression(Precedence.Expression); + } } - return _syntaxFactory.Argument(nameColon, refOrOutKeyword, expression); + return _syntaxFactory.Argument(nameColon, refOrOutKeyword, expression, typeSyntax, identifier); + } + + private bool IsPossibleOutVarDeclaration() + { + var tk = this.CurrentToken.Kind; + if (SyntaxFacts.IsPredefinedType(tk) && this.PeekToken(1).Kind != SyntaxKind.DotToken) + { + return true; + } + + var resetPoint = this.GetResetPoint(); + try + { + SyntaxToken lastTokenOfType; + ScanTypeFlags st = this.ScanType(out lastTokenOfType); + + return st != ScanTypeFlags.NotType && this.IsTrueIdentifier(); + } + finally + { + this.Reset(ref resetPoint); + this.Release(ref resetPoint); + } } private TypeOfExpressionSyntax ParseTypeOfExpression() @@ -9746,7 +9782,8 @@ private ExpressionSyntax ParseCastOrParenExpressionOrLambdaOrTuple(Precedence pr // ( , must be a tuple if (this.CurrentToken.Kind == SyntaxKind.CommaToken) { - var firstArg = _syntaxFactory.Argument(nameColon: null, refOrOutKeyword: default(SyntaxToken), expression: expression); + var firstArg = _syntaxFactory.Argument(nameColon: null, refOrOutKeyword: default(SyntaxToken), expression: expression, + type: null, identifier: default(SyntaxToken)); return ParseTupleExpressionTail(openParen, firstArg); } @@ -9757,7 +9794,8 @@ private ExpressionSyntax ParseCastOrParenExpressionOrLambdaOrTuple(Precedence pr var nameColon = _syntaxFactory.NameColon((IdentifierNameSyntax)expression, EatToken()); expression = ParseSubExpression(0); - var firstArg = _syntaxFactory.Argument(nameColon, refOrOutKeyword: default(SyntaxToken), expression: expression); + var firstArg = _syntaxFactory.Argument(nameColon, refOrOutKeyword: default(SyntaxToken), expression: expression, + type: null, identifier: default(SyntaxToken)); return ParseTupleExpressionTail(openParen, firstArg); } @@ -9792,11 +9830,13 @@ private TupleExpressionSyntax ParseTupleExpressionTail(SyntaxToken openParen, Ar var nameColon = _syntaxFactory.NameColon((IdentifierNameSyntax)expression, EatToken()); expression = ParseSubExpression(0); - arg = _syntaxFactory.Argument(nameColon, refOrOutKeyword: default(SyntaxToken), expression: expression); + arg = _syntaxFactory.Argument(nameColon, refOrOutKeyword: default(SyntaxToken), expression: expression, + type: null, identifier: default(SyntaxToken)); } else { - arg = _syntaxFactory.Argument(nameColon: null, refOrOutKeyword: default(SyntaxToken), expression: expression); + arg = _syntaxFactory.Argument(nameColon: null, refOrOutKeyword: default(SyntaxToken), expression: expression, + type: null, identifier: default(SyntaxToken)); } list.Add(arg); diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 36114469e6090..9c76eb41d7794 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,10 @@ Microsoft.CodeAnalysis.CSharp.Conversion.IsTuple.get -> bool +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Identifier.get -> Microsoft.CodeAnalysis.SyntaxToken +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Type.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.SyntaxToken refOrOutKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.SyntaxToken refOrOutKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.WithIdentifier(Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.WithType(Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax.RefKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax.WithRefKeyword(Microsoft.CodeAnalysis.SyntaxToken refKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax @@ -167,9 +173,14 @@ override Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Accept(Microsoft.C override Microsoft.CodeAnalysis.CSharp.Syntax.TupleTypeSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult override Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void override Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult +static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax declarationSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.ISymbol static Microsoft.CodeAnalysis.CSharp.CSharpExtensions.GetDeclaredSymbol(this Microsoft.CodeAnalysis.SemanticModel semanticModel, Microsoft.CodeAnalysis.CSharp.Syntax.DeclarationPatternSyntax declarationSyntax, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.ISymbol static Microsoft.CodeAnalysis.CSharp.SyntaxExtensions.Update(this Microsoft.CodeAnalysis.CSharp.Syntax.IndexerDeclarationSyntax syntax, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken thisKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.BracketedParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.AccessorListSyntax accessorList) -> Microsoft.CodeAnalysis.CSharp.Syntax.IndexerDeclarationSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxExtensions.Update(this Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax syntax, Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax returnType, Microsoft.CodeAnalysis.CSharp.Syntax.ExplicitInterfaceSpecifierSyntax explicitInterfaceSpecifier, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxList constraintClauses, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Argument() -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Argument(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.SyntaxToken outKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Argument(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.SyntaxToken refOrOutKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Argument(Microsoft.CodeAnalysis.SyntaxToken outKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax type, Microsoft.CodeAnalysis.SyntaxToken identifier) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ArrowExpressionClause(Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.SyntaxToken refKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArrowExpressionClauseSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.CSharp.Syntax.WhenClauseSyntax whenClause, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CasePatternSwitchLabel(Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CasePatternSwitchLabelSyntax diff --git a/src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs b/src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs index 67f1112cfc542..89c392dd3b7ba 100644 --- a/src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs +++ b/src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs @@ -16,7 +16,7 @@ internal enum LocalDeclarationKind : byte None, /// - /// User defined local variable declared by . + /// User defined local variable declared by or by "out var" argument. /// RegularVariable, diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index ae568be3d2667..0ecb380446bba 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -305,6 +305,9 @@ public override ImmutableArray DeclaringSyntaxReferences switch (_declarationKind) { case LocalDeclarationKind.RegularVariable: + Debug.Assert(node is VariableDeclaratorSyntax || node is ArgumentSyntax); + break; + case LocalDeclarationKind.Constant: case LocalDeclarationKind.FixedVariable: case LocalDeclarationKind.UsingVariable: diff --git a/src/Compilers/CSharp/Portable/Syntax/ArgumentSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/ArgumentSyntax.cs new file mode 100644 index 0000000000000..d684ab9d58f66 --- /dev/null +++ b/src/Compilers/CSharp/Portable/Syntax/ArgumentSyntax.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.CodeAnalysis.CSharp.Syntax +{ + public partial class ArgumentSyntax + { + public ArgumentSyntax Update(NameColonSyntax nameColon, SyntaxToken refOrOutKeyword, ExpressionSyntax expression) + { + return this.Update(nameColon, refOrOutKeyword, expression, type: null, identifier: default(SyntaxToken)) ; + } + + public ArgumentSyntax Update(NameColonSyntax nameColon, SyntaxToken refOrOutKeyword, TypeSyntax type, SyntaxToken identifier) + { + return this.Update(nameColon, refOrOutKeyword, null, type, identifier); + } + } +} diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index d0c54bb560b91..89c36e93554a5 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -1101,11 +1101,18 @@ SyntaxToken representing the optional ref or out keyword. - + ExpressionSyntax node representing the argument. + + + + Gets the identifier. + + + Class which represents the syntax node for argument. diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index 7b171ac4aeaf5..1942f1ca606bc 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -2513,5 +2513,47 @@ public static UsingDirectiveSyntax UsingDirective(NameEqualsSyntax alias, NameSy name: name, semicolonToken: Token(SyntaxKind.SemicolonToken)); } + + /// Creates a new ArgumentSyntax instance. + public static ArgumentSyntax Argument(NameColonSyntax nameColon, SyntaxToken refOrOutKeyword, ExpressionSyntax expression) + { + if (expression == null) + throw new ArgumentNullException(nameof(expression)); + + return Argument(nameColon, refOrOutKeyword, expression, null, default(SyntaxToken)); + } + + /// Creates a new ArgumentSyntax instance. + public static ArgumentSyntax Argument(ExpressionSyntax expression) + { + return Argument(default(NameColonSyntax), default(SyntaxToken), expression); + } + + /// Creates a new ArgumentSyntax instance. + public static ArgumentSyntax Argument(NameColonSyntax nameColon, SyntaxToken outKeyword, TypeSyntax type, SyntaxToken identifier) + { + if (outKeyword.Kind() != SyntaxKind.OutKeyword) + { + throw new ArgumentException(nameof(outKeyword)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (identifier.Kind() != SyntaxKind.IdentifierToken) + { + throw new ArgumentException(nameof(identifier)); + } + + return Argument(nameColon, outKeyword, null, type, identifier); + } + + /// Creates a new ArgumentSyntax instance. + public static ArgumentSyntax Argument(SyntaxToken outKeyword, TypeSyntax type, SyntaxToken identifier) + { + return Argument(default(NameColonSyntax), outKeyword, type, identifier); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj b/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj index d671254b2e8ac..4bc4a4e397b53 100644 --- a/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj +++ b/src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj @@ -99,6 +99,7 @@ + diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs new file mode 100644 index 0000000000000..3dae5b491f82d --- /dev/null +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs @@ -0,0 +1,875 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; +using Roslyn.Test.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + [CompilerTrait(CompilerFeature.OutVar)] + public class OutVarTests : CompilingTestBase + { + [Fact] + public void OldVersion() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1), x1); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, int y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp6)); + compilation.VerifyDiagnostics( + // (6,29): error CS8058: Feature 'out var' is experimental and unsupported; use '/features:outVar' to enable. + // Test2(Test1(out int x1), x1); + Diagnostic(ErrorCode.ERR_FeatureIsExperimental, "x1").WithArguments("out var", "outVar").WithLocation(6, 29) + ); + } + + [Fact] + public void Simple_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1), x1); + int x2; + Test3(out x2); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, int y) + { + System.Console.WriteLine(y); + } + + static void Test3(out int y) + { + y = 0; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + + var x2Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x2").Single(); + Assert.Null(model.GetDeclaredSymbol(x2Ref)); + Assert.Null(model.GetDeclaredSymbol((ArgumentSyntax)x2Ref.Parent)); + } + + private static void VerifyModelForOutVar(SemanticModel model, ArgumentSyntax decl, params IdentifierNameSyntax[] references) + { + var symbol = model.GetDeclaredSymbol(decl); + Assert.Equal(decl.Identifier.ValueText, symbol.Name); + Assert.Equal(LocalDeclarationKind.RegularVariable, ((LocalSymbol)symbol).DeclarationKind); + Assert.Same(symbol, model.GetDeclaredSymbol((SyntaxNode)decl)); + Assert.Same(symbol, model.LookupSymbols(decl.SpanStart, name: decl.Identifier.ValueText).Single()); + Assert.True(model.LookupNames(decl.SpanStart).Contains(decl.Identifier.ValueText)); + + foreach (var reference in references) + { + Assert.Same(symbol, model.GetSymbolInfo(reference).Symbol); + Assert.Same(symbol, model.LookupSymbols(reference.SpanStart, name: decl.Identifier.ValueText).Single()); + Assert.True(model.LookupNames(reference.SpanStart).Contains(decl.Identifier.ValueText)); + } + + var dataFlowParent = decl.Ancestors().OfType().First(); + var dataFlow = model.AnalyzeDataFlow(dataFlowParent); + + Assert.True(dataFlow.Succeeded); + Assert.True(dataFlow.VariablesDeclared.Contains(symbol, ReferenceEqualityComparer.Instance)); + Assert.True(dataFlow.AlwaysAssigned.Contains(symbol, ReferenceEqualityComparer.Instance)); + Assert.True(dataFlow.WrittenInside.Contains(symbol, ReferenceEqualityComparer.Instance)); + var flowsIn = FlowsIn(dataFlowParent, decl, references); + Assert.Equal(flowsIn, + dataFlow.DataFlowsIn.Contains(symbol, ReferenceEqualityComparer.Instance)); + Assert.Equal(flowsIn, + dataFlow.ReadInside.Contains(symbol, ReferenceEqualityComparer.Instance)); + + var flowsOut = FlowsOut(dataFlowParent, references); + Assert.Equal(flowsOut, + dataFlow.DataFlowsOut.Contains(symbol, ReferenceEqualityComparer.Instance)); + Assert.Equal(flowsOut, + dataFlow.ReadOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); + + Assert.Equal(WrittenOutside(dataFlowParent, references), + dataFlow.WrittenOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); + } + + private static bool FlowsIn(ExpressionSyntax dataFlowParent, ArgumentSyntax decl, IdentifierNameSyntax[] references) + { + foreach (var reference in references) + { + if (dataFlowParent.Span.Contains(reference.Span) && reference.SpanStart > decl.SpanStart) + { + if (IsRead(reference)) + { + return true; + } + } + } + + return false; + } + + private static bool IsRead(IdentifierNameSyntax reference) + { + switch (reference.Parent.Kind()) + { + case SyntaxKind.Argument: + if (((ArgumentSyntax)reference.Parent).RefOrOutKeyword.Kind() != SyntaxKind.OutKeyword) + { + return true; + } + break; + + case SyntaxKind.SimpleAssignmentExpression: + case SyntaxKind.AddAssignmentExpression: + case SyntaxKind.AndAssignmentExpression: + case SyntaxKind.DivideAssignmentExpression: + case SyntaxKind.ExclusiveOrAssignmentExpression: + case SyntaxKind.LeftShiftAssignmentExpression: + case SyntaxKind.ModuloAssignmentExpression: + case SyntaxKind.MultiplyAssignmentExpression: + case SyntaxKind.OrAssignmentExpression: + case SyntaxKind.RightShiftAssignmentExpression: + case SyntaxKind.SubtractAssignmentExpression: + if (((AssignmentExpressionSyntax)reference.Parent).Left != reference) + { + return true; + } + break; + + default: + return true; + } + + return false; + } + + private static bool FlowsOut(ExpressionSyntax dataFlowParent, IdentifierNameSyntax[] references) + { + foreach (var reference in references) + { + if (!dataFlowParent.Span.Contains(reference.Span)) + { + if (IsRead(reference)) + { + return true; + } + } + } + + return false; + } + + private static bool WrittenOutside(ExpressionSyntax dataFlowParent, IdentifierNameSyntax[] references) + { + foreach (var reference in references) + { + if (!dataFlowParent.Span.Contains(reference.Span)) + { + if (IsWrite(reference)) + { + return true; + } + } + } + + return false; + } + + private static bool IsWrite(IdentifierNameSyntax reference) + { + switch (reference.Parent.Kind()) + { + case SyntaxKind.Argument: + if (((ArgumentSyntax)reference.Parent).RefOrOutKeyword.Kind() != SyntaxKind.None) + { + return true; + } + break; + + case SyntaxKind.SimpleAssignmentExpression: + case SyntaxKind.AddAssignmentExpression: + case SyntaxKind.AndAssignmentExpression: + case SyntaxKind.DivideAssignmentExpression: + case SyntaxKind.ExclusiveOrAssignmentExpression: + case SyntaxKind.LeftShiftAssignmentExpression: + case SyntaxKind.ModuloAssignmentExpression: + case SyntaxKind.MultiplyAssignmentExpression: + case SyntaxKind.OrAssignmentExpression: + case SyntaxKind.RightShiftAssignmentExpression: + case SyntaxKind.SubtractAssignmentExpression: + if (((AssignmentExpressionSyntax)reference.Parent).Left == reference) + { + return true; + } + break; + + default: + return true; + } + + return false; + } + + [Fact] + public void Simple_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out System.Int32 x1), x1); + int x2 = 0; + Test3(x2); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, int y) + { + System.Console.WriteLine(y); + } + + static void Test3(int y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + + var x2Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x2").Single(); + Assert.Null(model.GetDeclaredSymbol(x2Ref)); + Assert.Null(model.GetDeclaredSymbol((ArgumentSyntax)x2Ref.Parent)); + } + + [Fact] + public void Simple_03() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out (int, int) x1), x1); + } + + static object Test1(out (int, int) x) + { + x = (123, 124); + return null; + } + + static void Test2(object x, (int, int) y) + { + System.Console.WriteLine(y); + } +} + +namespace System +{ + // struct with two values + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + this.Item2 = item2; + } + public override string ToString() + { + return '{' + Item1?.ToString() + "", "" + Item2?.ToString() + '}'; + } + } +} +"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature().WithTuplesFeature()); + + CompileAndVerify(compilation, expectedOutput: @"{123, 124}").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Simple_04() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out System.Collections.Generic.IEnumerable x1), x1); + } + + static object Test1(out System.Collections.Generic.IEnumerable x) + { + x = new System.Collections.Generic.List(); + return null; + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"System.Collections.Generic.List`1[System.Int32]").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Simple_05() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1, out x1), x1); + } + + static object Test1(out int x, out int y) + { + x = 123; + y = 124; + return null; + } + + static void Test2(object x, int y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"124").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").ToArray(); + Assert.Equal(2, x1Ref.Length); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Simple_06() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1, x1 = 124), x1); + } + + static object Test1(out int x, int y) + { + x = 123; + return null; + } + + static void Test2(object x, int y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").ToArray(); + Assert.Equal(2, x1Ref.Length); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Simple_07() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1), x1, x1 = 124); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, int y, int z) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").ToArray(); + Assert.Equal(2, x1Ref.Length); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Simple_08() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1), ref x1); + int x2 = 0; + Test3(ref x2); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, ref int y) + { + System.Console.WriteLine(y); + } + + static void Test3(ref int y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + + var x2Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x2").Single(); + Assert.Null(model.GetDeclaredSymbol(x2Ref)); + Assert.Null(model.GetDeclaredSymbol((ArgumentSyntax)x2Ref.Parent)); + } + + [Fact] + public void Simple_09() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int x1), out x1); + } + + static object Test1(out int x) + { + x = 123; + return null; + } + + static void Test2(object x, out int y) + { + y = 0; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void Scope_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + int x1 = 0; + Test1(out int x1); + Test2(Test1(out int x2), + out int x2); + } + + static object Test1(out int x) + { + x = 1; + return null; + } + + static void Test2(object y, out int x) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,23): error CS0136: A local or parameter named 'x1' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter + // Test1(out int x1); + Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "x1").WithArguments("x1").WithLocation(7, 23), + // (9,29): error CS0128: A local variable named 'x2' is already defined in this scope + // out int x2); + Diagnostic(ErrorCode.ERR_LocalDuplicate, "x2").WithArguments("x2").WithLocation(9, 29), + // (6,13): warning CS0219: The variable 'x1' is assigned but its value is never used + // int x1 = 0; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x1").WithArguments("x1").WithLocation(6, 13) + ); + } + + [Fact] + public void DataFlow_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out int x1, + x1); + } + + static void Test(out int x, int y) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,22): error CS0165: Use of unassigned local variable 'x1' + // x1); + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(7, 22) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void DataFlow_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out int x1, + ref x1); + } + + static void Test(out int x, ref int y) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,22): error CS0165: Use of unassigned local variable 'x1' + // ref x1); + Diagnostic(ErrorCode.ERR_UseDefViolation, "x1").WithArguments("x1").WithLocation(7, 22) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl, x1Ref); + } + + [Fact] + public void TypeMismatch_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out int x1); + } + + static void Test(out short x) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,14): error CS1503: Argument 1: cannot convert from 'out int' to 'out short' + // Test(out int x1); + Diagnostic(ErrorCode.ERR_BadArgType, "out int x1").WithArguments("1", "out int", "out short").WithLocation(6, 14) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl); + } + + [Fact] + public void Parse_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(int x1); + Test(ref int x2); + } + + static void Test(out int x) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,14): error CS1525: Invalid expression term 'int' + // Test(int x1); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(6, 14), + // (6,18): error CS1003: Syntax error, ',' expected + // Test(int x1); + Diagnostic(ErrorCode.ERR_SyntaxError, "x1").WithArguments(",", "").WithLocation(6, 18), + // (7,18): error CS1525: Invalid expression term 'int' + // Test(ref int x2); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(7, 18), + // (7,22): error CS1003: Syntax error, ',' expected + // Test(ref int x2); + Diagnostic(ErrorCode.ERR_SyntaxError, "x2").WithArguments(",", "").WithLocation(7, 22), + // (6,18): error CS0103: The name 'x1' does not exist in the current context + // Test(int x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 18), + // (7,22): error CS0103: The name 'x2' does not exist in the current context + // Test(ref int x2); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x2").WithArguments("x2").WithLocation(7, 22) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + Assert.False(tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.Kind() != SyntaxKind.None).Any()); + } + + [Fact] + public void Parse_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out int x1.); + } + + static void Test(out int x) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,24): error CS1003: Syntax error, ',' expected + // Test(out int x1.); + Diagnostic(ErrorCode.ERR_SyntaxError, ".").WithArguments(",", ".").WithLocation(6, 24) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var x1Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Single(); + VerifyModelForOutVar(model, x1Decl); + } + + [Fact] + public void Parse_03() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out System.Collections.Generic.IEnumerable); + } + + static void Test(out System.Collections.Generic.IEnumerable x) + { + x = null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,18): error CS0118: 'IEnumerable' is a type but is used like a variable + // Test(out System.Collections.Generic.IEnumerable); + Diagnostic(ErrorCode.ERR_BadSKknown, "System.Collections.Generic.IEnumerable").WithArguments("System.Collections.Generic.IEnumerable", "type", "variable").WithLocation(6, 18) + ); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + Assert.False(tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.Kind() != SyntaxKind.None).Any()); + } + + [Fact(Skip = "Needs adjustment. + No 'var' support yet.")] + public void OutVar_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out var y); + Print(y); + Test2(out (var z)); + Print(z); + var notused = new Cls(out var u); + Print(u); + + Test1(out checked(var v)); + Print(v); + Test2(out unchecked(var w)); + Print(w); + + notused = new Cls(out (checked(unchecked((checked(unchecked(var a))))))); + Print(a); + } + + static void Test1(out int x) + { + x = 123; + } + + static void Test2(out short x) + { + x = 1234; + } + + static void Print(T val) + { + System.Console.WriteLine(val); + System.Console.WriteLine(typeof(T)); + } + + Cls(out byte x) + { + x = 31; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: @"123 +System.Int32 +1234 +System.Int16 +31 +System.Byte +123 +System.Int32 +1234 +System.Int16 +31 +System.Byte").VerifyDiagnostics(); + + //TestSemanticModelAPI(compilation); + } + } +} diff --git a/src/Compilers/Test/Utilities/CSharp.Desktop/TestOptions.cs b/src/Compilers/Test/Utilities/CSharp.Desktop/TestOptions.cs index 7409411602413..72559d48c622c 100644 --- a/src/Compilers/Test/Utilities/CSharp.Desktop/TestOptions.cs +++ b/src/Compilers/Test/Utilities/CSharp.Desktop/TestOptions.cs @@ -78,5 +78,10 @@ public static CSharpParseOptions WithReplaceFeature(this CSharpParseOptions opti { return options.WithFeature("replace", "true"); } + + public static CSharpParseOptions WithOutVarFeature(this CSharpParseOptions options) + { + return options.WithFeature("outVar", "true"); + } } } diff --git a/src/Compilers/Test/Utilities/CSharp/TestOptions.cs b/src/Compilers/Test/Utilities/CSharp/TestOptions.cs index bc4d708e749ae..a5a0b0ef72049 100644 --- a/src/Compilers/Test/Utilities/CSharp/TestOptions.cs +++ b/src/Compilers/Test/Utilities/CSharp/TestOptions.cs @@ -73,5 +73,10 @@ public static CSharpParseOptions WithReplaceFeature(this CSharpParseOptions opti { return options.WithFeature("replace", "true"); } + + public static CSharpParseOptions WithOutVarFeature(this CSharpParseOptions options) + { + return options.WithFeature("outVar", "true"); + } } } diff --git a/src/Test/Utilities/Shared/Traits/CompilerFeature.cs b/src/Test/Utilities/Shared/Traits/CompilerFeature.cs index 1bb07c9c6c1ee..da46cbb1a9de7 100644 --- a/src/Test/Utilities/Shared/Traits/CompilerFeature.cs +++ b/src/Test/Utilities/Shared/Traits/CompilerFeature.cs @@ -18,5 +18,6 @@ public enum CompilerFeature Tuples, RefLocalsReturns, SourceGenerators, + OutVar, } }