From db6b19551a47b1ef95ee0aca8403a454d09baf28 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Sun, 22 May 2016 14:20:04 -0700 Subject: [PATCH] Added support for implicitly-typed out variables. Also addressed feeback provided for the previous change. --- docs/features/outvar.md | 44 +- .../CSharp/Portable/Binder/Binder.cs | 12 + .../Portable/Binder/Binder_Expressions.cs | 65 +- .../Portable/Binder/Binder_Invocation.cs | 36 +- .../Portable/Binder/Binder_Statements.cs | 8 + .../CSharp/Portable/Binder/BlockBinder.cs | 13 + .../Portable/Binder/CatchClauseBinder.cs | 8 + .../Portable/Binder/FixedStatementBinder.cs | 8 + .../Portable/Binder/ForEachLoopBinder.cs | 8 + .../CSharp/Portable/Binder/ForLoopBinder.cs | 8 + .../Portable/Binder/PatternVariableBinder.cs | 6 +- .../Portable/Binder/PatternVariableFinder.cs | 21 + .../OverloadResolution/OverloadResolution.cs | 22 +- .../OverloadResolutionResult.cs | 4 +- .../CSharp/Portable/Binder/SwitchBinder.cs | 8 + .../Portable/Binder/UsingStatementBinder.cs | 8 + .../CSharp/Portable/BoundTree/BoundNodes.xml | 7 + .../CSharp/Portable/BoundTree/Expression.cs | 25 +- .../CSharp/Portable/BoundTree/Formatting.cs | 11 + .../BoundTree/OutVarLocalPendingInference.cs | 32 + .../CSharp/Portable/CSharpCodeAnalysis.csproj | 1 + .../Portable/CSharpResources.Designer.cs | 29 +- .../CSharp/Portable/CSharpResources.resx | 11 +- .../Compilation/CSharpSemanticModel.cs | 14 +- .../CSharp/Portable/Errors/ErrorCode.cs | 3 + .../FlowAnalysis/VariablesDeclaredWalker.cs | 15 +- .../CSharp/Portable/Parser/LanguageParser.cs | 9 +- .../Symbols/Source/SourceLocalSymbol.cs | 73 +- .../CSharp/Portable/Syntax/SyntaxFacts.cs | 3 + .../Test/Semantic/Semantics/OutVarTests.cs | 1404 ++++++++++++++++- .../Semantics/PatternMatchingTests.cs | 12 + 31 files changed, 1824 insertions(+), 104 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/BoundTree/OutVarLocalPendingInference.cs diff --git a/docs/features/outvar.md b/docs/features/outvar.md index 43c378988926f..f7b743c694cc6 100644 --- a/docs/features/outvar.md +++ b/docs/features/outvar.md @@ -10,23 +10,35 @@ argument_value ; ``` -A variable declared this way is called an *out variable*. An *out variable* is read-only and scoped to the enclosing statement. More specifically, the scope will be the same as for a *pattern-variable* introduced via pattern-matching. - -> **Note**: We may treat *out variables* as *pattern variables* in the semantic model. - +A variable declared this way is called an *out variable*. You may use the contextual keyword `var` for the variable's type. +The scope will be the same as for a *pattern-variable* introduced via pattern-matching. +Out variables are disallowed within constructor initializers. -> **Open Issue**: The specification for overload resolution needs to be modified to account for the inference of the type of an *out variable*s declared with `var`. +According to Language Specification (section 7.6.7 Element access) +The argument-list of an element-access is not allowed to contain ref or out arguments. +However, due to backward compatibility, compiler overlooks this restriction during parsing +and even ignores out/ref modifiers in element access during binding. +We will enforce that language rule for out variables declarations at the syntax level. -An *out variable* may not be referenced before the close parenthesis of the invocation in which it is defined: +Within the scope of a local variable introduced by a local-variable-declaration, +it is a compile-time error to refer to that local variable in a textual position +that precedes its declaration. -```cs - M(out x, x = 3); // error -``` +It is also an error to reference implicitly-typed (§8.5.1) out variable in the same argument list that immediately +contains its declaration. Technically, we could support some restricted scenarios (when it is used as another +argument in the same list), but their utility is very questionable. Otherwise, we are looking into getting into +a cycle trying to infer its type. + +For the purposes of overload resolution (see sections 7.5.3.2 Better function member and 7.5.3.3 Better conversion from expression), +neither conversion is considered better when corresponding argument is an implicitly-typed out variable declaration. +Once overload resolution succeeds, the type of implicitly-typed out variable is set to be equal to the type of the +corresponding parameter in the signature of the best candidate. > **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. @@ -51,15 +63,5 @@ Added new API ``` -**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. +**Open issues and TODOs:** + Tracked at https://github.com/dotnet/roslyn/issues/11566. diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.cs index d713081a1f810..ecb596802992a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.cs @@ -168,6 +168,18 @@ internal virtual ImmutableArray GetDeclaredLocalFunctionsFo return this.Next.GetDeclaredLocalFunctionsForScope(scopeDesignator); } + /// + /// If this binder owns a scope for locals, return syntax node that is used + /// as the scope designator. Otherwise, null. + /// + internal virtual SyntaxNode ScopeDesignator + { + get + { + return null; + } + } + internal virtual bool IsLocalFunctionsScopeBinder { get diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 465e2856a274a..30008f669d75e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1369,12 +1369,43 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Diag } else { + SourceLocalSymbol sourceLocal; + type = null; + + if (node.SyntaxTree == localSymbolLocation.SourceTree && (object)(sourceLocal = localSymbol as SourceLocalSymbol) != null && + sourceLocal.IdentifierToken.Parent?.Kind() == SyntaxKind.Argument) + { + var argument = (ArgumentSyntax)sourceLocal.IdentifierToken.Parent; + + if (argument.Identifier == sourceLocal.IdentifierToken && + argument.Type.IsVar) + { + // We are referring to an out variable which might need type inference. + // If it is in fact needs inference, it is illegal to reference it in the same argument list that immediately + // contains its declaration. + // Technically, we could support some restricted scenarios (when it is used as another argument in the same list), + // but their utility is very questionable. Otherwise, we are looking into getting into a cycle trying to infer its type. + if (node.SpanStart < ((ArgumentListSyntax)argument.Parent).CloseParenToken.SpanStart && + sourceLocal.IsVar) // This check involves trying to bind 'var' as a real type, so keeping it last for performance. + { + Error(diagnostics, ErrorCode.ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList, node, node); + + // Treat this case as variable used before declaration, we might be able to infer type of the variable anyway and SemanticModel + // will be able to return non-error type information for this node. + type = new ExtendedErrorTypeSymbol(this.Compilation, name: "var", arity: 0, errorInfo: null, variableUsedBeforeDeclaration: true); + } + } + } + + if ((object)type == null) + { + type = localSymbol.Type; + } + if (IsBadLocalOrParameterCapture(localSymbol, localSymbol.RefKind)) { Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUseLocal, node, localSymbol); } - - type = localSymbol.Type; } return new BoundLocal(node, localSymbol, constantValueOpt, type, hasErrors: isError); @@ -2121,10 +2152,21 @@ private BoundExpression BindArgumentValue(DiagnosticBag diagnostics, ArgumentSyn this.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); } + if (this.InConstructorInitializer) + { + Error(diagnostics, ErrorCode.ERR_OutVarInConstructorInitializer, argumentSyntax.Identifier); + } + if (isVar) { - // PROTOTYPE(outvar): - throw new NotImplementedException(); + return new OutVarLocalPendingInference(argumentSyntax, localSymbol); + } + + if (this.ContainingMemberOrLambda.Kind == SymbolKind.Method + && ((MethodSymbol)this.ContainingMemberOrLambda).IsAsync + && declType.IsRestrictedType()) + { + Error(diagnostics, ErrorCode.ERR_BadSpecialByRefLocal, argumentSyntax.Type, declType); } return new BoundLocal(argumentSyntax, localSymbol, constantValueOpt: null, type: declType); @@ -2280,6 +2322,21 @@ private BoundExpression BindArgumentExpression(DiagnosticBag diagnostics, Expres arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics); } + else if (argument.Kind == BoundKind.OutVarLocalPendingInference) + { + TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg); + bool hasErrors = false; + + if (this.ContainingMemberOrLambda.Kind == SymbolKind.Method + && ((MethodSymbol)this.ContainingMemberOrLambda).IsAsync + && parameterType.IsRestrictedType()) + { + Error(diagnostics, ErrorCode.ERR_BadSpecialByRefLocal, ((ArgumentSyntax)argument.Syntax).Type, parameterType); + hasErrors = true; + } + + arguments[arg] = ((OutVarLocalPendingInference)argument).SetInferredType(parameterType, success: !hasErrors); + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 763599635e26d..3f00572903b35 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -113,6 +113,7 @@ private static ImmutableArray GetOriginalMethods(OverloadResolutio boundExpression.WasCompilerGenerated = true; var analyzedArguments = AnalyzedArguments.GetInstance(); + Debug.Assert(!args.Any(e => e.Kind == BoundKind.OutVarLocalPendingInference)); analyzedArguments.Arguments.AddRange(args); BoundExpression result = BindInvocationExpression( node, node, methodName, boundExpression, analyzedArguments, diagnostics, queryClause, @@ -329,6 +330,30 @@ private BoundExpression BindArgListOperator(InvocationExpressionSyntax node, Dia private ImmutableArray BuildArgumentsForDynamicInvocation(AnalyzedArguments arguments, DiagnosticBag diagnostics) { + for (int i = 0; i < arguments.Arguments.Count; i++) + { + if (arguments.Arguments[i].Kind == BoundKind.OutVarLocalPendingInference) + { + var builder = ArrayBuilder.GetInstance(arguments.Arguments.Count); + builder.AddRange(arguments.Arguments); + + do + { + BoundExpression argument = builder[i]; + + if (argument.Kind == BoundKind.OutVarLocalPendingInference) + { + builder[i] = ((OutVarLocalPendingInference)argument).FailInference(this, diagnostics); + } + + i++; + } + while (i < builder.Count); + + return builder.ToImmutableAndFree(); + } + } + return arguments.Arguments.ToImmutable(); } @@ -1100,7 +1125,8 @@ private ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedA { BoundKind argumentKind = oldArguments[i].Kind; - if (argumentKind == BoundKind.UnboundLambda && i < parameterCount) + if (argumentKind == BoundKind.OutVarLocalPendingInference || + (argumentKind == BoundKind.UnboundLambda && i < parameterCount)) { ArrayBuilder newArguments = ArrayBuilder.GetInstance(argumentCount); newArguments.AddRange(oldArguments); @@ -1120,8 +1146,16 @@ private ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedA newArguments[i] = ((UnboundLambda)oldArgument).Bind(parameterType); } break; + + case BoundKind.OutVarLocalPendingInference: + newArguments[i] = ((OutVarLocalPendingInference)oldArgument).SetInferredType(parameters[i].Type, success: true); + break; } } + else if (oldArgument.Kind == BoundKind.OutVarLocalPendingInference) + { + newArguments[i] = ((OutVarLocalPendingInference)oldArgument).FailInference(this, null); + } i++; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index acf2070eed90b..9a4e183c65c0d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1859,6 +1859,14 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); break; + + case BoundKind.Local: + Debug.Assert(expr.Syntax.Kind() != SyntaxKind.Argument || valueKind == BindValueKind.RefOrOut); + break; + + case BoundKind.OutVarLocalPendingInference: + Debug.Assert(valueKind == BindValueKind.RefOrOut); + return expr; } bool hasResolutionErrors = false; diff --git a/src/Compilers/CSharp/Portable/Binder/BlockBinder.cs b/src/Compilers/CSharp/Portable/Binder/BlockBinder.cs index 891c039e49a42..183a5629275be 100644 --- a/src/Compilers/CSharp/Portable/Binder/BlockBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/BlockBinder.cs @@ -85,6 +85,19 @@ private bool IsMatchingScopeDesignator(CSharpSyntaxNode scopeDesignator) return false; } + internal override SyntaxNode ScopeDesignator + { + get + { + if (_statements.Count == 1) + { + return _statements.First(); + } + + return _statements.Node; + } + } + internal override ImmutableArray GetDeclaredLocalFunctionsForScope(CSharpSyntaxNode scopeDesignator) { if (IsMatchingScopeDesignator(scopeDesignator)) diff --git a/src/Compilers/CSharp/Portable/Binder/CatchClauseBinder.cs b/src/Compilers/CSharp/Portable/Binder/CatchClauseBinder.cs index 3ba366256c165..994d8393f29c1 100644 --- a/src/Compilers/CSharp/Portable/Binder/CatchClauseBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/CatchClauseBinder.cs @@ -51,5 +51,13 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _syntax; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/FixedStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/FixedStatementBinder.cs index 8ec7223c1ed4d..613ecdb90c1d0 100644 --- a/src/Compilers/CSharp/Portable/Binder/FixedStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/FixedStatementBinder.cs @@ -56,5 +56,13 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _syntax; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index 0caa015b0514f..1690ae1aa1324 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -890,5 +890,13 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _syntax; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/ForLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForLoopBinder.cs index ed654418c4861..d3e6fb0b7a19f 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForLoopBinder.cs @@ -107,5 +107,13 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _syntax; + } + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/PatternVariableBinder.cs b/src/Compilers/CSharp/Portable/Binder/PatternVariableBinder.cs index 53d22775c1368..46b37aee20ca0 100644 --- a/src/Compilers/CSharp/Portable/Binder/PatternVariableBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/PatternVariableBinder.cs @@ -11,8 +11,6 @@ namespace Microsoft.CodeAnalysis.CSharp { internal sealed class PatternVariableBinder : LocalScopeBinder { - public readonly CSharpSyntaxNode ScopeDesignator; - internal PatternVariableBinder(CSharpSyntaxNode scopeDesignator, Binder next) : base(next) { this.ScopeDesignator = scopeDesignator; @@ -21,7 +19,7 @@ internal PatternVariableBinder(CSharpSyntaxNode scopeDesignator, Binder next) : protected override ImmutableArray BuildLocals() { var builder = ArrayBuilder.GetInstance(); - PatternVariableFinder.FindPatternVariables(this, builder, ScopeDesignator); + PatternVariableFinder.FindPatternVariables(this, builder, (CSharpSyntaxNode)ScopeDesignator); return builder.ToImmutableAndFree(); } @@ -39,5 +37,7 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator { get; } } } diff --git a/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs b/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs index ad514cb9dfb5a..f90e96af0698c 100644 --- a/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/PatternVariableFinder.cs @@ -165,6 +165,27 @@ public override void VisitArgument(ArgumentSyntax node) return; } + var contextKind = node.Identifier. + Parent. // ArgumentSyntax + Parent. // ArgumentListSyntax + Parent. // invocation/constructor initializer + Kind(); + + switch (contextKind) + { + case SyntaxKind.InvocationExpression: + case SyntaxKind.ObjectCreationExpression: + case SyntaxKind.ThisConstructorInitializer: + case SyntaxKind.BaseConstructorInitializer: + break; + default: + + // It looks like we are deling with a syntax tree that has a shape that could never be + // produced by the LanguageParser, including all error conditions. + // Out Variable declarations can only appear in an argument list of the syntax nodes mentioned above. + throw ExceptionUtilities.UnexpectedValue(contextKind); + } + _localsBuilder.Add(SourceLocalSymbol.MakeLocal(_binder.ContainingMemberOrLambda, _binder, RefKind.None, node.Type, node.Identifier, LocalDeclarationKind.RegularVariable)); } diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 96793028f2543..627adfdf066cd 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -1230,7 +1230,18 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2); bool okToDowngradeToNeither; - var r = BetterConversionFromExpression(arguments[i], + BetterResult r; + + if (argumentKind == BoundKind.OutVarLocalPendingInference) + { + // If argument is an out variable that needs type inference, + // neither candidate is better in this argument. + r = BetterResult.Neither; + okToDowngradeToNeither = false; + } + else + { + r = BetterConversionFromExpression(arguments[i], type1, m1.Result.ConversionForArg(i), refKind1, @@ -1240,6 +1251,7 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re considerRefKinds, ref useSiteDiagnostics, out okToDowngradeToNeither); + } if (r == BetterResult.Neither) { @@ -2912,6 +2924,14 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind return Conversion.ImplicitDynamic; } + if (argument.Kind == BoundKind.OutVarLocalPendingInference) + { + Debug.Assert(argRefKind != RefKind.None); + + // Any parameter type is good, we'll use it for the var local. + return Conversion.Identity; + } + if (argRefKind == RefKind.None) { var conversion = Conversions.ClassifyImplicitConversionFromExpression(argument, parameterType, ref useSiteDiagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs index 8687ffd0a57ef..3328dbeb29e27 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs @@ -924,7 +924,7 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed // If the expression is untyped because it is a lambda, anonymous method, method group or null // then we never want to report the error "you need a ref on that thing". Rather, we want to // say that you can't convert "null" to "ref int". - if (!argument.HasExpressionType()) + if (!argument.HasExpressionType() && argument.Kind != BoundKind.OutVarLocalPendingInference) { // If the problem is that a lambda isn't convertible to the given type, also report why. // The argument and parameter type might match, but may not have same in/out modifiers @@ -971,6 +971,8 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed } else { + Debug.Assert(argument.Kind != BoundKind.OutVarLocalPendingInference); + TypeSymbol argType = argument.Display as TypeSymbol; Debug.Assert((object)argType != null); diff --git a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs index 743d3524534fb..a4407a925c716 100644 --- a/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs @@ -332,6 +332,14 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _switchSyntax; + } + } # region "Switch statement binding methods" diff --git a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs index 3faf8b5c1486b..0b03ad8267a0d 100644 --- a/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/UsingStatementBinder.cs @@ -150,5 +150,13 @@ internal override ImmutableArray GetDeclaredLocalFunctionsF { throw ExceptionUtilities.Unreachable; } + + internal override SyntaxNode ScopeDesignator + { + get + { + return _syntax; + } + } } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 53d531f48b775..78debb8562247 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -1459,4 +1459,11 @@ + + + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index b41e6fa9d4fce..0aa72307a817a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -182,7 +182,7 @@ private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, I return new Argument(ArgumentKind.Named, parameter, argument); }); } - + private static IOperation CreateParamArray(IParameterSymbol parameter, ImmutableArray boundArguments, int firstArgumentElementIndex, SyntaxNode invocationSyntax) { if (parameter.Type.TypeKind == TypeKind.Array) @@ -500,7 +500,7 @@ public override void Accept(OperationVisitor visitor) } } - internal partial class BoundTupleExpression + internal partial class BoundTupleExpression { protected override OperationKind ExpressionKind => OperationKind.None; @@ -1277,7 +1277,7 @@ public override void Accept(OperationVisitor visitor) internal partial class BoundImplicitReceiver : IInstanceReferenceExpression { InstanceReferenceKind IInstanceReferenceExpression.InstanceReferenceKind => InstanceReferenceKind.Implicit; - + protected override OperationKind ExpressionKind => OperationKind.InstanceReferenceExpression; public override void Accept(OperationVisitor visitor) @@ -1906,7 +1906,7 @@ public override void Accept(OperationVisitor visitor) return visitor.VisitNoneOperation(this, argument); } } - + internal partial class BoundDynamicCollectionElementInitializer { protected override OperationKind ExpressionKind => OperationKind.None; @@ -2799,4 +2799,21 @@ public override void Accept(OperationVisitor visitor) protected override OperationKind ExpressionKind => OperationKind.None; } + /// + /// Providing iplementation as OperationKind.None. This implementation is sufficient because the node doesn't survive initial binding. + /// + internal partial class OutVarLocalPendingInference + { + public override void Accept(OperationVisitor visitor) + { + visitor.VisitNoneOperation(this); + } + + public override TResult Accept(OperationVisitor visitor, TArgument argument) + { + return visitor.VisitNoneOperation(this, argument); + } + + protected override OperationKind ExpressionKind => OperationKind.None; + } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs index a51da0578be39..3bb4b35bd8d46 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs @@ -75,4 +75,15 @@ public override object Display get { throw ExceptionUtilities.Unreachable; } } } + + internal partial class OutVarLocalPendingInference + { + public override object Display + { + get + { + return MessageID.IDS_FeatureOutVar.Localize(); + } + } + } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutVarLocalPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutVarLocalPendingInference.cs new file mode 100644 index 0000000000000..4ecdf69cd6b26 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/OutVarLocalPendingInference.cs @@ -0,0 +1,32 @@ +// 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; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class OutVarLocalPendingInference + { + public BoundLocal SetInferredType(TypeSymbol type, bool success) + { + var syntaxNode = (ArgumentSyntax)this.Syntax; + + Binder.DeclareLocalVariable( + (SourceLocalSymbol)this.LocalSymbol, + syntaxNode.Identifier, + type); + + return new BoundLocal(syntaxNode, this.LocalSymbol, constantValueOpt: null, type: type, hasErrors: this.HasErrors || !success); + } + + public BoundLocal FailInference(Binder binder, DiagnosticBag diagnosticsOpt) + { + if (diagnosticsOpt != null) + { + Binder.Error(diagnosticsOpt, ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedOutVariable, ((ArgumentSyntax)this.Syntax).Identifier); + } + + return this.SetInferredType(binder.CreateErrorType("var"), success: false); + } + } +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index 2344c59c6562e..3a068a15caca0 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -200,6 +200,7 @@ + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 5ce0040bb7d69..cba6ee333fc7f 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -4786,6 +4786,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Reference to an implicitly-typed out variable '{0}' is not permitted in the same argument list.. + /// + internal static string ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList { + get { + return ResourceManager.GetString("ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot initialize an implicitly-typed variable with an array initializer. /// @@ -6874,6 +6883,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Out variable declarations are not allowed within constructor initializers.. + /// + internal static string ERR_OutVarInConstructorInitializer { + get { + return ResourceManager.GetString("ERR_OutVarInConstructorInitializer", resourceCulture); + } + } + /// /// Looks up a localized string similar to '{0}' cannot define overloaded methods that differ only on ref and out. /// @@ -8674,6 +8692,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Cannot infer the type of implicitly-typed out variable.. + /// + internal static string ERR_TypeInferenceFailedForImplicitlyTypedOutVariable { + get { + return ResourceManager.GetString("ERR_TypeInferenceFailedForImplicitlyTypedOutVariable", resourceCulture); + } + } + /// /// Looks up a localized string similar to Type parameter declaration must be an identifier not a type. /// @@ -9702,7 +9729,7 @@ internal class CSharpResources { } /// - /// Looks up a localized string similar to out var. + /// Looks up a localized string similar to out variable declaration. /// internal static string IDS_FeatureOutVar { get { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 86c044720bd67..bb97c81133364 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4873,6 +4873,15 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ tuples - out var + out variable declaration + + + Reference to an implicitly-typed out variable '{0}' is not permitted in the same argument list. + + + Cannot infer the type of implicitly-typed out variable. + + + Out variable declarations are not allowed within constructor initializers. \ 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 998fda1194951..c72077307fbb5 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -519,10 +519,20 @@ public SymbolInfo GetSymbolInfo(ExpressionSyntax expression, CancellationToken c // Named arguments handled in special way. return this.GetNamedArgumentSymbolInfo((IdentifierNameSyntax)expression, cancellationToken); } - else + else if (expression.Parent?.Kind() == SyntaxKind.Argument && + ((ArgumentSyntax)expression.Parent).Type == expression) { - return this.GetSymbolInfoWorker(expression, SymbolInfoOptions.DefaultOptions, cancellationToken); + TypeSymbol outVarType = (GetDeclaredSymbol((ArgumentSyntax)expression.Parent, cancellationToken) as LocalSymbol)?.Type; + + if (outVarType?.IsErrorType() == false) + { + return new SymbolInfo(outVarType); + } + + return SymbolInfo.None; } + + return this.GetSymbolInfoWorker(expression, SymbolInfoOptions.DefaultOptions, cancellationToken); } /// diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 150fa46996801..a31b05c6f763e 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1367,6 +1367,9 @@ internal enum ErrorCode ERR_RefReturnLocal = 8924, ERR_RefReturnLocal2 = 8925, ERR_RefReturnStructThis = 8926, + ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList = 8927, + ERR_TypeInferenceFailedForImplicitlyTypedOutVariable = 8928, + ERR_OutVarInConstructorInitializer = 8929, // more diagnostics for ref locals and ref returns ERR_MustBeRefAssignable = 8930, diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs index 86d307dd19ba6..64c82e3db5a31 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/VariablesDeclaredWalker.cs @@ -140,13 +140,24 @@ public override BoundNode VisitQueryClause(BoundQueryClause node) protected override void VisitLvalue(BoundLocal node) { - if (!node.WasCompilerGenerated && node.Syntax.Kind() == SyntaxKind.Argument && + CheckOutVarDeclaration(node); + base.VisitLvalue(node); + } + + private void CheckOutVarDeclaration(BoundLocal node) + { + if (IsInside && + !node.WasCompilerGenerated && node.Syntax.Kind() == SyntaxKind.Argument && ((ArgumentSyntax)node.Syntax).Identifier == node.LocalSymbol.IdentifierToken) { _variablesDeclared.Add(node.LocalSymbol); } + } - base.VisitLvalue(node); + public override BoundNode VisitLocal(BoundLocal node) + { + CheckOutVarDeclaration(node); + return base.VisitLocal(node); } } } diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 6720a01309e39..b61820ab1fb3a 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -9483,7 +9483,14 @@ private ArgumentSyntax ParseArgumentExpression(bool isIndexer) } else { - if (refOrOutKeyword != null && refOrOutKeyword.Kind == SyntaxKind.OutKeyword && + // According to Language Specification, section 7.6.7 Element access + // The argument-list of an element-access is not allowed to contain ref or out arguments. + // However, due to backward compatibility, compiler overlooks this restriction during parsing + // and even ignores out/ref modifiers in element access during binding. + // + // We will enforce that language rule for out variables declarations at the parser level. + if (!isIndexer && + refOrOutKeyword != null && refOrOutKeyword.Kind == SyntaxKind.OutKeyword && IsPossibleOutVarDeclaration()) { expression = null; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index 0ecb380446bba..3ceac23e9588e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -93,9 +93,21 @@ internal Binder Binder EqualsValueClauseSyntax initializer = null) { Debug.Assert(declarationKind != LocalDeclarationKind.ForEachIterationVariable); - return (initializer == null) ? - new SourceLocalSymbol(containingSymbol, binder, refKind, typeSyntax, identifierToken, declarationKind) : - new LocalWithInitializer(containingSymbol, binder, refKind, typeSyntax, identifierToken, initializer, declarationKind); + if (initializer == null) + { + if (identifierToken.Parent?.Kind() == SyntaxKind.Argument) + { + var argument = (ArgumentSyntax)identifierToken.Parent; + if (argument.Identifier == identifierToken && argument.Type.IsVar) + { + return new PossibleOutVarLocalSymbol(containingSymbol, binder, refKind, typeSyntax, identifierToken, declarationKind); + } + } + + return new SourceLocalSymbol(containingSymbol, binder, refKind, typeSyntax, identifierToken, declarationKind); + } + + return new LocalWithInitializer(containingSymbol, binder, refKind, typeSyntax, identifierToken, initializer, declarationKind); } internal override bool IsImportedFromMetadata @@ -510,5 +522,60 @@ protected override TypeSymbol InferTypeOfVarVariable(DiagnosticBag diagnostics) return ((ForEachLoopBinder)this.binder).InferCollectionElementType(diagnostics, _collection); } } + + /// + /// Symbol for an out variable local that might require type inference during overload resolution. + /// + private class PossibleOutVarLocalSymbol : SourceLocalSymbol + { + public PossibleOutVarLocalSymbol( + Symbol containingSymbol, + Binder binder, + RefKind refKind, + TypeSyntax typeSyntax, + SyntaxToken identifierToken, + LocalDeclarationKind declarationKind) + : base(containingSymbol, binder, refKind, typeSyntax, identifierToken, declarationKind) + { + Debug.Assert(identifierToken.Parent.Kind() == SyntaxKind.Argument && + ((ArgumentSyntax)identifierToken.Parent).Identifier == identifierToken); + + Debug.Assert(identifierToken.Parent.Parent.Parent is ConstructorInitializerSyntax ? + binder.ScopeDesignator == identifierToken.Parent.Parent : + binder.ScopeDesignator.Contains(identifierToken.Parent.Parent.Parent)); + } + + protected override TypeSymbol InferTypeOfVarVariable(DiagnosticBag diagnostics) + { + // Try binding immediately enclosing invocation expression, this should force the inference. + + CSharpSyntaxNode invocation = (CSharpSyntaxNode)IdentifierToken. + Parent. // ArgumentSyntax + Parent. // ArgumentListSyntax + Parent; // invocation/constructor initializer + + TypeSymbol result; + + switch (invocation.Kind()) + { + case SyntaxKind.InvocationExpression: + case SyntaxKind.ObjectCreationExpression: + this.binder.BindExpression((ExpressionSyntax)invocation, diagnostics); + result = this._type; + Debug.Assert((object)result != null); + return result; + + case SyntaxKind.ThisConstructorInitializer: + case SyntaxKind.BaseConstructorInitializer: + this.binder.BindConstructorInitializer(((ConstructorInitializerSyntax)invocation).ArgumentList, (MethodSymbol)this.binder.ContainingMember(), diagnostics); + result = this._type; + Debug.Assert((object)result != null); + return result; + + default: + throw ExceptionUtilities.UnexpectedValue(invocation.Kind()); + } + } + } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs index ca9ac70b4e459..1c10dce642ab9 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFacts.cs @@ -186,6 +186,9 @@ public static bool IsInTypeOnlyContext(ExpressionSyntax node) case DeclarationPattern: return ((DeclarationPatternSyntax)parent).Type == node; + + case Argument: + return ((ArgumentSyntax)parent).Type == node; } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs index 3dae5b491f82d..cdfb0e77348b7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OutVarTests.cs @@ -41,10 +41,17 @@ static void Test2(object x, int 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. + // (6,29): error CS8058: Feature 'out variable declaration' 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) + Diagnostic(ErrorCode.ERR_FeatureIsExperimental, "x1").WithArguments("out variable declaration", "outVar").WithLocation(6, 29) ); + + 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] @@ -93,6 +100,11 @@ static void Test3(out int y) } private static void VerifyModelForOutVar(SemanticModel model, ArgumentSyntax decl, params IdentifierNameSyntax[] references) + { + VerifyModelForOutVar(model, decl, false, references); + } + + private static void VerifyModelForOutVar(SemanticModel model, ArgumentSyntax decl, bool isDelegateCreation, params IdentifierNameSyntax[] references) { var symbol = model.GetDeclaredSymbol(decl); Assert.Equal(decl.Identifier.ValueText, symbol.Name); @@ -101,34 +113,54 @@ private static void VerifyModelForOutVar(SemanticModel model, ArgumentSyntax dec Assert.Same(symbol, model.LookupSymbols(decl.SpanStart, name: decl.Identifier.ValueText).Single()); Assert.True(model.LookupNames(decl.SpanStart).Contains(decl.Identifier.ValueText)); + var local = (SourceLocalSymbol)symbol; + + if (decl.Type.IsVar && local.IsVar && local.Type.IsErrorType()) + { + Assert.Null(model.GetSymbolInfo(decl.Type).Symbol); + } + else + { + Assert.Equal(local.Type, model.GetSymbolInfo(decl.Type).Symbol); + } + 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)); + Assert.Equal(local.Type, model.GetTypeInfo(reference).Type); } - 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)); + if (!(decl.Parent.Parent is ConstructorInitializerSyntax)) + { + var dataFlowParent = (ExpressionSyntax)decl.Parent.Parent; + var dataFlow = model.AnalyzeDataFlow(dataFlowParent); - var flowsOut = FlowsOut(dataFlowParent, references); - Assert.Equal(flowsOut, - dataFlow.DataFlowsOut.Contains(symbol, ReferenceEqualityComparer.Instance)); - Assert.Equal(flowsOut, - dataFlow.ReadOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); + Assert.True(dataFlow.Succeeded); + Assert.True(dataFlow.VariablesDeclared.Contains(symbol, ReferenceEqualityComparer.Instance)); - Assert.Equal(WrittenOutside(dataFlowParent, references), - dataFlow.WrittenOutside.Contains(symbol, ReferenceEqualityComparer.Instance)); + if (!isDelegateCreation) + { + 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) @@ -565,6 +597,79 @@ static void Test2(object x, out int y) VerifyModelForOutVar(model, x1Decl, x1Ref); } + [Fact] + public void Simple_10() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out dynamic x1), x1); + } + + static object Test1(out dynamic x) + { + x = 123; + return null; + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + references: new MetadataReference[] { CSharpRef, SystemCoreRef }, + 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); + } + + [Fact] + public void Simple_11() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out int[] x1), x1); + } + + static object Test1(out int[] x) + { + x = new [] {123}; + return null; + } + + static void Test2(object x, int[] y) + { + System.Console.WriteLine(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); + } + [Fact] public void Scope_01() { @@ -605,6 +710,128 @@ static void Test2(object y, out int x) ); } + [Fact] + public void Scope_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(x1, + Test1(out int x1)); + } + + static object Test1(out int x) + { + x = 1; + return null; + } + + static void Test2(object y, object x) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,15): error CS0841: Cannot use local variable 'x1' before it is declared + // Test2(x1, + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x1").WithArguments("x1").WithLocation(6, 15) + ); + } + + [Fact] + public void Scope_03() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(out x1, + Test1(out int x1)); + } + + static object Test1(out int x) + { + x = 1; + return null; + } + + static void Test2(out int y, object x) + { + y = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,19): error CS0841: Cannot use local variable 'x1' before it is declared + // Test2(out x1, + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x1").WithArguments("x1").WithLocation(6, 19) + ); + } + + [Fact] + public void Scope_04() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out int x1); + System.Console.WriteLine(x1); + } + + static object Test1(out int x) + { + x = 1; + return null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,34): error CS0103: The name 'x1' does not exist in the current context + // System.Console.WriteLine(x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 34) + ); + } + + [Fact] + public void Scope_05() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(out x1, + Test1(out var x1)); + } + + static object Test1(out int x) + { + x = 1; + return null; + } + + static void Test2(out int y, object x) + { + y = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,19): error CS0841: Cannot use local variable 'x1' before it is declared + // Test2(out x1, + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x1").WithArguments("x1").WithLocation(6, 19) + ); + } + [Fact] public void DataFlow_01() { @@ -670,6 +897,46 @@ static void Test(out int x, ref int y) var x1Ref = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "x1").Single(); VerifyModelForOutVar(model, x1Decl, x1Ref); } + + [Fact] + public void DataFlow_03() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test(out int x1); + var x2 = 1; + } + + static void Test(out int x) + { + x = 1; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,13): warning CS0219: The variable 'x2' is assigned but its value is never used + // var x2 = 1; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x2").WithArguments("x2").WithLocation(7, 13) + ); + + 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); + + var x2Decl = tree.GetRoot().DescendantNodes().OfType().Single(); + + var dataFlow = model.AnalyzeDataFlow(x2Decl); + + Assert.True(dataFlow.Succeeded); + Assert.Equal("System.Int32 x2", dataFlow.VariablesDeclared.Single().ToTestDisplayString()); + Assert.Equal("System.Int32 x1", dataFlow.WrittenOutside.Single().ToTestDisplayString()); + } [Fact] public void TypeMismatch_01() @@ -809,67 +1076,1086 @@ static void Test(out System.Collections.Generic.IEnumerable x) 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() + [Fact] + public void GetAliasInfo_01() { var text = @" +using a = System.Int32; + 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 a x1); + } + + static object Test1(out int x) + { + x = 123; + return null; + } +}"; + 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(); + VerifyModelForOutVar(model, x1Decl); + + Assert.Equal("a=System.Int32", model.GetAliasInfo(x1Decl.Type).ToTestDisplayString()); + } - Test1(out checked(var v)); - Print(v); - Test2(out unchecked(var w)); - Print(w); + [Fact] + public void VarIsNotVar_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } - notused = new Cls(out (checked(unchecked((checked(unchecked(var a))))))); - Print(a); + static object Test1(out var x) + { + x = new var() {val = 123}; + return null; } - static void Test1(out int x) + static void Test2(object x, var y) { - x = 123; + System.Console.WriteLine(y.val); } - static void Test2(out short x) + struct var { - x = 1234; + public int val; } +}"; + 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); + } - static void Print(T val) + [Fact] + public void VarIsNotVar_02() + { + var text = @" +public class Cls +{ + public static void Main() { - System.Console.WriteLine(val); - System.Console.WriteLine(typeof(T)); + Test1(out var x1); } - Cls(out byte x) + struct var { - x = 31; + public int val; } }"; - var compilation = CreateCompilationWithMscorlib(text, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithOutVarFeature()); + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,9): error CS0103: The name 'Test1' does not exist in the current context + // Test1(out var x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "Test1").WithArguments("Test1").WithLocation(6, 9), + // (11,20): warning CS0649: Field 'Cls.var.val' is never assigned to, and will always have its default value 0 + // public int val; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "val").WithArguments("Cls.var.val", "0").WithLocation(11, 20) + ); - CompileAndVerify(compilation, expectedOutput: @"123 -System.Int32 -1234 -System.Int16 -31 -System.Byte -123 -System.Int32 -1234 -System.Int16 -31 -System.Byte").VerifyDiagnostics(); - - //TestSemanticModelAPI(compilation); + 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); + + Assert.Equal("Cls.var", ((LocalSymbol)model.GetDeclaredSymbol(x1Decl)).Type.ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(out int x) + { + x = 123; + 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: @"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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + } + + [Fact] + public void SimpleVar_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static void Test2(object x, object y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,15): error CS0103: The name 'Test1' does not exist in the current context + // Test2(Test1(out var x1), x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "Test1").WithArguments("Test1").WithLocation(6, 15) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + } + + [Fact] + public void SimpleVar_03() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out var x1, + out x1); + } + + static object Test1(out int x, out int x2) + { + x = 123; + x2 = 124; + return null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,23): error CS8927: Reference to an implicitly-typed out variable 'x1' is not permitted in the same argument list. + // out x1); + Diagnostic(ErrorCode.ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList, "x1").WithArguments("x1").WithLocation(7, 23) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("System.Int32 x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_04() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out var x1, + Test1(out x1, + 3)); + } + + static object Test1(out int x, int x2) + { + x = 123; + x2 = 124; + return null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,25): error CS8927: Reference to an implicitly-typed out variable 'x1' is not permitted in the same argument list. + // Test1(out x1, + Diagnostic(ErrorCode.ERR_ImplicitlyTypedOutVariableUsedInTheSameArgumentList, "x1").WithArguments("x1").WithLocation(7, 25) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("System.Int32 x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_05() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(ref int x) + { + x = 123; + return null; + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,21): error CS1620: Argument 1 must be passed with the 'ref' keyword + // Test2(Test1(out var x1), x1); + Diagnostic(ErrorCode.ERR_BadArgRef, "out var x1").WithArguments("1", "ref").WithLocation(6, 21) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("System.Int32 x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_06() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(int x) + { + x = 123; + return null; + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,21): error CS1615: Argument 1 may not be passed with the 'out' keyword + // Test2(Test1(out var x1), x1); + Diagnostic(ErrorCode.ERR_BadArgExtraRef, "out var x1").WithArguments("1", "out").WithLocation(6, 21) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("System.Int32 x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_07() + { + var text = @" +public class Cls +{ + public static void Main() + { + dynamic x = null; + Test2(x.Test1(out var x1), + x1); + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,31): error CS8928: Cannot infer the type of implicitly-typed out variable. + // Test2(x.Test1(out var x1), + Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedOutVariable, "x1").WithLocation(7, 31) + ); + + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("var x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_08() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(new Test1(out var x1), x1); + } + + class Test1 + { + public Test1(out int x) + { + x = 123; + } + } + + 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: @"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); + } + + [Fact] + public void SimpleVar_09() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(new System.Action(out var x1), + x1); + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,33): error CS0149: Method name expected + // Test2(new System.Action(out var x1), + Diagnostic(ErrorCode.ERR_MethodNameExpected, "out var x1").WithLocation(6, 33), + // (6,33): error CS0165: Use of unassigned local variable 'x1' + // Test2(new System.Action(out var x1), + Diagnostic(ErrorCode.ERR_UseDefViolation, "out var x1").WithArguments("x1").WithLocation(6, 33) + ); + + 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, true, x1Ref); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("var x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_10() + { + var text = @" +public class Cls +{ + public static void Main() + { + } + + public static object Test1(out int x) + { + x = 123; + return null; + } + + class Test2 + { + Test2(object x, object y) + { + } + + Test2() + : this(Test1(out var x1), x1) + {} + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (21,30): error CS8929: Out variable declarations are not allowed within constructor initializers. + // : this(Test1(out var x1), x1) + Diagnostic(ErrorCode.ERR_OutVarInConstructorInitializer, "x1").WithLocation(21, 30) + ); + + 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 SimpleVar_11() + { + var text = @" +public class Cls +{ + public static void Main() + { + } + + public static object Test1(out int x) + { + x = 123; + return null; + } + + class Test2 + { + public Test2(object x, object y) + { + } + } + + class Test3 : Test2 + { + + Test3() + : base(Test1(out var x1), x1) + {} + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (25,30): error CS8929: Out variable declarations are not allowed within constructor initializers. + // : base(Test1(out var x1), x1) + Diagnostic(ErrorCode.ERR_OutVarInConstructorInitializer, "x1").WithLocation(25, 30) + ); + + 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 SimpleVar_12() + { + var text = @" +public class Cls +{ + public static void Main() + { + } + + class Test2 + { + Test2(out int x) + { + x = 2; + } + + Test2() + : this(out var x1) + {} + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (16,24): error CS8929: Out variable declarations are not allowed within constructor initializers. + // : this(out var x1) + Diagnostic(ErrorCode.ERR_OutVarInConstructorInitializer, "x1").WithLocation(16, 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 SimpleVar_13() + { + var text = @" +public class Cls +{ + public static void Main() + { + } + + class Test2 + { + public Test2(out int x) + { + x = 1; + } + } + + class Test3 : Test2 + { + + Test3() + : base(out var x1) + {} + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (20,24): error CS8929: Out variable declarations are not allowed within constructor initializers. + // : base(out var x1) + Diagnostic(ErrorCode.ERR_OutVarInConstructorInitializer, "x1").WithLocation(20, 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 SimpleVar_14() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(out dynamic x) + { + x = 123; + return null; + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + references: new MetadataReference[] { CSharpRef, SystemCoreRef }, + 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); + + Assert.Null(model.GetAliasInfo(x1Decl.Type)); + Assert.Equal("dynamic x1", model.GetDeclaredSymbol(x1Decl).ToTestDisplayString()); + } + + [Fact] + public void SimpleVar_15() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(new Test1(out var var), var); + } + + class Test1 + { + public Test1(out int x) + { + x = 123; + } + } + + 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: @"123").VerifyDiagnostics(); + + var tree = compilation.SyntaxTrees.Single(); + var model = compilation.GetSemanticModel(tree); + + var varDecl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "var").Single(); + var varRef = tree.GetRoot().DescendantNodes().OfType().Where(id => id.Identifier.ValueText == "var").Skip(1).Single(); + VerifyModelForOutVar(model, varDecl, varRef); + } + + [Fact] + public void VarAndBetterness_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out var x1, null); + Test2(out var x2, null); + } + + static object Test1(out int x, object y) + { + x = 123; + System.Console.WriteLine(x); + return null; + } + + static object Test1(out short x, string y) + { + x = 124; + System.Console.WriteLine(x); + return null; + } + + static object Test2(out int x, string y) + { + x = 125; + System.Console.WriteLine(x); + return null; + } + + static object Test2(out short x, object y) + { + x = 126; + System.Console.WriteLine(x); + return null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + CompileAndVerify(compilation, expectedOutput: +@"124 +125").VerifyDiagnostics(); + + 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); + + var x2Decl = tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x2").Single(); + VerifyModelForOutVar(model, x2Decl); + } + + [Fact] + public void VarAndBetterness_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test1(out var x1, null); + } + + static object Test1(out int x, object y) + { + x = 123; + System.Console.WriteLine(x); + return null; + } + + static object Test1(out short x, object y) + { + x = 124; + System.Console.WriteLine(x); + return null; + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (6,9): error CS0121: The call is ambiguous between the following methods or properties: 'Cls.Test1(out int, object)' and 'Cls.Test1(out short, object)' + // Test1(out var x1, null); + Diagnostic(ErrorCode.ERR_AmbigCall, "Test1").WithArguments("Cls.Test1(out int, object)", "Cls.Test1(out short, object)").WithLocation(6, 9) + ); + + 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 RestrictedTypes_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(out System.ArgIterator x) + { + x = default(System.ArgIterator); + return null; + } + + static void Test2(object x, System.ArgIterator y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (9,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' + // static object Test1(out System.ArgIterator x) + Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(9, 25) + ); + + 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 RestrictedTypes_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + Test2(Test1(out System.ArgIterator x1), x1); + } + + static object Test1(out System.ArgIterator x) + { + x = default(System.ArgIterator); + return null; + } + + static void Test2(object x, System.ArgIterator y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (9,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' + // static object Test1(out System.ArgIterator x) + Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(9, 25) + ); + + 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 RestrictedTypes_03() + { + var text = @" +public class Cls +{ + public static void Main() {} + + async void Test() + { + Test2(Test1(out var x1), x1); + } + + static object Test1(out System.ArgIterator x) + { + x = default(System.ArgIterator); + return null; + } + + static void Test2(object x, System.ArgIterator y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (11,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' + // static object Test1(out System.ArgIterator x) + Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(11, 25), + // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // Test2(Test1(out var x1), x1); + Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("System.ArgIterator").WithLocation(8, 25), + // (6,16): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async void Test() + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Test").WithLocation(6, 16) + ); + + 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 RestrictedTypes_04() + { + var text = @" +public class Cls +{ + public static void Main() {} + + async void Test() + { + Test2(Test1(out System.ArgIterator x1), x1); + var x = default(System.ArgIterator); + } + + static object Test1(out System.ArgIterator x) + { + x = default(System.ArgIterator); + return null; + } + + static void Test2(object x, System.ArgIterator y) + { + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (12,25): error CS1601: Cannot make reference to variable of type 'ArgIterator' + // static object Test1(out System.ArgIterator x) + Diagnostic(ErrorCode.ERR_MethodArgCantBeRefAny, "out System.ArgIterator x").WithArguments("System.ArgIterator").WithLocation(12, 25), + // (8,25): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // Test2(Test1(out System.ArgIterator x1), x1); + Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "System.ArgIterator").WithArguments("System.ArgIterator").WithLocation(8, 25), + // (9,9): error CS4012: Parameters or locals of type 'ArgIterator' cannot be declared in async methods or lambda expressions. + // var x = default(System.ArgIterator); + Diagnostic(ErrorCode.ERR_BadSpecialByRefLocal, "var").WithArguments("System.ArgIterator").WithLocation(9, 9), + // (9,13): warning CS0219: The variable 'x' is assigned but its value is never used + // var x = default(System.ArgIterator); + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "x").WithArguments("x").WithLocation(9, 13), + // (6,16): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + // async void Test() + Diagnostic(ErrorCode.WRN_AsyncLacksAwaits, "Test").WithLocation(6, 16) + ); + + 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 ElementAccess_01() + { + var text = @" +public class Cls +{ + public static void Main() + { + var x = new [] {1}; + Test2(x[out var x1], x1); + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,25): error CS1003: Syntax error, ',' expected + // Test2(x[out var x1], x1); + Diagnostic(ErrorCode.ERR_SyntaxError, "x1").WithArguments(",", "").WithLocation(7, 25), + // (7,21): error CS0103: The name 'var' does not exist in the current context + // Test2(x[out var x1], x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "var").WithArguments("var").WithLocation(7, 21), + // (7,25): error CS0103: The name 'x1' does not exist in the current context + // Test2(x[out var x1], x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 25), + // (7,30): error CS0103: The name 'x1' does not exist in the current context + // Test2(x[out var x1], x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 30) + ); + + var tree = compilation.SyntaxTrees.Single(); + Assert.False(tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Any()); + } + + [Fact] + public void ElementAccess_02() + { + var text = @" +public class Cls +{ + public static void Main() + { + var x = new [] {1}; + Test2(x[out int x1], x1); + } + + static void Test2(object x, object y) + { + System.Console.WriteLine(y); + } +}"; + var compilation = CreateCompilationWithMscorlib(text, + options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithOutVarFeature()); + + compilation.VerifyDiagnostics( + // (7,21): error CS1525: Invalid expression term 'int' + // Test2(x[out int x1], x1); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "int").WithArguments("int").WithLocation(7, 21), + // (7,25): error CS1003: Syntax error, ',' expected + // Test2(x[out int x1], x1); + Diagnostic(ErrorCode.ERR_SyntaxError, "x1").WithArguments(",", "").WithLocation(7, 25), + // (7,25): error CS0103: The name 'x1' does not exist in the current context + // Test2(x[out int x1], x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 25), + // (7,30): error CS0103: The name 'x1' does not exist in the current context + // Test2(x[out int x1], x1); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(7, 30) + ); + + var tree = compilation.SyntaxTrees.Single(); + Assert.False(tree.GetRoot().DescendantNodes().OfType().Where(p => p.Identifier.ValueText == "x1").Any()); } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs index 77398ad29213c..0fd82d9bef8d7 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs @@ -850,6 +850,12 @@ private static void VerifyModelForDeclarationPattern(SemanticModel model, Declar Assert.Same(symbol, model.LookupSymbols(decl.SpanStart, name: decl.Identifier.ValueText).Single()); Assert.True(model.LookupNames(decl.SpanStart).Contains(decl.Identifier.ValueText)); + var type = ((LocalSymbol)symbol).Type; + if (!decl.Type.IsVar || !type.IsErrorType()) + { + Assert.Equal(type, model.GetSymbolInfo(decl.Type).Symbol); + } + foreach (var reference in references) { Assert.Same(symbol, model.GetSymbolInfo(reference).Symbol); @@ -866,6 +872,12 @@ private static void VerifyModelForDeclarationPatternDuplicateInSameScope(Semanti Assert.Same(symbol, model.GetDeclaredSymbol((SyntaxNode)decl)); Assert.NotEqual(symbol, model.LookupSymbols(decl.SpanStart, name: decl.Identifier.ValueText).Single()); Assert.True(model.LookupNames(decl.SpanStart).Contains(decl.Identifier.ValueText)); + + var type = ((LocalSymbol)symbol).Type; + if (!decl.Type.IsVar || !type.IsErrorType()) + { + Assert.Equal(type, model.GetSymbolInfo(decl.Type).Symbol); + } } private static void VerifyNotAPatternLocal(SemanticModel model, IdentifierNameSyntax reference)