From 00552fc2287f820ae9d42fd259aa6c07c2c5a805 Mon Sep 17 00:00:00 2001 From: RoslynTeam Date: Thu, 21 Aug 2014 16:53:38 -0700 Subject: [PATCH] Initial commit of pattern matching experiment. --- .../BinderFactory.BinderFactoryVisitor.cs | 5 + .../Portable/Binder/Binder_Expressions.cs | 7 +- .../Portable/Binder/Binder_Initializers.cs | 35 +- .../Portable/Binder/Binder_Operators.cs | 42 +- .../CSharp/Portable/Binder/Binder_Patterns.cs | 421 ++++++ .../Portable/Binder/Binder_Statements.cs | 9 +- .../Portable/Binder/BuckStopsHereBinder.cs | 2 +- .../Portable/Binder/LocalScopeBinder.cs | 17 + .../Semantics/Operators/OperatorFacts.cs | 6 +- .../CSharp/Portable/Binder/SwitchBinder.cs | 97 +- .../CSharp/Portable/BoundTree/BoundNodes.xml | 54 + .../BoundTree/BoundStatementExtensions.cs | 13 +- .../Portable/BoundTree/BoundTreeVisitors.cs | 12 + .../CSharp/Portable/CSharpCodeAnalysis.csproj | 11 + .../CSharp/Portable/CSharpParseOptions.cs | 14 + .../Portable/Compiler/MethodCompiler.cs | 43 +- .../Portable/Declarations/DeclarationKind.cs | 4 +- .../Declarations/DeclarationModifiers.cs | 4 +- .../Declarations/DeclarationTreeBuilder.cs | 16 +- .../Emitter/Model/NamedTypeSymbolAdapter.cs | 8 + .../CSharp/Portable/Errors/MessageID.cs | 2 + .../Portable/FlowAnalysis/DataFlowPass.cs | 45 +- .../FlowAnalysis/PreciseAbstractFlowPass.cs | 106 ++ .../Portable/Lowering/InitializerRewriter.cs | 3 + .../LocalRewriter_MatchExpression.cs | 258 ++++ .../LocalRewriter_MatchStatement.cs | 91 ++ .../Lowering/SyntheticBoundNodeFactory.cs | 62 +- .../CSharp/Portable/Parser/LanguageParser.cs | 1151 +++++++++++++---- .../CSharp/Portable/Parser/SyntaxParser.cs | 17 + .../Portable/Symbols/EnumConversions.cs | 1 + .../Portable/Symbols/FieldInitializer.cs | 4 +- .../Portable/Symbols/LocalDeclarationKind.cs | 5 + .../Portable/Symbols/NamespaceOrTypeSymbol.cs | 1 + .../CSharp/Portable/Symbols/PropertySymbol.cs | 11 + .../Portable/Symbols/Source/ModifierUtils.cs | 5 + .../Symbols/Source/ParameterHelpers.cs | 133 ++ .../Symbols/Source/SourceConstructorSymbol.cs | 89 +- .../Symbols/Source/SourceLocalSymbol.cs | 6 +- .../Source/SourceMemberContainerSymbol.cs | 107 +- ...berContainerSymbol_ImplementationChecks.cs | 5 + .../Symbols/Source/SourceMethodSymbol.cs | 12 +- .../Symbols/Source/SourceNamedTypeSymbol.cs | 6 +- .../Symbols/Source/SourceNamespaceSymbol.cs | 1 + .../Symbols/Source/SourceParameterSymbol.cs | 2 +- .../Symbols/Source/SourcePropertySymbol.cs | 2 +- .../SourceUserDefinedOperatorSymbolBase.cs | 4 + .../SynthesizedConstantHelperMethod.cs | 136 ++ .../Records/SynthesizedEqualsMethod.cs | 118 ++ .../Records/SynthesizedGetHashCodeMethod.cs | 123 ++ .../Records/SynthesizedIsOperator.cs | 92 ++ .../SynthesizedMethodBaseForRecords.cs | 280 ++++ .../Records/SynthesizedPropertySymbol.cs | 244 ++++ .../SynthesizedBackingFieldSymbol.cs | 4 +- .../Syntax/RecordParameterListSyntax.cs | 15 + .../Portable/Syntax/RecordParameterSyntax.cs | 26 + .../CSharp/Portable/Syntax/Syntax.xml | 260 ++++ .../CSharp/Portable/Syntax/SyntaxKind.cs | 32 +- .../CSharp/Portable/Syntax/SyntaxKindFacts.cs | 7 + .../CSharpCompilerSemanticTest.csproj | 1 + .../Semantics/PatternMatchingTests.cs | 958 ++++++++++++++ .../CSharp/Test/Semantic/packages.config | 5 +- .../CSharp/Test/Syntax/packages.config | 5 +- Src/Compilers/CSharp/csc/packages.config | 2 +- .../Core/MSBuildTasks/packages.config | 2 +- .../Portable/Symbols/WellKnownMemberNames.cs | 5 + Src/Compilers/PackageFiles/packages.config | 2 +- .../CSharpTrackingDiagnosticAnalyzer.cs | 2 +- 67 files changed, 4915 insertions(+), 353 deletions(-) create mode 100644 Src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs create mode 100644 Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchExpression.cs create mode 100644 Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchStatement.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedConstantHelperMethod.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedEqualsMethod.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedGetHashCodeMethod.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedIsOperator.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedMethodBaseForRecords.cs create mode 100644 Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPropertySymbol.cs create mode 100644 Src/Compilers/CSharp/Portable/Syntax/RecordParameterListSyntax.cs create mode 100644 Src/Compilers/CSharp/Portable/Syntax/RecordParameterSyntax.cs create mode 100644 Src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs diff --git a/Src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/Src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index 8a4572ee..f2c5e5bd 100644 --- a/Src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/Src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -780,6 +780,11 @@ public override Binder VisitClassDeclaration(ClassDeclarationSyntax node) return VisitTypeDeclarationCore(node); } + public override Binder VisitRecordDeclaration(RecordDeclarationSyntax node) + { + return VisitTypeDeclarationCore(node); + } + public override Binder VisitStructDeclaration(StructDeclarationSyntax node) { return VisitTypeDeclarationCore(node); diff --git a/Src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/Src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index da7fe5a3..24c186cc 100644 --- a/Src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/Src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -405,6 +405,9 @@ private BoundExpression BindExpressionInternal(ExpressionSyntax node, Diagnostic case SyntaxKind.IsExpression: return BindIsOperator((BinaryExpressionSyntax)node, diagnostics); + case SyntaxKind.MatchExpression: + return BindMatchExpression((MatchExpressionSyntax)node, diagnostics); + case SyntaxKind.AsExpression: return BindAsOperator((BinaryExpressionSyntax)node, diagnostics); @@ -543,7 +546,7 @@ private BoundExpression BindDeclarationExpression(DeclarationExpressionSyntax no TypeSymbol declType = BindVariableType(node, diagnostics, typeSyntax, ref isConst, out isVar, out alias); SourceLocalSymbol localSymbol = this.LookupLocal(node.Variable.Identifier); - + if ((object)localSymbol == null) { Error(diagnostics, ErrorCode.ERR_DeclarationExpressionOutOfContext, node); @@ -1165,7 +1168,7 @@ private BoundExpression BindIdentifier( Error(diagnostics, ErrorCode.ERR_NameNotInContext, node, node.Identifier.ValueText); } } - + lookupResult.Free(); return expression; } diff --git a/Src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs b/Src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs index fd121aed..07fd5841 100644 --- a/Src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs +++ b/Src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs @@ -97,7 +97,29 @@ private static void BindRegularCSharpFieldInitializers( foreach (var info in infos) { - BoundFieldInitializer boundInitializer = BindFieldInitializer(info.Binder, info.Initializer.Field, info.EqualsValue, diagnostics); + BoundFieldInitializer boundInitializer; + if (info.EqualsValue.Kind == SyntaxKind.EqualsValueClause) + { + var equalsValueSyntax = (EqualsValueClauseSyntax)info.EqualsValue; + boundInitializer = BindFieldInitializer(info.Binder, info.Initializer.Field, equalsValueSyntax, diagnostics); + } + else + { + Debug.Assert(info.EqualsValue.Kind == SyntaxKind.RecordParameter); + // This is automatically generating backing field and its initializer for record parameters. + + var recordParameter = (RecordParameterSyntax)info.EqualsValue; + var recordParameterName = recordParameter.Identifier.ValueText; + var lookupResult = LookupResult.GetInstance(); + LookupOptions options = LookupOptions.AllMethodsOnArityZero; + HashSet useSiteDiagnostics = null; + info.Binder.LookupSymbolsWithFallback(lookupResult, recordParameterName, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics, options: options); + var parameterSymbol = lookupResult.Symbols[0]; + Debug.Assert(parameterSymbol.Kind == SymbolKind.Parameter); + boundInitializer = new BoundFieldInitializer(recordParameter, info.Initializer.Field, + new BoundParameter(recordParameter, (ParameterSymbol)parameterSymbol)); + lookupResult.Free(); + } initializersBuilder.Add(boundInitializer); } @@ -136,7 +158,7 @@ internal static ImmutableArray GetFieldInitializerInfos( { //Can't assert that this is a regular C# compilation, because we could be in a nested type of a script class. SyntaxReference syntaxRef = initializer.Syntax; - var initializerNode = (EqualsValueClauseSyntax)syntaxRef.GetSyntax(); + var initializerNode = (CSharpSyntaxNode)syntaxRef.GetSyntax(); if (binderFactory == null) { @@ -145,6 +167,8 @@ internal static ImmutableArray GetFieldInitializerInfos( Binder parentBinder = binderFactory.GetBinder(initializerNode); Debug.Assert(parentBinder.ContainingMemberOrLambda == fieldSymbol.ContainingType || //should be the binder for the type + parentBinder.ContainingMemberOrLambda == fieldSymbol.ContainingNamespace || //for the recordparametersyntax's binder + parentBinder.ContainingMemberOrLambda == fieldSymbol.ContainingType.ContainingType || //for the recordparametersyntax's binder fieldSymbol.ContainingType.IsImplicitClass); //however, we also allow fields in namespaces to help support script scenarios if (generateDebugInfo && firstDebugImports == null) @@ -196,10 +220,11 @@ private static ImmutableArray GetInitializationScopeLocals(ArrayBui foreach (var info in infos) { // Constant initializers do not contribute to the initialization scope. - if (!info.Initializer.Field.IsConst) + if (!info.Initializer.Field.IsConst && info.EqualsValue.Kind == SyntaxKind.EqualsValueClause) { - var walker = new LocalScopeBinder.BuildLocalsFromDeclarationsWalker(info.Binder, info.EqualsValue.Value); - walker.Visit(info.EqualsValue.Value); + var equalsValueExpression = ((EqualsValueClauseSyntax)info.EqualsValue).Value; + var walker = new LocalScopeBinder.BuildLocalsFromDeclarationsWalker(info.Binder, equalsValueExpression); + walker.Visit(equalsValueExpression); if (walker.Locals != null) { diff --git a/Src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs b/Src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs index a926c7f8..f5701efc 100644 --- a/Src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs +++ b/Src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Collections; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp @@ -2392,11 +2393,50 @@ private static bool IsDivisionByZero(BinaryOperatorKind kind, ConstantValue valu return false; } + internal BoundExpression BindMatchExpression(MatchExpressionSyntax node, DiagnosticBag diagnostics) + { + var leftBound = BindValue(node.Expression, diagnostics, BindValueKind.RValue); + var pattern = node.Pattern; + var resultType = GetSpecialType(SpecialType.System_Boolean, diagnostics, node); + var rightBound = this.BindPattern(pattern, leftBound.Type, diagnostics); + return new BoundMatchExpression(node, leftBound, rightBound, resultType); + } + private BoundExpression BindIsOperator(BinaryExpressionSyntax node, DiagnosticBag diagnostics) { var operand = BindValue(node.Left, diagnostics, BindValueKind.RValue); AliasSymbol alias; - TypeSymbol targetType = BindType(node.Right, diagnostics, out alias); + TypeSymbol targetType; + var options = (CSharpParseOptions)node.SyntaxTree.Options; + if (options.IsRecordsEnabled()) + { + var extraDiagnostics = DiagnosticBag.GetInstance(); + targetType = BindType(node.Right, extraDiagnostics, out alias); + + // node.Right might be a constant and it might be converted to a constant expression (e.g., o is Days.Sun) + if (extraDiagnostics.HasAnyErrors() && node.Right is NameSyntax) + { + var diagnosticsForConstant = DiagnosticBag.GetInstance(); + var boundConstant = BindValue(node.Right, diagnosticsForConstant, BindValueKind.RValue); + if (boundConstant.ConstantValue != null) + { + // E.g., (o is Days.Sun) Days.Sun would not be bound as a type so we will try our chance for a constant expression. + // If it is constant, remove old diagnostics about why Days.Sun could not be bounded as a type. + // Then, bind the is operator as a match expression. + extraDiagnostics.Free(); + diagnostics.AddRangeAndFree(diagnosticsForConstant); + var boolType = GetSpecialType(SpecialType.System_Boolean, diagnostics, node); + return new BoundMatchExpression(node, operand, new BoundConstantPattern(node.Right, boundConstant), boolType); + } + diagnosticsForConstant.Free(); + } + diagnostics.AddRangeAndFree(extraDiagnostics); + } + else + { + targetType = BindType(node.Right, diagnostics, out alias); + } + var typeExpression = new BoundTypeExpression(node.Right, alias, targetType); var targetTypeKind = targetType.TypeKind; var resultType = (TypeSymbol)GetSpecialType(SpecialType.System_Boolean, diagnostics, node); diff --git a/Src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/Src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs new file mode 100644 index 00000000..eac59861 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -0,0 +1,421 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Collections; +using Roslyn.Utilities; +using System; + +namespace Microsoft.CodeAnalysis.CSharp +{ + /// + /// This portion of the binder converts PatternSyntax nodes into BoundPattern nodes. + /// + internal partial class Binder + { + internal BoundPattern BindPattern(PatternSyntax node, TypeSymbol operandType, DiagnosticBag diagnostics) + { + var operandTypeCandidates = PooledHashSet.GetInstance(); + operandTypeCandidates.Add(operandType); + var boundPattern = BindPattern(node, operandTypeCandidates, diagnostics); + operandTypeCandidates.Free(); + return boundPattern; + } + + // One of 'operandType' or 'opIsCandidates' must be null. There are two modes: + // (1) You bind a pattern which is not in the recursive pattern. In this case you just have one candidate for the operand type. + // (2) You bind a pattern which in in the recursive pattern. In this case, you might have many candidates for the operand type because of the parent recursive pattern's multiple op_Is methods. + internal BoundPattern BindPattern(PatternSyntax node, HashSet operandTypeCandidates, DiagnosticBag diagnostics) + { + switch (node.Kind) + { + case SyntaxKind.WildCardPattern: + return new BoundWildCardPattern(node); + + case SyntaxKind.ConstantPattern: + { + var constantPattern = (ConstantPatternSyntax)node; + return BindConstantPattern(constantPattern.Expression, operandTypeCandidates, diagnostics); + } + + case SyntaxKind.DeclarationPattern: + { + var declarationPattern = (DeclarationPatternSyntax)node; + var typeSyntax = declarationPattern.Type; + var localSymbol = LookupLocal(declarationPattern.Identifier); + + if (!typeSyntax.IsVar) + { + // Source: operandType, destination: localSymbol.type + RemoveBadTypeCandidates(declarationPattern, localSymbol.Type, operandTypeCandidates, diagnostics); + } + else + { + if (operandTypeCandidates.Count == 1) + { + localSymbol.SetTypeSymbol(operandTypeCandidates.ToImmutableList()[0]); + } + // I have to set the type of the var declarations after op_Is is found if there is more than one operandTypeCandidate + } + + return new BoundDeclarationPattern(node, localSymbol); + } + case SyntaxKind.RecursivePattern: + return BindRecursivePattern((RecursivePatternSyntax)node, operandTypeCandidates, diagnostics); + + case SyntaxKind.PropertyPattern: + return BindPropertyPattern((PropertyPatternSyntax)node, operandTypeCandidates, diagnostics); + + default: + throw ExceptionUtilities.UnexpectedValue(node.Kind); + } + } + + internal BoundPattern BindConstantPattern(ExpressionSyntax constantExpr, TypeSymbol operandType, DiagnosticBag diagnostics) + { + var operandTypeCandidates = PooledHashSet.GetInstance(); + operandTypeCandidates.Add(operandType); + var boundPattern = BindConstantPattern(constantExpr, operandTypeCandidates, diagnostics); + operandTypeCandidates.Free(); + return boundPattern; + } + + internal BoundPattern BindConstantPattern(ExpressionSyntax constantExpr, HashSet operandTypeCandidates, DiagnosticBag diagnostics) + { + bool hasErrors; + var boundConstant = BindValue(constantExpr, diagnostics, BindValueKind.RValue); + hasErrors = boundConstant.HasAnyErrors; + + if (!hasErrors) + { + if (boundConstant.ConstantValue == null) + { + Error(diagnostics, ErrorCode.ERR_ConstantExpected, constantExpr); + hasErrors = true; + } + // Check whether it is not 'null' type. + else if (boundConstant.Type != null) + { + // source: boundConstantType, destination: operandTypes + RemoveBadTypeCandidates(constantExpr, boundConstant.Type, operandTypeCandidates, diagnostics); + } + } + return new BoundConstantPattern(constantExpr, boundConstant, hasErrors); + } + + private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, HashSet operandTypeCandidates, DiagnosticBag diagnostics) + { + var type = (NamedTypeSymbol)this.BindType(node.Type, diagnostics); + var subPatterns = node.PatternList.SubPatterns; + var opIsCandidates = ArrayBuilder.GetInstance(); + // First, add all op_Is methods in the type + foreach (var member in type.GetMembers(WellKnownMemberNames.IsOperatorName)) + { + if (member.Kind == SymbolKind.Method) + { + opIsCandidates.Add((MethodSymbol)member); + } + } + + // Remove the candidates which have different number of operands or do not have the paramName in the subpattern. + RemoveBadParamsCandidates(opIsCandidates, subPatterns); + + // If there is no opIs, don't bind the subpatterns! + if (opIsCandidates.Count == 0) + { + // TODO Add an error message saying that "There is no suitable op_Is method" + Error(diagnostics, ErrorCode.ERR_NoSuchMember, node, type, "is operator"); + opIsCandidates.Free(); + return new BoundWildCardPattern(node, hasErrors: true); + } + + // Check whether all patterns have a name after a named pattern. + bool isAnyNamedPattern = false; + foreach (var subPattern in subPatterns) + { + if (subPattern.NameColon != null) + { + isAnyNamedPattern = true; + } + else if (isAnyNamedPattern) + { + Error(diagnostics, ErrorCode.ERR_NamedArgumentSpecificationBeforeFixedArgument, subPattern); + return new BoundWildCardPattern(node, hasErrors: true); + } + } + + ImmutableArray boundPatterns = BindSubRecursivePatterns(node, opIsCandidates, diagnostics); + + // Second, eliminate opIsCandidates if there is no operandType candidate that can be converted to it. + // Also, eliminate operandTypeCandidates that cannot be converted to the first parameter type of the opIsCandidate, if there are other working operandTypeCandidates!!! + Unify(node.Type, operandTypeCandidates, opIsCandidates, diagnostics); + + MethodSymbol opIs = null; + ImmutableArray patternsToParams; + bool hasErrors = false; + if (opIsCandidates.Count > 0) + { + // If there are more than one opIsCandidate, choose the first one. + opIs = opIsCandidates[0]; + + // Constructing patternsToParams, an integer array which specifies the order of the patterns based on the opIs parameters. + patternsToParams = ConstructPatterns2Params(node, opIs); + + // Set the type of 'var' declarations based on the opIs parameters + SetTypeForVarDeclarations(subPatterns, boundPatterns, opIs); + } + else + { + patternsToParams = ImmutableArray.Empty; + hasErrors = true; + } + opIsCandidates.Free(); + return new BoundRecursivePattern(node, type, opIs, boundPatterns, patternsToParams, hasErrors); + } + + private BoundPattern BindPropertyPattern(PropertyPatternSyntax node, HashSet operandTypeCandidates, DiagnosticBag diagnostics) + { + var type = (NamedTypeSymbol)this.BindType(node.Type, diagnostics); + var properties = ArrayBuilder.GetInstance(); + var boundPatterns = BindSubPropertyPatterns(node, properties, type, diagnostics); + bool hasErrors = properties.Count != boundPatterns.Length; + return new BoundPropertyPattern(node, type, boundPatterns, properties.ToImmutableAndFree(), hasErrors: hasErrors); + } + + // operandTypeCandidates and opIsCandidates might be modified. + private void Unify(CSharpSyntaxNode node, HashSet operandTypeCandidates, ArrayBuilder opIsCandidates, DiagnosticBag diagnostics) + { + for (int j = opIsCandidates.Count - 1; j >= 0; j--) + { + var firstParamType = opIsCandidates[j].ParameterTypes[0]; + var isAnyGoodOperandCandidate = false; + + // This is going to be used to diagnose the problem if there is no good operand candidate and there is only one opIsCandidate left. + TypeSymbol oneOperandType = null ; + foreach (var operandType in operandTypeCandidates) + { + oneOperandType = operandType; + if (IsAnyConversionForPattern(node, firstParamType, operandType, diagnostics, isDiagnosed: false)) + { + isAnyGoodOperandCandidate = true; + } + } + if (!isAnyGoodOperandCandidate) + { + // If there is only one opIsCandidate left, diagnose the problem by saying that there is no conversion from x type to y type. + if (opIsCandidates.Count == 1) + { + // operandTypeCandidates cannot be empty! + Debug.Assert(oneOperandType != null); + IsAnyConversionForPattern(node, firstParamType, oneOperandType, diagnostics, isDiagnosed: true); + } + opIsCandidates.RemoveAt(j); + } + else + { + RemoveBadTypeCandidates(node, firstParamType, operandTypeCandidates, diagnostics); + } + } + } + + private ImmutableArray BindSubRecursivePatterns(RecursivePatternSyntax node, ArrayBuilder opIsCandidates, DiagnosticBag diagnostics) + { + ArrayBuilder boundPatternsBuilder = ArrayBuilder.GetInstance(); + + // Patterns start after the operand in the parameter list of op_Is: + // operand is Type(subpatterns) => Type.op_Is(operand, subpatterns) + // That's why, it starts from 1 + int paramIndex = 1; + var operandTypeCandidates = PooledHashSet.GetInstance(); + foreach (var syntax in node.PatternList.SubPatterns) + { + // We guarantee that there is no candidate which has a paramater using paramName if paramName is not empty + ChooseOperandTypeCandidates(operandTypeCandidates, opIsCandidates, paramIndex, syntax.NameColon); // every opIsCandidate might have this paramName in different indices. + + BoundPattern boundPattern = this.BindPattern(syntax.Pattern, operandTypeCandidates, diagnostics); + + // Remove the opIsCandidates which do not use one of the operandTypeCandidates in the correct index. + UpdateOpIsCandidates(opIsCandidates, operandTypeCandidates, paramIndex, syntax.NameColon); + + boundPatternsBuilder.Add(boundPattern); + operandTypeCandidates.Clear(); + paramIndex++; + } + operandTypeCandidates.Free(); + + return boundPatternsBuilder.ToImmutableAndFree(); + } + + private ImmutableArray BindSubPropertyPatterns(PropertyPatternSyntax node, ArrayBuilder properties, TypeSymbol type, DiagnosticBag diagnostics) + { + var boundPatternsBuilder = ArrayBuilder.GetInstance(); + foreach (var syntax in node.PatternList.SubPatterns) + { + var propName = syntax.Left; + BoundPattern pattern; + + PropertySymbol property = FindPropertyByName(type, propName); + if ((object)property != null) + { + properties.Add(property); + pattern = this.BindPattern(syntax.Pattern, property.Type, diagnostics); + } + else + { + Error(diagnostics, ErrorCode.ERR_NoSuchMember, propName, type, propName.Identifier.ValueText); + pattern = new BoundWildCardPattern(node, hasErrors: true); + } + boundPatternsBuilder.Add(pattern); + } + return boundPatternsBuilder.ToImmutableAndFree(); + } + + private void RemoveBadTypeCandidates(CSharpSyntaxNode node, TypeSymbol type, HashSet operandTypeCandidates, DiagnosticBag diagnostics) + { + // Only if there is only one candidate left, we should diagnose the issue. + operandTypeCandidates.RemoveWhere((operandType) => + !IsAnyConversionForPattern(node, type, operandType, diagnostics, isDiagnosed: operandTypeCandidates.Count == 1)); + } + + private bool IsAnyConversionForPattern(CSharpSyntaxNode node, TypeSymbol source, TypeSymbol destination, DiagnosticBag diagnostics, bool isDiagnosed) + { + HashSet useSiteDiagnostics = null; + bool isAnyConversion = Conversions.ClassifyImplicitConversion(source, destination, ref useSiteDiagnostics).Exists; + + if (!isAnyConversion) + { + // if the only candidate is this one, report the error + if (isDiagnosed) + { + SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, source, destination); + Error(diagnostics, ErrorCode.ERR_NoImplicitConv, node, distinguisher.First, distinguisher.Second); + } + } + return isAnyConversion; + } + + private static void SetTypeForVarDeclarations(SeparatedSyntaxList subPatterns, ImmutableArray boundPatterns, MethodSymbol opIs) + { + for (int i = 0; i < subPatterns.Count; i++) + { + if (subPatterns[i].Pattern.Kind == SyntaxKind.DeclarationPattern) + { + var declarationPattern = (DeclarationPatternSyntax)subPatterns[i].Pattern; + if (declarationPattern.Type.IsVar) + { + var local = ((BoundDeclarationPattern)boundPatterns[i]).LocalSymbol; + Debug.Assert(local is SourceLocalSymbol); + // Choose the param type from the OpIs. the param index should be i+1 because of the shifting. + var type = opIs.ParameterTypes[FindParamIndex(opIs, subPatterns[i].NameColon, i + 1)]; + ((SourceLocalSymbol)local).SetTypeSymbol(type); + } + } + } + } + + private static void ChooseOperandTypeCandidates(HashSet operandTypeCandidates, ArrayBuilder opIsCandidates, int paramIndex, NameColonSyntax nameColon) + { + foreach (var opIs in opIsCandidates) + { + TypeSymbol operandType = opIs.ParameterTypes[FindParamIndex(opIs, nameColon, paramIndex)]; + operandTypeCandidates.Add(operandType); + } + } + + private static void UpdateOpIsCandidates(ArrayBuilder opIsCandidates, HashSet operandTypeCandidates, int paramIndex, NameColonSyntax nameColon) + { + for (int i = opIsCandidates.Count - 1; i >= 0; i--) + { + var opIs = opIsCandidates[i]; + TypeSymbol operandType = opIs.ParameterTypes[FindParamIndex(opIs, nameColon, paramIndex)]; + if (!operandTypeCandidates.Contains(operandType)) + { + opIsCandidates.RemoveAt(i); + } + } + } + + private static ImmutableArray ConstructPatterns2Params(RecursivePatternSyntax node, MethodSymbol opIs) + { + ArrayBuilder patternsToParams = ArrayBuilder.GetInstance(); + int paramIndex = 1; + foreach (var syntax in node.PatternList.SubPatterns) + { + var patternSyntax = syntax.Pattern; + var nameColon = syntax.NameColon; + + // We already elimated the methods which do not use this name + patternsToParams.Add(FindParamIndex(opIs, nameColon, paramIndex)); + + paramIndex++; + } + return patternsToParams.ToImmutableAndFree(); + } + + // Remove the opIsCandidates which have wrong arity and which do not use a paramName that subPattern has. + private static void RemoveBadParamsCandidates(ArrayBuilder opIsCandidates, SeparatedSyntaxList subPatterns) + { + for (int i = opIsCandidates.Count - 1; i >= 0; i--) + { + var opIs = opIsCandidates[i]; + // Lets decrease the parameter count by 1, because it also has the operand besides the patterns + if (opIs.ParameterCount - 1 != subPatterns.Count) + { + opIsCandidates.RemoveAt(i); + } + else + { + // if there is no problem with the number of the patterns, check the paramName. + foreach (var subPattern in subPatterns) + { + if (FindParamIndex(opIs, subPattern.NameColon) < 0) + { + opIsCandidates.RemoveAt(i); + } + } + } + } + } + + private static int FindParamIndex(MethodSymbol method, NameColonSyntax nameColon, int index = 0) + { + if (nameColon == null) + { + return index; + } + var name = nameColon.Name.Identifier.ValueText; + var parameters = method.Parameters; + for (int i = 0; i < parameters.Length; i++) + { + if (parameters[i].Name == name) + { + return i; + } + } + return -1; + } + + private PropertySymbol FindPropertyByName(TypeSymbol type, IdentifierNameSyntax name) + { + var symbols = ArrayBuilder.GetInstance(); + var result = LookupResult.GetInstance(); + HashSet useSiteDiagnostics = null; + this.LookupMembersWithFallback(result, type, name.Identifier.ValueText, 0, ref useSiteDiagnostics); + + if (result.IsMultiViable) + { + foreach (var symbol in result.Symbols) + { + if (symbol.Kind == SymbolKind.Property) + { + return (PropertySymbol)symbol; + } + } + } + return null; + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/Src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index ac01eb71..ba8895a6 100644 --- a/Src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/Src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -626,6 +626,11 @@ internal BoundExpression BindInferredVariableInitializer(DiagnosticBag diagnosti { if (initializer == null) { + if (errorSyntax.Parent.Parent.Kind == SyntaxKind.DeclarationPattern) + { + return null; + } + if (!errorSyntax.HasErrors) { Error(diagnostics, ErrorCode.ERR_ImplicitlyTypedVariableWithNoInitializer, errorSyntax); @@ -2637,14 +2642,14 @@ protected BoundExpression BindBooleanExpression(ExpressionSyntax node, Diagnosti }; } - public BoundSwitchStatement BindSwitchStatement(SwitchStatementSyntax node, DiagnosticBag diagnostics) + public BoundStatement BindSwitchStatement(SwitchStatementSyntax node, DiagnosticBag diagnostics) { Debug.Assert(node != null); Binder switchBinder = this.GetBinder(node); return switchBinder.BindSwitchExpressionAndSections(node, switchBinder, diagnostics); } - internal virtual BoundSwitchStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) + internal virtual BoundStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) { return this.Next.BindSwitchExpressionAndSections(node, originalBinder, diagnostics); } diff --git a/Src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs b/Src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs index d5119bd3..49992132 100644 --- a/Src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs +++ b/Src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs @@ -133,7 +133,7 @@ internal override ImmutableArray GetDeclaredLocalsForScope(CSharpSy throw ExceptionUtilities.Unreachable; } - internal override BoundSwitchStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) + internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) { // There's supposed to be a SwitchBinder (or other overrider of this method) in the chain. throw ExceptionUtilities.Unreachable; diff --git a/Src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs b/Src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs index 8861c03c..34033158 100644 --- a/Src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs +++ b/Src/Compilers/CSharp/Portable/Binder/LocalScopeBinder.cs @@ -230,6 +230,23 @@ public override void VisitVariableDeclaration(VariableDeclarationSyntax node) } } + public override void VisitDeclarationPattern(DeclarationPatternSyntax node) + { + if (Locals == null) + { + Locals = ArrayBuilder.GetInstance(); + } + + var localSymbol = SourceLocalSymbol.MakeLocal( + Binder.ContainingMemberOrLambda, + Binder, + node.Type, + node.Identifier, + LocalDeclarationKind.Pattern); + + Locals.Add(localSymbol); + } + public override void VisitDeclarationExpression(DeclarationExpressionSyntax node) { Debug.Assert(scopeSegmentRoot != null); diff --git a/Src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorFacts.cs b/Src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorFacts.cs index f745b111..9e21bdc9 100644 --- a/Src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorFacts.cs +++ b/Src/Compilers/CSharp/Portable/Binder/Semantics/Operators/OperatorFacts.cs @@ -114,7 +114,11 @@ public static string OperatorNameFromDeclaration(OperatorDeclarationSyntax decla public static string OperatorNameFromDeclaration(Syntax.InternalSyntax.OperatorDeclarationSyntax declaration) { - if (SyntaxFacts.IsBinaryExpressionOperatorToken(declaration.OperatorToken.Kind)) + if (declaration.OperatorToken.Kind == SyntaxKind.IsKeyword) + { + return WellKnownMemberNames.IsOperatorName; + } + else if (SyntaxFacts.IsBinaryExpressionOperatorToken(declaration.OperatorToken.Kind)) { // Some tokens may be either unary or binary operators (e.g. +, -). if (SyntaxFacts.IsPrefixUnaryExpressionOperatorToken(declaration.OperatorToken.Kind) && diff --git a/Src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs b/Src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs index 9ba3fa50..b3b67747 100644 --- a/Src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs +++ b/Src/Compilers/CSharp/Portable/Binder/SwitchBinder.cs @@ -16,12 +16,14 @@ internal class SwitchBinder : LocalScopeBinder private readonly SwitchStatementSyntax switchSyntax; private TypeSymbol switchGoverningType; private readonly GeneratedLabelSymbol breakLabel; + private readonly bool isMatchStatement; internal SwitchBinder(Binder next, SwitchStatementSyntax switchSyntax) : base(next) { this.switchSyntax = switchSyntax; this.breakLabel = new GeneratedLabelSymbol("break"); + this.isMatchStatement = IsMatchStatement(switchSyntax); } // Dictionary for the switch case/default labels. @@ -96,6 +98,18 @@ override protected ImmutableArray BuildLocals() foreach (var section in switchSyntax.Sections) { builder.AddRange(BuildLocals(section.Statements)); + + // TODO: The locals declared in a switch pattern should be local to that switch section, not exposed throughout the switch + if (isMatchStatement) + { + foreach (var label in section.Labels) + { + if (label.Kind == SyntaxKind.CaseMatchLabel) + { + builder.AddRange(BuildLocals(((CaseMatchLabelSyntax)label).Pattern)); + } + } + } } return builder.ToImmutableAndFree(); @@ -163,6 +177,21 @@ private void BuildSwitchLabels(SyntaxList labelsSyntax, ref A } } + private static bool IsMatchStatement(SwitchStatementSyntax switchStatement) + { + // It is not reliable to use SyntaxTree here because it does not necessarily preserve the ParseOptions. + // When some identifiers are shortened and new nodes are created, the ParseOptions becomes the default one (CSharp6, no preprocessor symbol) + foreach (var section in switchStatement.Sections) + { + foreach (var label in section.Labels) + { + if (label.Kind == SyntaxKind.CaseMatchLabel) + return true; + } + } + return false; + } + private BoundExpression ConvertCaseExpression(TypeSymbol switchGoverningType, CSharpSyntaxNode node, BoundExpression caseExpression, ref ConstantValue constantValueOpt, DiagnosticBag diagnostics, bool isGotoCaseExpr = false) { BoundExpression convertedCaseExpression; @@ -263,13 +292,19 @@ private List FindMatchingSwitchLabels(object key) # region "Switch statement binding methods" - internal override BoundSwitchStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) + internal override BoundStatement BindSwitchExpressionAndSections(SwitchStatementSyntax node, Binder originalBinder, DiagnosticBag diagnostics) { Debug.Assert(this.switchSyntax.Equals(node)); // Bind switch expression and set the switch governing type var boundSwitchExpression = BindSwitchExpressionAndGoverningType(node.Expression, diagnostics); + if (isMatchStatement) + { + ImmutableArray boundMatchSections = BindMatchSections(node.Sections, originalBinder, boundSwitchExpression, diagnostics); + return new BoundMatchStatement(node, ((ScopedExpressionBinder)this.Next).Locals, boundSwitchExpression, Locals, boundMatchSections, this.BreakLabel); + } + // Switch expression might be a constant expression. // For this scenario we can determine the target label of the switch statement // at compile time. @@ -321,6 +356,11 @@ private BoundExpression BindSwitchExpression(ExpressionSyntax node, DiagnosticBa var switchExpression = this.Next.BindValue(node, diagnostics, BindValueKind.RValue); var switchGoverningType = switchExpression.Type; + if (isMatchStatement) + { + return switchExpression; + } + if ((object)switchGoverningType != null && !switchGoverningType.IsErrorType()) { // SPEC: The governing type of a switch statement is established by the switch expression. @@ -530,6 +570,61 @@ private BoundSwitchLabel BindSwitchSectionLabel(SwitchLabelSyntax node, Diagnost hasErrors: hasErrors || hasDuplicateErrors); } + private ImmutableArray BindMatchSections(SyntaxList switchSections, Binder originalBinder, BoundExpression boundSwitchExpression, DiagnosticBag diagnostics) + { + var boundSwitchSectionsBuilder = ArrayBuilder.GetInstance(); + foreach (var sectionSyntax in switchSections) + { + boundSwitchSectionsBuilder.Add(BindMatchSection(sectionSyntax, originalBinder, boundSwitchExpression, diagnostics)); + } + + return boundSwitchSectionsBuilder.ToImmutableAndFree(); + } + + private BoundMatchSection BindMatchSection(SwitchSectionSyntax node, Binder originalBinder, BoundExpression boundSwitchExpression, DiagnosticBag diagnostics) + { + var boundLabelsBuilder = ArrayBuilder.GetInstance(); + foreach (var labelSyntax in node.Labels) + { + BoundMatchLabel boundLabel = BindMatchSectionLabel(labelSyntax, boundSwitchExpression.Type, diagnostics); + boundLabelsBuilder.Add(boundLabel); + } + + var boundStatementsBuilder = ArrayBuilder.GetInstance(); + foreach (var statement in node.Statements) + { + boundStatementsBuilder.Add(originalBinder.BindStatement(statement, diagnostics)); + } + + return new BoundMatchSection(node, boundLabelsBuilder.ToImmutableAndFree(), boundStatementsBuilder.ToImmutableAndFree()); + } + + private BoundMatchLabel BindMatchSectionLabel(SwitchLabelSyntax node, TypeSymbol switchExpressionType, DiagnosticBag diagnostics) + { + BoundPattern patternOpt = null; + BoundExpression conditionOpt = null; + if (node.Kind == SyntaxKind.CaseMatchLabel) + { + var caseMatch = (CaseMatchLabelSyntax)node; + patternOpt = BindPattern(caseMatch.Pattern, switchExpressionType, diagnostics); + if (caseMatch.Condition != null) + { + conditionOpt = BindExpression(caseMatch.Condition, diagnostics); + } + } + else if (node.Kind == SyntaxKind.CaseSwitchLabel) + { + var caseSwitch = (CaseSwitchLabelSyntax)node; + patternOpt = BindConstantPattern(caseSwitch.Value, switchExpressionType, diagnostics); + } + + return new BoundMatchLabel( + syntax: node, + label: new SourceLabelSymbol((MethodSymbol)this.ContainingMemberOrLambda, node), + patternOpt: patternOpt, + conditionOpt: conditionOpt); + } + internal BoundStatement BindGotoCaseOrDefault(GotoStatementSyntax node, DiagnosticBag diagnostics) { Debug.Assert(node.Kind == SyntaxKind.GotoCaseStatement || node.Kind == SyntaxKind.GotoDefaultStatement); diff --git a/Src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/Src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index d5320171..de6964ab 100644 --- a/Src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/Src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -661,6 +661,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -1327,4 +1348,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Src/Compilers/CSharp/Portable/BoundTree/BoundStatementExtensions.cs b/Src/Compilers/CSharp/Portable/BoundTree/BoundStatementExtensions.cs index 34a0a05c..01221382 100644 --- a/Src/Compilers/CSharp/Portable/BoundTree/BoundStatementExtensions.cs +++ b/Src/Compilers/CSharp/Portable/BoundTree/BoundStatementExtensions.cs @@ -11,7 +11,7 @@ internal static partial class BoundStatementExtensions internal static void AssertIsLabeledStatement(this BoundStatement node) { Debug.Assert(node != null); - Debug.Assert(node.Kind == BoundKind.LabelStatement || node.Kind == BoundKind.LabeledStatement || node.Kind == BoundKind.SwitchSection); + Debug.Assert(node.Kind == BoundKind.LabelStatement || node.Kind == BoundKind.LabeledStatement || node.Kind == BoundKind.SwitchSection || node.Kind == BoundKind.MatchSection); } @@ -41,6 +41,17 @@ internal static void AssertIsLabeledStatementWithLabel(this BoundStatement node, Debug.Assert(false); break; + case BoundKind.MatchSection: + foreach (var boundMatchLabel in ((BoundMatchSection)node).BoundMatchLabels) + { + if (boundMatchLabel.Label == label) + { + return; + } + } + Debug.Assert(false); + break; + default: Debug.Assert(false); break; diff --git a/Src/Compilers/CSharp/Portable/BoundTree/BoundTreeVisitors.cs b/Src/Compilers/CSharp/Portable/BoundTree/BoundTreeVisitors.cs index 0fa6eee7..59605c92 100644 --- a/Src/Compilers/CSharp/Portable/BoundTree/BoundTreeVisitors.cs +++ b/Src/Compilers/CSharp/Portable/BoundTree/BoundTreeVisitors.cs @@ -76,6 +76,8 @@ public virtual R Visit(BoundNode node, A arg) return VisitExpressionStatement(node as BoundExpressionStatement, arg); case BoundKind.SwitchStatement: return VisitSwitchStatement(node as BoundSwitchStatement, arg); + case BoundKind.MatchStatement: + return VisitMatchStatement(node as BoundMatchStatement, arg); case BoundKind.BreakStatement: return VisitBreakStatement(node as BoundBreakStatement, arg); case BoundKind.ContinueStatement: @@ -118,6 +120,16 @@ public virtual R Visit(BoundNode node, A arg) return VisitLambda(node as BoundLambda, arg); case BoundKind.NameOfOperator: return VisitNameOfOperator(node as BoundNameOfOperator, arg); + case BoundKind.MatchExpression: + return VisitMatchExpression(node as BoundMatchExpression, arg); + case BoundKind.ConstantPattern: + return VisitConstantPattern(node as BoundConstantPattern, arg); + case BoundKind.DeclarationPattern: + return VisitDeclarationPattern(node as BoundDeclarationPattern, arg); + case BoundKind.WildCardPattern: + return VisitWildCardPattern(node as BoundWildCardPattern, arg); + case BoundKind.RecursivePattern: + return VisitRecursivePattern(node as BoundRecursivePattern, arg); } return VisitInternal(node, arg); diff --git a/Src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/Src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index 0048789e..44802443 100644 --- a/Src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/Src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -94,6 +94,7 @@ + @@ -418,11 +419,13 @@ + + @@ -673,6 +676,10 @@ + + + + @@ -680,6 +687,8 @@ + + @@ -776,6 +785,8 @@ + + diff --git a/Src/Compilers/CSharp/Portable/CSharpParseOptions.cs b/Src/Compilers/CSharp/Portable/CSharpParseOptions.cs index 8de3cb40..4e57d4c0 100644 --- a/Src/Compilers/CSharp/Portable/CSharpParseOptions.cs +++ b/Src/Compilers/CSharp/Portable/CSharpParseOptions.cs @@ -190,4 +190,18 @@ public override int GetHashCode() Hash.Combine((int)this.LanguageVersion, 0)); } } + + internal static partial class CSharpParseOptionsExtensions + { + public static bool IsRecordsEnabled(this CSharpParseOptions options) + { + LanguageVersion availableVersion = options.LanguageVersion; + LanguageVersion requiredVersion = MessageID.IDS_FeatureRecords.RequiredVersion(); + if (availableVersion >= requiredVersion) + { + return options.PreprocessorSymbolNames.Contains(name => name == "RECORDS"); + } + return false; + } + } } \ No newline at end of file diff --git a/Src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/Src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index bdb40c67..92a19b6d 100644 --- a/Src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/Src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -441,7 +441,7 @@ private void CompileNamedType(NamedTypeSymbol symbol) case SymbolKind.Property: { - SourcePropertySymbol sourceProperty = member as SourcePropertySymbol; + PropertySymbol sourceProperty = member as PropertySymbol; if ((object)sourceProperty != null && sourceProperty.IsSealed && compilationState.Emitting) { CompileSynthesizedSealedAccessors(sourceProperty, compilationState); @@ -644,9 +644,23 @@ private void CompileSynthesizedExplicitImplementations(SourceMemberContainerType } } - private void CompileSynthesizedSealedAccessors(SourcePropertySymbol sourceProperty, TypeCompilationState compilationState) + private void CompileSynthesizedSealedAccessors(PropertySymbol property, TypeCompilationState compilationState) { - SynthesizedSealedPropertyAccessor synthesizedAccessor = sourceProperty.SynthesizedSealedAccessorOpt; + MethodSymbol synthesizedAccessor = null; + var sourceProperty = property as SourcePropertySymbol; + + if ((object)sourceProperty != null) + { + synthesizedAccessor = sourceProperty.SynthesizedSealedAccessorOpt; + } + else + { + var synthesizedProperty = property as SynthesizedPropertySymbol; + if ((object)synthesizedProperty != null) + { + synthesizedAccessor = synthesizedProperty.GetMethod; + } + } // we are not generating any observable diagnostics here so it is ok to shortcircuit on global errors. if ((object)synthesizedAccessor != null && !globalHasErrors) @@ -657,7 +671,7 @@ private void CompileSynthesizedSealedAccessors(SourcePropertySymbol sourceProper Debug.Assert(!discardedDiagnostics.HasAnyErrors()); discardedDiagnostics.Free(); - moduleBeingBuiltOpt.AddSynthesizedDefinition(sourceProperty.ContainingType, synthesizedAccessor); + moduleBeingBuiltOpt.AddSynthesizedDefinition(property.ContainingType, synthesizedAccessor); } } @@ -815,7 +829,7 @@ private void CompileMethod( { // See if there are any locals in intialization scope Debug.Assert((object)methodSymbol == sourceMethod); - Debug.Assert(sourceMethod.SyntaxNode.Kind == SyntaxKind.ParameterList); + Debug.Assert(sourceMethod.SyntaxNode.Kind == SyntaxKind.ParameterList || sourceMethod.SyntaxNode.Kind == SyntaxKind.RecordParameterList); var correspondingTypeDeclaration = (TypeDeclarationSyntax)sourceMethod.SyntaxNode.Parent; @@ -1498,11 +1512,11 @@ internal static BoundExpression BindConstructorInitializer(MethodSymbol construc } else { - // Primary constuctor case. - Debug.Assert(syntax.Kind == SyntaxKind.ParameterList); - if (syntax.Parent.Kind == SyntaxKind.ClassDeclaration) + // Primary constructor case. + Debug.Assert(syntax.Kind == SyntaxKind.ParameterList || syntax.Kind == SyntaxKind.RecordParameterList); + if (syntax.Parent.Kind == SyntaxKind.ClassDeclaration || syntax.Parent.Kind == SyntaxKind.RecordDeclaration) { - var classDecl = (ClassDeclarationSyntax)syntax.Parent; + var classDecl = (TypeDeclarationSyntax)syntax.Parent; if (classDecl.BaseList != null && classDecl.BaseList.Types.Count > 0) { @@ -1601,6 +1615,11 @@ internal static BoundExpression BindConstructorInitializer(MethodSymbol construc { bodyToken = ((ClassDeclarationSyntax)containerNode).OpenBraceToken; } + else if (containerNode.Kind == SyntaxKind.RecordDeclaration) + { + // TODO: support ";" as a body by introducing a new nonterminal for the type declaration body. + bodyToken = ((RecordDeclarationSyntax)containerNode).OpenBraceToken; + } else if (containerNode.Kind == SyntaxKind.StructDeclaration) { bodyToken = ((StructDeclarationSyntax)containerNode).OpenBraceToken; @@ -1615,7 +1634,7 @@ internal static BoundExpression BindConstructorInitializer(MethodSymbol construc Debug.Assert(false, "How did we get an implicit constructor added to something that is neither a class nor a struct?"); bodyToken = containerNode.GetFirstToken(); } - + outerBinder = compilation.GetBinderFactory(containerNode.SyntaxTree).GetBinder(containerNode, bodyToken.Position); } else if (initializerArgumentListOpt == null) @@ -1626,7 +1645,9 @@ internal static BoundExpression BindConstructorInitializer(MethodSymbol construc outerBinder = compilation.GetBinderFactory(sourceConstructor.SyntaxTree).GetBinder(syntax.Kind == SyntaxKind.ParameterList ? syntax : - ((ConstructorDeclarationSyntax)syntax).ParameterList); + syntax.Kind == SyntaxKind.RecordParameterList ? + syntax : + ((ConstructorDeclarationSyntax)syntax).ParameterList); } else { diff --git a/Src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs b/Src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs index c2a29743..821076dd 100644 --- a/Src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs +++ b/Src/Compilers/CSharp/Portable/Declarations/DeclarationKind.cs @@ -17,7 +17,8 @@ internal enum DeclarationKind : byte Delegate, Script, Submission, - ImplicitClass + ImplicitClass, + Record } internal static partial class EnumConversions @@ -27,6 +28,7 @@ internal static DeclarationKind ToDeclarationKind(this SyntaxKind kind) switch (kind) { case SyntaxKind.ClassDeclaration: return DeclarationKind.Class; + case SyntaxKind.RecordDeclaration: return DeclarationKind.Record; case SyntaxKind.InterfaceDeclaration: return DeclarationKind.Interface; case SyntaxKind.StructDeclaration: return DeclarationKind.Struct; case SyntaxKind.NamespaceDeclaration: return DeclarationKind.Namespace; diff --git a/Src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs b/Src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs index 0f930d06..206db042 100644 --- a/Src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs +++ b/Src/Compilers/CSharp/Portable/Declarations/DeclarationModifiers.cs @@ -33,11 +33,11 @@ internal enum DeclarationModifiers Indexer = 1 << 18, // not a real modifier, but used to record that indexer syntax was used. Sharing this bit with PrimaryCtor. PrimaryCtor = 1 << 18, // not a real modifier, but used to record that this is a primary constructor. Sharing this bit with Indexer. - Async = 1 << 19, All = (Async | (Async - 1)), // all modifiers - Unset = 1 << 20, // used when a modifiers value hasn't yet been computed + Record = 1 << 20, + Unset = 1 << 21, // used when a modifiers value hasn't yet been computed AccessibilityMask = Private | Protected | Internal | ProtectedInternal | Public, } diff --git a/Src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs b/Src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs index f9ada79d..bc25d9db 100644 --- a/Src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs +++ b/Src/Compilers/CSharp/Portable/Declarations/DeclarationTreeBuilder.cs @@ -298,6 +298,11 @@ public override SingleNamespaceOrTypeDeclaration VisitInterfaceDeclaration(Inter return VisitTypeDeclaration(node, DeclarationKind.Interface); } + public override SingleNamespaceOrTypeDeclaration VisitRecordDeclaration(RecordDeclarationSyntax node) + { + return VisitTypeDeclaration(node, DeclarationKind.Record); + } + private SingleNamespaceOrTypeDeclaration VisitTypeDeclaration(TypeDeclarationSyntax node, DeclarationKind kind) { SingleTypeDeclaration.TypeDeclarationFlags declFlags = node.AttributeLists.Any() ? @@ -309,13 +314,15 @@ private SingleNamespaceOrTypeDeclaration VisitTypeDeclaration(TypeDeclarationSyn declFlags |= SingleTypeDeclaration.TypeDeclarationFlags.HasBaseDeclarations; } - var memberNames = GetNonTypeMemberNames(((Syntax.InternalSyntax.TypeDeclarationSyntax)(node.Green)).Members, - node.Kind == SyntaxKind.ClassDeclaration ? + bool hasPrimaryConstructor = node.Kind == SyntaxKind.ClassDeclaration ? ((ClassDeclarationSyntax)node).ParameterList != null : node.Kind == SyntaxKind.StructDeclaration ? ((StructDeclarationSyntax)node).ParameterList != null : - false, - ref declFlags); + node.Kind == SyntaxKind.RecordDeclaration ? + ((RecordDeclarationSyntax)node).ParameterList != null : + false; + + var memberNames = GetNonTypeMemberNames(((Syntax.InternalSyntax.TypeDeclarationSyntax)(node.Green)).Members, hasPrimaryConstructor, ref declFlags); return new SingleTypeDeclaration( kind: kind, @@ -516,6 +523,7 @@ private static bool CheckMemberForAttributes(Syntax.InternalSyntax.CSharpSyntaxN return (((Syntax.InternalSyntax.CompilationUnitSyntax)member).AttributeLists).Any(); case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: diff --git a/Src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs b/Src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs index d971cd86..1e1458b0 100644 --- a/Src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs +++ b/Src/Compilers/CSharp/Portable/Emitter/Model/NamedTypeSymbolAdapter.cs @@ -606,6 +606,14 @@ bool Cci.ITypeDefinition.IsSealed } } + internal virtual bool IsRecord + { + get + { + return false; + } + } + internal virtual bool IsMetadataSealed { get diff --git a/Src/Compilers/CSharp/Portable/Errors/MessageID.cs b/Src/Compilers/CSharp/Portable/Errors/MessageID.cs index 19a727bf..12c1978e 100644 --- a/Src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/Src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -109,6 +109,7 @@ internal enum MessageID IDS_VersionExperimental = MessageBase + 12694, IDS_FeatureNameof = MessageBase + 12695, IDS_FeatureDictionaryInitializer = MessageBase + 12696, + IDS_FeatureRecords = MessageBase + 12697 } // Message IDs may refer to strings that need to be localized. @@ -146,6 +147,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) // Experimental features. case MessageID.IDS_FeatureDeclarationExpression: case MessageID.IDS_FeaturePrimaryConstructor: + case MessageID.IDS_FeatureRecords: return LanguageVersion.Experimental; // C# 6 features. diff --git a/Src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs b/Src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs index b0bc5f77..abc77319 100644 --- a/Src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs +++ b/Src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs @@ -775,11 +775,16 @@ protected virtual void ReportUnassigned(Symbol symbol, CSharpSyntaxNode node) // CONSIDER: could suppress this diagnostic in cases where the local was declared in a using // or fixed statement because there's a special error code for not initializing those. - ErrorCode errorCode = - (symbol.Kind == SymbolKind.Parameter && ((ParameterSymbol)symbol).RefKind == RefKind.Out) ? - (((ParameterSymbol)symbol).IsThis) ? ErrorCode.ERR_UseDefViolationThis : ErrorCode.ERR_UseDefViolationOut : - ErrorCode.ERR_UseDefViolation; - Diagnostics.Add(errorCode, new SourceLocation(node), symbol.Name); + // ignore SynthesizedLocals! also ignore SourceLocalSymbol whose declaration kind is Pattern, for now! + // TODO: it is important to get definite assignment right for them. A pattern variable is definitely assigned when the enclosing (full) pattern returns true. + if (!(symbol is SynthesizedLocal) && (!(symbol is SourceLocalSymbol) || (((SourceLocalSymbol) symbol).DeclarationKind != LocalDeclarationKind.Pattern))) + { + ErrorCode errorCode = + (symbol.Kind == SymbolKind.Parameter && ((ParameterSymbol)symbol).RefKind == RefKind.Out) ? + (((ParameterSymbol)symbol).IsThis) ? ErrorCode.ERR_UseDefViolationThis : ErrorCode.ERR_UseDefViolationOut : + ErrorCode.ERR_UseDefViolation; + Diagnostics.Add(errorCode, new SourceLocation(node), symbol.Name); + } } alreadyReported[slot] = true; // mark the variable's slot so that we don't complain about the variable again @@ -1415,11 +1420,14 @@ private void ReportUnusedVariables(ImmutableArray locals) private void ReportIfUnused(LocalSymbol symbol, bool assigned) { - if (!usedVariables.Contains(symbol)) + if (symbol.SynthesizedLocalKind == SynthesizedLocalKind.None) { - if (!string.IsNullOrEmpty(symbol.Name)) // avoid diagnostics for parser-inserted names + if (!usedVariables.Contains(symbol)) { - Diagnostics.Add(assigned && writtenVariables.Contains(symbol) ? ErrorCode.WRN_UnreferencedVarAssg : ErrorCode.WRN_UnreferencedVar, symbol.Locations[0], symbol.Name); + if (!string.IsNullOrEmpty(symbol.Name)) // avoid diagnostics for parser-inserted names + { + Diagnostics.Add(assigned && writtenVariables.Contains(symbol) ? ErrorCode.WRN_UnreferencedVarAssg : ErrorCode.WRN_UnreferencedVar, symbol.Locations[0], symbol.Name); + } } } } @@ -1887,6 +1895,27 @@ public override BoundNode VisitDynamicObjectInitializerMember(BoundDynamicObject return null; } + // TODO: pattern variables are definitely assigned when the entire enclosing pattern is true. + public override BoundNode VisitDeclarationPattern(BoundDeclarationPattern node) + { + return null; + } + + public override BoundNode VisitConstantPattern(BoundConstantPattern node) + { + return null; + } + + public override BoundNode VisitWildCardPattern(BoundWildCardPattern node) + { + return null; + } + + public override BoundNode VisitRecursivePattern(BoundRecursivePattern node) + { + return null; + } + #endregion Visitors protected override string Dump(LocalState state) diff --git a/Src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/Src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index 3b53ee79..1dd7985a 100644 --- a/Src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/Src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -818,6 +818,15 @@ protected void RestorePending(SavedPending oldPending) } } break; + case BoundKind.MatchSection: + { + var sec = (BoundMatchSection)node; + foreach (var label in sec.BoundMatchLabels) + { + backwardBranchChanged |= ResolveBranches(label.Label, sec); + } + } + break; default: // there are no other kinds of labels Debug.Assert(false); @@ -1575,6 +1584,18 @@ public override BoundNode VisitSwitchStatement(BoundSwitchStatement node) return null; } + public override BoundNode VisitMatchStatement(BoundMatchStatement node) + { + // visit switch header + LocalState breakState = VisitMatchHeader(node); + SetUnreachable(); + + // visit switch block + VisitMatchBlock(node); + ResolveBreaks(breakState, node.BreakLabel); + return null; + } + private LocalState VisitSwitchHeader(BoundSwitchStatement node) { // Initial value for the Break state for a switch statement is established as follows: @@ -1628,6 +1649,47 @@ private LocalState VisitSwitchHeader(BoundSwitchStatement node) return breakState; } + private LocalState VisitMatchHeader(BoundMatchStatement node) + { + // Initial value for the Break state for a switch statement is established as follows: + // Break state = UnreachableState if either of the following is true: + // (1) there is a default label, or + // (2) the switch expression is constant and there is a matching case label. + // Otherwise, the Break state = current state. + + // visit switch expression + VisitRvalue(node.BoundExpression); + LocalState breakState = this.State; + + // For a switch statement, we simulate a possible jump to the switch labels to ensure that + // the label is not treated as an unused label and a pending branch to the label is noted. + + // However, if switch expression is a constant, we must have determined the single target label + // at bind time, i.e. node.ConstantTargetOpt, and we must simulate a jump only to this label. + + bool hasDefaultLabel = false; + foreach (var section in node.MatchSections) + { + foreach (var boundMatchLabel in section.BoundMatchLabels) + { + var label = boundMatchLabel.Label; + hasDefaultLabel = hasDefaultLabel || label.IdentifierNodeOrToken.CSharpKind() == SyntaxKind.DefaultSwitchLabel; + SetState(breakState.Clone()); + var simulatedGoto = new BoundGotoStatement(node.Syntax, label); + VisitGotoStatement(simulatedGoto); + } + } + + if (hasDefaultLabel) + { + // Condition (1) for an unreachable break state is satisfied + breakState = UnreachableState(); + } + + + return breakState; + } + private void VisitSwitchBlock(BoundSwitchStatement node) { var switchSections = node.SwitchSections; @@ -1639,11 +1701,28 @@ private void VisitSwitchBlock(BoundSwitchStatement node) } } + private void VisitMatchBlock(BoundMatchStatement node) + { + var matchSections = node.MatchSections; + var iLastSection = (matchSections.Length - 1); + // visit switch sections + for (var iSection = 0; iSection <= iLastSection; iSection++) + { + VisitMatchSection(matchSections[iSection], iSection == iLastSection); + } + } + public virtual BoundNode VisitSwitchSection(BoundSwitchSection node, bool lastSection) { return VisitSwitchSection(node); } + public virtual BoundNode VisitMatchSection(BoundMatchSection node, bool lastSection) + { + // TODO: a match variable is definitely assigned when the match is satisfied. + return VisitMatchSection(node); + } + public override BoundNode VisitSwitchSection(BoundSwitchSection node) { // visit switch section labels @@ -1659,6 +1738,21 @@ public override BoundNode VisitSwitchSection(BoundSwitchSection node) return null; } + public override BoundNode VisitMatchSection(BoundMatchSection node) + { + // visit switch section labels + foreach (var boundMatchLabel in node.BoundMatchLabels) + { + //VisitRvalue(boundMatchLabel.PatternOpt); + VisitMatchSectionLabel(boundMatchLabel.Label, node); + } + + // visit switch section body + VisitStatementList(node); + + return null; + } + public override BoundNode VisitArrayAccess(BoundArrayAccess node) { VisitRvalue(node.Expression); @@ -1887,6 +1981,13 @@ public override BoundNode VisitIsOperator(BoundIsOperator node) return null; } + public override BoundNode VisitMatchExpression(BoundMatchExpression node) + { + // TODO: a match variable is definitely assigned *when true* for the enclosing match expression + VisitRvalue(node.Operand); + return null; + } + public override BoundNode VisitMethodGroup(BoundMethodGroup node) { if (node.ReceiverOpt != null) @@ -2105,6 +2206,11 @@ protected virtual void VisitSwitchSectionLabel(LabelSymbol label, BoundSwitchSec VisitLabel(label, node); } + protected virtual void VisitMatchSectionLabel(LabelSymbol label, BoundMatchSection node) + { + VisitLabel(label, node); + } + public override BoundNode VisitLabelStatement(BoundLabelStatement node) { VisitLabel(node.Label, node); diff --git a/Src/Compilers/CSharp/Portable/Lowering/InitializerRewriter.cs b/Src/Compilers/CSharp/Portable/Lowering/InitializerRewriter.cs index 76dc5f57..e04dfd57 100644 --- a/Src/Compilers/CSharp/Portable/Lowering/InitializerRewriter.cs +++ b/Src/Compilers/CSharp/Portable/Lowering/InitializerRewriter.cs @@ -110,6 +110,9 @@ private static BoundStatement RewriteFieldInitializer(BoundFieldInitializer fiel { WasCompilerGenerated = true }) { WasCompilerGenerated = true }; + if (syntax.Kind == SyntaxKind.RecordParameter) + return boundStatement; + Debug.Assert(syntax is ExpressionSyntax); // Should be the initial value. Debug.Assert(syntax.Parent.Kind == SyntaxKind.EqualsValueClause); switch (syntax.Parent.Parent.Kind) diff --git a/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchExpression.cs b/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchExpression.cs new file mode 100644 index 00000000..8b289dfa --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchExpression.cs @@ -0,0 +1,258 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed partial class LocalRewriter + { + public override BoundNode VisitMatchExpression(BoundMatchExpression node) + { + BoundExpression loweredOperand = VisitExpression(node.Operand); + BoundPattern pattern = node.Pattern; + var F = this.factory; + + switch (pattern.Kind) + { + case BoundKind.WildCardPattern: + { + return F.Literal(true); + } + + case BoundKind.ConstantPattern: + { + var constantPattern = (BoundConstantPattern)pattern; + var constant = constantPattern.BoundConstant; + + return CompareWithConstant(loweredOperand, constant); + } + + case BoundKind.DeclarationPattern: + { + // TODO: Support for nullable types. If there is a nullable type, rewrite it to: (operand.HasValue && (local = (type) operand.GetValue)) + var declarationPattern = (BoundDeclarationPattern)pattern; + var local = declarationPattern.LocalSymbol; + var type = local.Type; + // we cannot use as operator with primitive types. + // operand is type && (local = (type)operand) != null + + BoundExpression rewritten; + if (loweredOperand.Type.IsNullableType()) + { + rewritten = F.Property(loweredOperand, "HasValue"); + loweredOperand = F.Call(loweredOperand, loweredOperand.Type, "GetValueOrDefault", ImmutableArray.Empty); + } + else + { + rewritten = F.Is(loweredOperand, type); + } + + rewritten = F.LogicalAnd(rewritten, + F.Is(F.AssignmentExpression(F.Local(local), F.Cast(type, loweredOperand)), type)); + //if (type.IsValueType) + //{ + // // if it already passed the type checking in the binder, it should be converted. + // // (local = (type)operand) is type . + // newExpression = F.ObjectNotEqual(F.As(F.AssignmentExpression(F.Local(local), F.Cast(type, loweredOperand)), type, Conversion.ExplicitNumeric), + // F.Null(type)); + //} + + // F.LogicalAnd(F.Is(loweredOperand, type), + // F.IntNotEqual(F.AssignmentExpression(F.Local(local), F.Cast(type, loweredOperand)), + // F.Null(type))); + + // operand is type && (local = (type)operand) is type + + return rewritten; + } + + case BoundKind.RecursivePattern: + return RewriteRecursivePattern((BoundRecursivePattern)pattern, loweredOperand); + + case BoundKind.PropertyPattern: + return RewritePropertyPattern((BoundPropertyPattern)pattern, loweredOperand); + default: + return null; + } + } + + private BoundNode RewriteRecursivePattern(BoundRecursivePattern boundRecursivePattern, BoundExpression operand) + { + var F = this.factory; + + TypeSymbol type = boundRecursivePattern.Type; + ImmutableArray patternsToParams = boundRecursivePattern.PatternsToParams; + ImmutableArray patterns = boundRecursivePattern.Patterns; + + var isOperator = boundRecursivePattern.IsOperator; + var isOperatorParameterTypes = isOperator.ParameterTypes; + + BoundExpression[] isOperatorCallArguments = new BoundExpression[isOperatorParameterTypes.Length]; + RefKind[] isOperatorCallArgumentsRefKinds = new RefKind[isOperatorParameterTypes.Length]; + + // operand is Add(...) => operand is Add && Add.matches((Add)operand, ...) + BoundExpression current = F.Is(operand, isOperatorParameterTypes[0]); + + SmallDictionary synthesizedLocalsMap = new SmallDictionary(); + + isOperatorCallArguments[0] = F.Cast(isOperatorParameterTypes[0], operand); + isOperatorCallArgumentsRefKinds[0] = RefKind.None; + + Debug.Assert((isOperatorCallArguments.Length - 1) == patterns.Length); + + // Adding arguments to the isOperatorCall based on the patternsToParamsOpt + for (int i = 0; i < patterns.Length; i++) + { + var pattern = patterns[i]; + var paramIndex = patternsToParams[i]; + var paramType = isOperatorParameterTypes[paramIndex]; + LocalSymbol local = F.SynthesizedLocal(paramType); + synthesizedLocalsMap.Add(pattern, local); + + isOperatorCallArguments[paramIndex] = F.Local(local); + isOperatorCallArgumentsRefKinds[paramIndex] = RefKind.Out; + } + + var isOperatorCall = F.Call(null, isOperator, isOperatorCallArguments.AsImmutable(), isOperatorCallArgumentsRefKinds.AsImmutable()); + + current = F.Binary(BinaryOperatorKind.LogicalAnd, F.SpecialType(SpecialType.System_Boolean), + current, + isOperatorCall); + + + foreach (KeyValuePair entry in synthesizedLocalsMap) + { + BoundPattern pattern = entry.Key; + LocalSymbol synLocal = entry.Value; + + BoundExpression newBoundExpression = (BoundExpression)Visit(F.Match(F.Local(synLocal), pattern)); + + if (newBoundExpression != null) + current = F.Binary(BinaryOperatorKind.LogicalAnd, F.SpecialType(SpecialType.System_Boolean), + current, newBoundExpression); + } + + return F.Sequence(synthesizedLocalsMap.Values.AsImmutable(), current); + } + + private BoundNode RewritePropertyPattern(BoundPropertyPattern boundPropertyPattern, BoundExpression operand) + { + var F = this.factory; + var type = boundPropertyPattern.Type; + var patterns = boundPropertyPattern.Patterns; + var properties = boundPropertyPattern.Properties; + + BoundExpression current = F.Is(operand, type); + operand = F.Cast(type, operand); + + for (int i = 0; i < patterns.Length; i++) + { + var pattern = patterns[i]; + var property = properties[i]; + BoundExpression newOperand = F.Call(operand, property.GetMethod); + + var temp = (BoundExpression)Visit(F.Match(newOperand, pattern)); + current = F.LogicalAnd(current, temp); + } + return current; + } + + private BoundExpression CompareWithConstant(BoundExpression operand, BoundExpression constant) + { + var F = this.factory; + + // operand is null => (object)operand == null + if (constant.Type == null) + { + return F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Object), operand), constant); + } + else if (ConversionsBase.IsNumericType(constant.Type.SpecialType)) + { + if (operand.Type.SpecialType == SpecialType.System_Object) + { + var helperMethod = NumericConstantHelperMethodManager(operand, constant); + var arguments = ImmutableArray.Create(operand, constant); + + var call = F.Call(null, helperMethod, arguments); + return call; + } + else if (operand.Type.IsNullableType()) + { + var temp = F.Property(operand, "HasValue"); + operand = F.Call(operand, operand.Type, "GetValueOrDefault", ImmutableArray.Empty); + return F.LogicalAnd(temp, F.ObjectEqual(operand, constant)); + } + return F.ObjectEqual(operand, constant); + } + else + { + if (operand.Type.IsNullableType()) + { + var temp = F.Property(operand, "HasValue"); + operand = F.Call(operand, operand.Type, "GetValueOrDefault", ImmutableArray.Empty); + return F.LogicalAnd(temp, F.ObjectEqual(F.Cast(constant.Type, operand), constant)); + } + return F.LogicalAnd(F.Is(operand, constant.Type), + F.ObjectEqual(F.Cast(constant.Type, operand), constant)); + } + } + + private MethodSymbol NumericConstantHelperMethodManager(BoundExpression left, BoundExpression constant) + { + var f = this.factory; + + var classSymbol = f.CurrentClass; + string methodName = HelperMethodName(constant.Type.SpecialType); + + var symbols = f.CompilationState.ModuleBuilderOpt.GetSynthesizedMethods(classSymbol); + + if (symbols != null) + { + foreach (var symbol in symbols) + { + if (symbol.Name == methodName) + return (MethodSymbol)symbol; + } + } + + TypeSymbol boolType = this.compilation.GetSpecialType(SpecialType.System_Boolean); + var helperMethod = new SynthesizedConstantHelperMethod(methodName, classSymbol, left.Type, constant.Type, boolType, f.CompilationState, diagnostics); + + // add the method to module + if (f.CompilationState.Emitting) + { + f.CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(classSymbol, helperMethod); + } + return helperMethod; + + } + + private static string HelperMethodName(SpecialType type) + { + switch (type) + { + case SpecialType.System_Int32: + return "<>Int32Helper"; + case SpecialType.System_UInt32: + return "<>UInt32Helper"; + case SpecialType.System_Int64: + return "<>Int64Helper"; + case SpecialType.System_UInt64: + return "<>UInt64Helper"; + case SpecialType.System_Double: + return "<>DoubleHelper"; + case SpecialType.System_Single: + return "<>FloatHelper"; + case SpecialType.System_Decimal: + return "<>DecimalHelper"; + default: + throw ExceptionUtilities.UnexpectedValue(type); + } + } + + } +} diff --git a/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchStatement.cs b/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchStatement.cs new file mode 100644 index 00000000..7897bb1c --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_MatchStatement.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CSharp.Symbols; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal sealed partial class LocalRewriter + { + public override BoundNode VisitMatchStatement(BoundMatchStatement boundMatch) + { + var F = this.factory; + var boolType = F.SpecialType(SpecialType.System_Boolean); + + var leftExpr = boundMatch.BoundExpression; + + var conditionsBuilder = ArrayBuilder.GetInstance(); + var blocksBuilder = ArrayBuilder.GetInstance(); + + bool isAnyDefaultLabel = false; + foreach (var section in boundMatch.MatchSections) + { + BoundPattern boundPattern = null; + BoundExpression current = null; + + foreach (var label in section.BoundMatchLabels) + { + boundPattern = label.PatternOpt; + + if (label.Syntax.Kind == SyntaxKind.DefaultSwitchLabel) + { + isAnyDefaultLabel = true; + break; + } + BoundExpression temp = new BoundMatchExpression(boundPattern.Syntax, leftExpr, boundPattern, boolType) { WasCompilerGenerated = true }; + + if (label.ConditionOpt != null) + { + temp = F.LogicalAnd(temp, label.ConditionOpt); + } + + current = current == null ? (BoundExpression)temp : + F.Binary(BinaryOperatorKind.Or, F.SpecialType(SpecialType.System_Boolean), current, temp); + } + + conditionsBuilder.Add((BoundExpression)Visit(current)); + + // Break cannot be converted to the goto's because we cannot use VisitSwitchStatement(BoundSwitchStatement) method. This is BoundMatchStatement. + // We just remove every statement after the break in the current section. If there is no break statement, we DO NOT add the statements from the next section. + // TODO: We have to manually transform break statements to the goto statements accordingly. + var statements = section.Statements; + bool isAnyBreakStatement = false; + foreach (var statement in section.Statements) + { + if (statement.Kind == BoundKind.BreakStatement) + { + isAnyBreakStatement = true; + } + if (isAnyBreakStatement) + { + statements = statements.Remove(statement); + } + } + blocksBuilder.Add((BoundBlock)Visit(F.Block(statements))); + } + + BoundStatement previous; + int length = blocksBuilder.Count; + if (isAnyDefaultLabel) + { + previous = blocksBuilder[length - 1]; + length--; + } + else + { + previous = F.Block(); + } + + for (int i = length - 1; i >= 0; i--) + { + // We're creating nested if statements from the last section to the first one. + // We only add the locals in the if statement which is created for the first section. + // Because the if statements are created for the other sections are under the first one, they can still use these locals. + previous = F.If(i == 0 ? boundMatch.InnerLocals : ImmutableArray.Empty, conditionsBuilder[i], blocksBuilder[i], previous); + } + conditionsBuilder.Free(); + blocksBuilder.Free(); + return previous; + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs b/Src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs index c2761785..02de84ea 100644 --- a/Src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs +++ b/Src/Compilers/CSharp/Portable/Lowering/SyntheticBoundNodeFactory.cs @@ -420,6 +420,21 @@ public BoundAsOperator As(BoundExpression operand, TypeSymbol type) return new BoundAsOperator(this.Syntax, operand, Type(type), Conversion.ExplicitReference, type) { WasCompilerGenerated = true }; } + public BoundAsOperator As(BoundExpression operand, TypeSymbol type, Conversion k) + { + return new BoundAsOperator(this.Syntax, operand, Type(type), k, type) { WasCompilerGenerated = true }; + } + + public BoundIsOperator Is(BoundExpression operand, TypeSymbol type) + { + return new BoundIsOperator(this.Syntax, operand, Type(type), Conversion.ExplicitReference, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean)) { WasCompilerGenerated = true }; + } + + public BoundMatchExpression Match(BoundExpression operand, BoundPattern pattern) + { + return new BoundMatchExpression(this.Syntax, operand, pattern, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean)) { WasCompilerGenerated = true }; + } + public BoundBinaryOperator LogicalAnd(BoundExpression left, BoundExpression right) { return Binary(BinaryOperatorKind.LogicalBoolAnd, SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean), left, right); @@ -579,12 +594,19 @@ public BoundCall Call(BoundExpression receiver, MethodSymbol method, params Boun } public BoundCall Call(BoundExpression receiver, MethodSymbol method, ImmutableArray args) + { + Debug.Assert(method.ParameterCount == args.Length); + return Call(receiver, method, args, ImmutableArray.Empty); + } + + public BoundCall Call(BoundExpression receiver, MethodSymbol method, ImmutableArray args, ImmutableArray refKinds) { Debug.Assert(method.ParameterCount == args.Length); return new BoundCall( Syntax, receiver, method, args, - ImmutableArray.Empty, ImmutableArray.Empty, false, false, false, - ImmutableArray.Empty, LookupResultKind.Viable, method.ReturnType) { WasCompilerGenerated = true }; + ImmutableArray.Empty, refKinds, false, false, false, + ImmutableArray.Empty, LookupResultKind.Viable, method.ReturnType) + { WasCompilerGenerated = true }; } public BoundCall Call(BoundExpression receiver, TypeSymbol declaringType, string methodName, ImmutableArray args) @@ -612,17 +634,11 @@ public BoundExpression Coalesce(BoundExpression left, BoundExpression right) public BoundStatement If(BoundExpression condition, BoundStatement thenClause, BoundStatement elseClauseOpt = null) { - // We translate - // if (condition) thenClause else elseClause - // as - // { - // ConditionalGoto(!condition) alternative - // thenClause - // goto afterif; - // alternative: - // elseClause - // afterif: - // } + return If(ImmutableArray.Empty, condition, thenClause, elseClauseOpt); + } + + public BoundStatement If(ImmutableArray locals, BoundExpression condition, BoundStatement thenClause, BoundStatement elseClauseOpt) + { Debug.Assert(thenClause != null); var statements = ArrayBuilder.GetInstance(); @@ -645,7 +661,7 @@ public BoundStatement If(BoundExpression condition, BoundStatement thenClause, B } statements.Add(Label(afterif)); - return Block(statements.ToImmutableAndFree()); + return Block(locals, statements.ToImmutableAndFree()); } public BoundStatement For(BoundExpression initialization, BoundExpression termination, BoundExpression increment, BoundStatement body) @@ -795,6 +811,11 @@ public BoundLiteral Literal(Boolean value) return new BoundLiteral(Syntax, ConstantValue.Create(value), SpecialType(Microsoft.CodeAnalysis.SpecialType.System_Boolean)) { WasCompilerGenerated = true }; } + public BoundLiteral Literal(ConstantValue value) + { + return new BoundLiteral(Syntax, value, SpecialType(value.SpecialType)) { WasCompilerGenerated = true }; + } + public BoundLiteral Literal(string value) { var stringConst = ConstantValue.Create(value); @@ -945,6 +966,19 @@ private MethodSymbol GetFieldFromHandleMethod(NamedTypeSymbol fieldContainer) CodeAnalysis.WellKnownMember.System_Reflection_FieldInfo__GetFieldFromHandle2); } + public BoundExpression Cast(TypeSymbol type, BoundExpression arg) + { + HashSet useSiteDiagnostics = null; + Conversion c = Compilation.Conversions.ClassifyConversionForCast(arg, type, ref useSiteDiagnostics); + + Debug.Assert(useSiteDiagnostics.IsNullOrEmpty()); + + // If this happens, we should probably check if the method has ObsoleteAttribute. + Debug.Assert((object)c.Method == null, "Why are we synthesizing a user-defined conversion after initial binding?"); + + return new BoundConversion(Syntax, arg, c, @checked: false, explicitCastInCode: true, constantValueOpt: ConstantValue.NotAvailable, type: type) { WasCompilerGenerated = true }; + } + public BoundExpression Convert(TypeSymbol type, BoundExpression arg) { HashSet useSiteDiagnostics = null; diff --git a/Src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/Src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index e6fae950..fa99c0aa 100644 --- a/Src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/Src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -1164,26 +1164,28 @@ private AttributeArgumentSyntax ParseAttributeArgument(ref bool shouldHaveName) return syntaxFactory.AttributeArgument(nameEquals, nameColon, expr); } + // TODO: change to 1<(); + + try + { + var openKind = SyntaxKind.OpenParenToken; + var closeKind = SyntaxKind.CloseParenToken; + + SyntaxToken open; + SyntaxToken close; + this.ParseRecordParameterList(out open, parameters, out close, openKind, closeKind, allowThisKeyword, allowDefaults, allowAttributes); + return syntaxFactory.RecordParameterList(open, parameters, close); + } + finally + { + this.pool.Free(parameters); + } + } + internal BracketedParameterListSyntax ParseBracketedParameterList(bool allowDefaults = true) { if (this.IsIncrementalAndFactoryContextMatches && CanReuseBracketedParameterList(this.CurrentNode as CSharp.Syntax.BracketedParameterListSyntax)) @@ -3829,7 +3906,7 @@ private void ParseParameterList( { if (this.CurrentToken.Kind != closeKind) { - tryAgain: + tryAgain: int mustBeLastIndex = -1; bool mustBeLastHadParams = false; bool hasParams = false; @@ -3903,6 +3980,101 @@ private void ParseParameterList( } } + private void ParseRecordParameterList( + out SyntaxToken open, + SeparatedSyntaxListBuilder nodes, + out SyntaxToken close, + SyntaxKind openKind, + SyntaxKind closeKind, + bool allowThisKeyword, + bool allowDefaults, + bool allowAttributes) + { + open = this.EatToken(openKind); + + var saveTerm = this.termState; + this.termState |= TerminatorState.IsEndOfParameterList; + + var attributes = this.pool.Allocate(); + var modifiers = this.pool.Allocate(); + try + { + if (this.CurrentToken.Kind != closeKind) + { + tryAgain: + int mustBeLastIndex = -1; + bool mustBeLastHadParams = false; + bool hasParams = false; + bool hasArgList = false; + + if (this.IsPossibleParameter(allowThisKeyword) || this.CurrentToken.Kind == SyntaxKind.CommaToken) + { + // first parameter + attributes.Clear(); + modifiers.Clear(); + var parameter = this.ParseRecordParameter(attributes, modifiers, allowThisKeyword, allowDefaults, allowAttributes); + nodes.Add(parameter); + hasParams = modifiers.Any(SyntaxKind.ParamsKeyword); + hasArgList = parameter.Identifier.Kind == SyntaxKind.ArgListKeyword; + bool mustBeLast = hasParams || hasArgList; + if (mustBeLast && mustBeLastIndex == -1) + { + mustBeLastIndex = nodes.Count - 1; + mustBeLastHadParams = hasParams; + } + + // additional parameters + while (true) + { + if (this.CurrentToken.Kind == closeKind) + { + break; + } + else if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleParameter(allowThisKeyword)) + { + nodes.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + attributes.Clear(); + modifiers.Clear(); + parameter = this.ParseRecordParameter(attributes, modifiers, allowThisKeyword, allowDefaults, allowAttributes); + nodes.Add(parameter); + hasParams = modifiers.Any(SyntaxKind.ParamsKeyword); + hasArgList = parameter.Identifier.Kind == SyntaxKind.ArgListKeyword; + mustBeLast = hasParams || hasArgList; + if (mustBeLast && mustBeLastIndex == -1) + { + mustBeLastIndex = nodes.Count - 1; + mustBeLastHadParams = hasParams; + } + + continue; + } + else if (this.SkipBadRecordParameterListTokens(ref open, nodes, SyntaxKind.CommaToken, closeKind, allowThisKeyword) == PostSkipAction.Abort) + { + break; + } + } + } + else if (this.SkipBadRecordParameterListTokens(ref open, nodes, SyntaxKind.IdentifierToken, closeKind, allowThisKeyword) == PostSkipAction.Continue) + { + goto tryAgain; + } + + if (mustBeLastIndex >= 0 && mustBeLastIndex < nodes.Count - 1) + { + nodes[mustBeLastIndex] = this.AddError(nodes[mustBeLastIndex], mustBeLastHadParams ? ErrorCode.ERR_ParamsLast : ErrorCode.ERR_VarargsLast); + } + } + + this.termState = saveTerm; + close = this.EatToken(closeKind); + } + finally + { + this.pool.Free(modifiers); + this.pool.Free(attributes); + } + } + private bool IsEndOfParameterList() { return this.CurrentToken.Kind == SyntaxKind.CloseParenToken @@ -3917,6 +4089,14 @@ private PostSkipAction SkipBadParameterListTokens(ref SyntaxToken open, Separate expected); } + private PostSkipAction SkipBadRecordParameterListTokens(ref SyntaxToken open, SeparatedSyntaxListBuilder list, SyntaxKind expected, SyntaxKind closeKind, bool allowThisKeyword) + { + return this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list, + p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossibleParameter(allowThisKeyword), + p => p.CurrentToken.Kind == closeKind || p.IsTerminator(), + expected); + } + private bool IsPossibleParameter(bool allowThisKeyword) { switch (this.CurrentToken.Kind) @@ -4068,147 +4248,231 @@ private ParameterSyntax ParseParameter( return syntaxFactory.Parameter(attributes, modifiers.ToTokenList(), type, name, def); } - private static bool IsParameterModifier(SyntaxKind kind, bool allowThisKeyword) + private RecordParameterSyntax ParseRecordParameter( + SyntaxListBuilder attributes, + SyntaxListBuilder modifiers, + bool allowThisKeyword, + bool allowDefaults, + bool allowAttributes) { - return GetParamFlags(kind, allowThisKeyword) != ParamFlags.None; - } + //TODO: Add support for incremental parser - [Flags] - private enum ParamFlags - { - None = 0x00, - This = 0x01, - Ref = 0x02, - Out = 0x04, - Params = 0x08, - } + this.ParseAttributeDeclarations(attributes, allowAttributes); + this.ParseParameterModifiers(modifiers, allowThisKeyword); - private static ParamFlags GetParamFlags(SyntaxKind kind, bool allowThisKeyword) - { - switch (kind) - { - case SyntaxKind.ThisKeyword: - // if (this.IsCSharp3Enabled) - return (allowThisKeyword ? ParamFlags.This : ParamFlags.None); + var hasArgList = this.CurrentToken.Kind == SyntaxKind.ArgListKeyword; - // goto default; - case SyntaxKind.RefKeyword: - return ParamFlags.Ref; - case SyntaxKind.OutKeyword: - return ParamFlags.Out; - case SyntaxKind.ParamsKeyword: - return ParamFlags.Params; - default: - return ParamFlags.None; + TypeSyntax type = null; + if (!hasArgList) + { + type = this.ParseType(true); + } + else if (this.IsPossibleType()) + { + type = this.ParseType(true); + type = WithAdditionalDiagnostics(type, this.GetExpectedTokenError(SyntaxKind.CloseParenToken, SyntaxKind.IdentifierToken, 0, type.Width)); } - } - private void ParseParameterModifiers(SyntaxListBuilder modifiers, bool allowThisKeyword) - { - var flags = ParamFlags.None; + SyntaxToken name = null; + if (!hasArgList) + { + name = this.ParseIdentifierToken(); - while (IsParameterModifier(this.CurrentToken.Kind, allowThisKeyword)) + // When the user type "int foo[]", give them a useful error + if (this.CurrentToken.Kind == SyntaxKind.OpenBracketToken && this.PeekToken(1).Kind == SyntaxKind.CloseBracketToken) { - var mod = this.EatToken(); - - if (mod.Kind == SyntaxKind.ThisKeyword || - mod.Kind == SyntaxKind.RefKeyword || - mod.Kind == SyntaxKind.OutKeyword || - mod.Kind == SyntaxKind.ParamsKeyword) - { - if (mod.Kind == SyntaxKind.ThisKeyword) - { - mod = CheckFeatureAvailability(mod, MessageID.IDS_FeatureExtensionMethod); + var open = this.EatToken(); + var close = this.EatToken(); + open = this.AddError(open, ErrorCode.ERR_BadArraySyntax); + name = AddTrailingSkippedSyntax(name, SyntaxList.List(open, close)); + } + } + else if (this.IsPossibleName()) + { + // Current token is an identifier token, we expected a CloseParenToken. + // Get the expected token error for the missing token with correct diagnostic + // span and then parse the identifier token. - if ((flags & ParamFlags.This) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.ThisKeyword)); - } - else if ((flags & ParamFlags.Out) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadOutWithThis); - } - else if ((flags & ParamFlags.Ref) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); - } - else if ((flags & ParamFlags.Params) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadParamModThis); - } - else - { - flags |= ParamFlags.This; - } + SyntaxDiagnosticInfo diag = this.GetExpectedTokenError(SyntaxKind.CloseParenToken, SyntaxKind.IdentifierToken); + name = this.ParseIdentifierToken(); + name = WithAdditionalDiagnostics(name, diag); + } + else + { + // name is not optional on ParameterSyntax + name = this.EatToken(SyntaxKind.ArgListKeyword); + } + + ColonNameSyntax colonName = null; + if (this.CurrentToken.Kind == SyntaxKind.ColonToken) + { + var colon = this.EatToken(SyntaxKind.ColonToken); + var name2 = this.ParseIdentifierName(); + colonName = syntaxFactory.ColonName(colon, name2); + colonName = CheckFeatureAvailability(colonName, MessageID.IDS_FeatureNamedArgument); + } + + EqualsValueClauseSyntax def = null; + if (this.CurrentToken.Kind == SyntaxKind.EqualsToken) + { + var equals = this.EatToken(SyntaxKind.EqualsToken); + var expr = this.ParseExpression(); + def = syntaxFactory.EqualsValueClause(equals, expr); + + if (!allowDefaults) + { + def = this.AddError(def, equals, ErrorCode.ERR_DefaultValueNotAllowed); + } + else + { + def = CheckFeatureAvailability(def, MessageID.IDS_FeatureOptionalParameter); + } + } + + return syntaxFactory.RecordParameter(attributes, modifiers.ToTokenList(), type, name, colonName, def); + } + + private static bool IsParameterModifier(SyntaxKind kind, bool allowThisKeyword) + { + return GetParamFlags(kind, allowThisKeyword) != ParamFlags.None; + } + + [Flags] + private enum ParamFlags + { + None = 0x00, + This = 0x01, + Ref = 0x02, + Out = 0x04, + Params = 0x08, + } + + private static ParamFlags GetParamFlags(SyntaxKind kind, bool allowThisKeyword) + { + switch (kind) + { + case SyntaxKind.ThisKeyword: + // if (this.IsCSharp3Enabled) + return (allowThisKeyword ? ParamFlags.This : ParamFlags.None); + + // goto default; + case SyntaxKind.RefKeyword: + return ParamFlags.Ref; + case SyntaxKind.OutKeyword: + return ParamFlags.Out; + case SyntaxKind.ParamsKeyword: + return ParamFlags.Params; + default: + return ParamFlags.None; + } + } + + private void ParseParameterModifiers(SyntaxListBuilder modifiers, bool allowThisKeyword) + { + var flags = ParamFlags.None; + + while (IsParameterModifier(this.CurrentToken.Kind, allowThisKeyword)) + { + var mod = this.EatToken(); + + if (mod.Kind == SyntaxKind.ThisKeyword || + mod.Kind == SyntaxKind.RefKeyword || + mod.Kind == SyntaxKind.OutKeyword || + mod.Kind == SyntaxKind.ParamsKeyword) + { + if (mod.Kind == SyntaxKind.ThisKeyword) + { + mod = CheckFeatureAvailability(mod, MessageID.IDS_FeatureExtensionMethod); + + if ((flags & ParamFlags.This) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.ThisKeyword)); } - else if (mod.Kind == SyntaxKind.RefKeyword) + else if ((flags & ParamFlags.Out) != 0) { - if ((flags & ParamFlags.Ref) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.RefKeyword)); - } - else if ((flags & ParamFlags.This) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); - } - else if ((flags & ParamFlags.Params) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_ParamsCantBeRefOut); - } - else if ((flags & ParamFlags.Out) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); - } - else - { - flags |= ParamFlags.Ref; - } + mod = this.AddError(mod, ErrorCode.ERR_BadOutWithThis); } - else if (mod.Kind == SyntaxKind.OutKeyword) + else if ((flags & ParamFlags.Ref) != 0) { - if ((flags & ParamFlags.Out) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.OutKeyword)); - } - else if ((flags & ParamFlags.This) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadOutWithThis); - } - else if ((flags & ParamFlags.Params) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_ParamsCantBeRefOut); - } - else if ((flags & ParamFlags.Ref) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); - } - else - { - flags |= ParamFlags.Out; - } + mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); } - else if (mod.Kind == SyntaxKind.ParamsKeyword) + else if ((flags & ParamFlags.Params) != 0) { - if ((flags & ParamFlags.Params) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.ParamsKeyword)); - } - else if ((flags & ParamFlags.This) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_BadParamModThis); - } - else if ((flags & (ParamFlags.Ref | ParamFlags.Out | ParamFlags.This)) != 0) - { - mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); - } - else - { - flags |= ParamFlags.Params; - } + mod = this.AddError(mod, ErrorCode.ERR_BadParamModThis); + } + else + { + flags |= ParamFlags.This; + } + } + else if (mod.Kind == SyntaxKind.RefKeyword) + { + if ((flags & ParamFlags.Ref) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.RefKeyword)); + } + else if ((flags & ParamFlags.This) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_BadRefWithThis); + } + else if ((flags & ParamFlags.Params) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_ParamsCantBeRefOut); + } + else if ((flags & ParamFlags.Out) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); + } + else + { + flags |= ParamFlags.Ref; + } + } + else if (mod.Kind == SyntaxKind.OutKeyword) + { + if ((flags & ParamFlags.Out) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.OutKeyword)); + } + else if ((flags & ParamFlags.This) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_BadOutWithThis); + } + else if ((flags & ParamFlags.Params) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_ParamsCantBeRefOut); + } + else if ((flags & ParamFlags.Ref) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); + } + else + { + flags |= ParamFlags.Out; } } + else if (mod.Kind == SyntaxKind.ParamsKeyword) + { + if ((flags & ParamFlags.Params) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_DupParamMod, SyntaxFacts.GetText(SyntaxKind.ParamsKeyword)); + } + else if ((flags & ParamFlags.This) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_BadParamModThis); + } + else if ((flags & (ParamFlags.Ref | ParamFlags.Out | ParamFlags.This)) != 0) + { + mod = this.AddError(mod, ErrorCode.ERR_MultiParamMod); + } + else + { + flags |= ParamFlags.Params; + } + } + } - modifiers.Add(mod); + modifiers.Add(mod); } } @@ -4587,72 +4851,72 @@ private VariableDeclaratorSyntax ParseVariableDeclarator(TypeSyntax parentType, if (!isExpressionContext) { - // Check for the common pattern of: - // - // C //<-- here - // Console.WriteLine(); - // - // Standard greedy parsing will assume that this should be parsed as a variable - // declaration: "C Console". We want to avoid that as it can confused parts of the - // system further up. So, if we see certain things following the identifier, then we can - // assume it's not the actual name. - // - // So, if we're after a newline and we see a name followed by the list below, then we - // assume that we're accidently consuming too far into the next statement. - // - // , , any binary operator (except =), . None of these characters - // are allowed in a normal variable declaration. This also provides a more useful error - // message to the user. Instead of telling them that a semicolon is expected after the - // following token, then instead get a useful message about an identifier being missing. - // The above list prevents: - // - // C //<-- here - // Console.WriteLine(); - // - // C //<-- here - // Console->WriteLine(); - // - // C - // A + B; // etc. - // - // C - // A ? B : D; - var resetPoint = this.GetResetPoint(); - try - { - var currentTokenKind = this.CurrentToken.Kind; - if (currentTokenKind == SyntaxKind.IdentifierToken && !parentType.IsMissing) + // Check for the common pattern of: + // + // C //<-- here + // Console.WriteLine(); + // + // Standard greedy parsing will assume that this should be parsed as a variable + // declaration: "C Console". We want to avoid that as it can confused parts of the + // system further up. So, if we see certain things following the identifier, then we can + // assume it's not the actual name. + // + // So, if we're after a newline and we see a name followed by the list below, then we + // assume that we're accidently consuming too far into the next statement. + // + // , , any binary operator (except =), . None of these characters + // are allowed in a normal variable declaration. This also provides a more useful error + // message to the user. Instead of telling them that a semicolon is expected after the + // following token, then instead get a useful message about an identifier being missing. + // The above list prevents: + // + // C //<-- here + // Console.WriteLine(); + // + // C //<-- here + // Console->WriteLine(); + // + // C + // A + B; // etc. + // + // C + // A ? B : D; + var resetPoint = this.GetResetPoint(); + try { - var isAfterNewLine = parentType.GetLastToken().TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia); - if (isAfterNewLine) + var currentTokenKind = this.CurrentToken.Kind; + if (currentTokenKind == SyntaxKind.IdentifierToken && !parentType.IsMissing) { - int offset, width; - this.GetDiagnosticSpanForMissingToken(out offset, out width); + var isAfterNewLine = parentType.GetLastToken().TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia); + if (isAfterNewLine) + { + int offset, width; + this.GetDiagnosticSpanForMissingToken(out offset, out width); - this.EatToken(); - currentTokenKind = this.CurrentToken.Kind; + this.EatToken(); + currentTokenKind = this.CurrentToken.Kind; - var isNonEqualsBinaryToken = - currentTokenKind != SyntaxKind.EqualsToken && - SyntaxFacts.IsBinaryExpressionOperatorToken(currentTokenKind); + var isNonEqualsBinaryToken = + currentTokenKind != SyntaxKind.EqualsToken && + SyntaxFacts.IsBinaryExpressionOperatorToken(currentTokenKind); - if (currentTokenKind == SyntaxKind.DotToken || - currentTokenKind == SyntaxKind.MinusGreaterThanToken || - isNonEqualsBinaryToken) - { - var missingIdentifier = CreateMissingIdentifierToken(); - missingIdentifier = this.AddError(missingIdentifier, offset, width, ErrorCode.ERR_IdentifierExpected); + if (currentTokenKind == SyntaxKind.DotToken || + currentTokenKind == SyntaxKind.MinusGreaterThanToken || + isNonEqualsBinaryToken) + { + var missingIdentifier = CreateMissingIdentifierToken(); + missingIdentifier = this.AddError(missingIdentifier, offset, width, ErrorCode.ERR_IdentifierExpected); - return syntaxFactory.VariableDeclarator(missingIdentifier, null, null); + return syntaxFactory.VariableDeclarator(missingIdentifier, null, null); + } } } } - } - finally - { - this.Reset(ref resetPoint); - this.Release(ref resetPoint); - } + finally + { + this.Reset(ref resetPoint); + this.Release(ref resetPoint); + } } // NOTE: Diverges from Dev10. @@ -4943,7 +5207,7 @@ private void ParseEnumMemberDeclarations( { if (this.CurrentToken.Kind != SyntaxKind.CloseBraceToken) { - tryAgain: + tryAgain: if (this.IsPossibleEnumMemberDeclaration() || this.CurrentToken.Kind == SyntaxKind.CommaToken || this.CurrentToken.Kind == SyntaxKind.SemicolonToken) { @@ -6315,10 +6579,10 @@ private StatementSyntax ParsePossibleBadAwaitStatement(ref ResetPoint resetPoint // possible that it was only not legal because we were not in an async context. Debug.Assert(!IsInAsync); - + // Let's see if we're in case (4). Pretend that we're in an async method and see if parsing // a non-declaration statement would have succeeded. - + this.Reset(ref resetPointBeforeStatement); IsInAsync = true; result = ParseStatementNoDeclaration(allowAnyExpression: false); @@ -7374,7 +7638,7 @@ private void ParseForStatementExpressionList(ref SyntaxToken startToken, Separat { if (this.CurrentToken.Kind != SyntaxKind.CloseParenToken && this.CurrentToken.Kind != SyntaxKind.SemicolonToken) { - tryAgain: + tryAgain: if (this.IsPossibleExpression() || this.CurrentToken.Kind == SyntaxKind.CommaToken) { // first argument @@ -7434,7 +7698,7 @@ private ForEachStatementSyntax ParseForEachStatement() } else { - @foreach = this.EatToken(SyntaxKind.ForEachKeyword); + @foreach = this.EatToken(SyntaxKind.ForEachKeyword); } var openParen = this.EatToken(SyntaxKind.OpenParenToken); @@ -7653,13 +7917,37 @@ private SwitchSectionSyntax ParseSwitchSection() { expression = this.CreateMissingIdentifierName(); expression = this.AddError(expression, ErrorCode.ERR_ConstantExpected); + colon = this.EatToken(SyntaxKind.ColonToken); + label = syntaxFactory.CaseSwitchLabel(specifier, expression, colon); + } + else if (IsRecordsEnabled) + { + var node = ParseExpressionOrPattern(); + if (node is PatternSyntax) + { + SyntaxToken with = null; ; + ExpressionSyntax condition = null; + if (this.CurrentToken.ContextualKind == SyntaxKind.WhereKeyword) + { + with = this.EatContextualToken(SyntaxKind.WhereKeyword); + condition = ParseExpression(); + } + colon = this.EatToken(SyntaxKind.ColonToken); + label = syntaxFactory.CaseMatchLabel(specifier, (PatternSyntax)node, with, condition, colon); + } + else + { + colon = this.EatToken(SyntaxKind.ColonToken); + Debug.Assert(node is ExpressionSyntax); + label = syntaxFactory.CaseSwitchLabel(specifier, (ExpressionSyntax)node, colon); + } } else { expression = this.ParseExpression(); + colon = this.EatToken(SyntaxKind.ColonToken); + label = syntaxFactory.CaseSwitchLabel(specifier, expression, colon); } - colon = this.EatToken(SyntaxKind.ColonToken); - label = syntaxFactory.CaseSwitchLabel(specifier, expression, colon); } else { @@ -8344,11 +8632,32 @@ private ExpressionSyntax ParseSubExpression(uint precedence, bool allowDeclarati opToken = SyntaxFactory.Token(opToken.GetLeadingTrivia(), kind, opToken2.GetTrailingTrivia()); } - if (opKind == SyntaxKind.IsExpression || opKind == SyntaxKind.AsExpression) + if (opKind == SyntaxKind.AsExpression) { leftOperand = syntaxFactory.BinaryExpression(opKind, leftOperand, opToken, this.ParseTypeCore(parentIsParameter: false, isOrAs: true, expectSizes: false, isArrayCreation: false)); } + else if (opKind == SyntaxKind.IsExpression) + { + if (IsRecordsEnabled) + { + var node = this.ParseTypeOrPattern(); + if (node is PatternSyntax) + { + leftOperand = syntaxFactory.MatchExpression(leftOperand, opToken, (PatternSyntax)node); + } + else + { + Debug.Assert(node is TypeSyntax); + leftOperand = syntaxFactory.BinaryExpression(opKind, leftOperand, opToken, (TypeSyntax)node); + } + } + else + { + leftOperand = syntaxFactory.BinaryExpression(opKind, leftOperand, opToken, + this.ParseTypeCore(parentIsParameter: false, isOrAs: true, expectSizes: false, isArrayCreation: false)); + } + } else { leftOperand = syntaxFactory.BinaryExpression(opKind, leftOperand, opToken, @@ -8381,6 +8690,358 @@ private ExpressionSyntax ParseSubExpression(uint precedence, bool allowDeclarati return leftOperand; } + // This method is used when we always want a pattern as a result. + // For instance, it is used in parsing recursivepattern and propertypattern. + // SubPatterns in these (recursivepattern, propertypattern) must be a type of Pattern. + private PatternSyntax ParsePattern() + { + var node = this.ParseExpressionOrPattern(); + if (node is PatternSyntax) + { + return (PatternSyntax)node; + } + Debug.Assert(node is ExpressionSyntax); + return syntaxFactory.ConstantPattern((ExpressionSyntax)node); + } + + // Priority is the TypeSyntax. It might return TypeSyntax which might be a constant pattern such as enum 'Days.Sunday' + // We handle such cases in the binder of is operator. + // It is used for parsing patterns in the is operators. + private CSharpSyntaxNode ParseTypeOrPattern() + { + var tk = this.CurrentToken.Kind; + CSharpSyntaxNode node = null; + + if (tk == SyntaxKind.AsteriskToken) + { + var asteriskToken = this.EatToken(); + return syntaxFactory.WildCardPattern(asteriskToken); + } + + // If it is a nameof, skip the 'if' and parse as a constant pattern. + if (SyntaxFacts.IsPredefinedType(tk) || + (tk == SyntaxKind.IdentifierToken && this.CurrentToken.ContextualKind != SyntaxKind.NameOfKeyword)) + { + var resetPoint = this.GetResetPoint(); + TypeSyntax type = this.ParseTypeCore(parentIsParameter: false, isOrAs: true, expectSizes: false, isArrayCreation: false); + + tk = this.CurrentToken.Kind; + if (!type.IsMissing) + { + // X.Y.Z ( ... ) : RecursivePattern + if (tk == SyntaxKind.OpenParenToken) + { + node = syntaxFactory.RecursivePattern(type, this.ParseSubRecursivePatternList()); + } + // X.Y.Z { ... } : PropertyPattern + else if (tk == SyntaxKind.OpenBraceToken) + { + node = syntaxFactory.PropertyPattern(type, this.ParseSubPropertyPatternList()); + } + // X.Y.Z id + else if (this.IsTrueIdentifier()) + { + var identifier = ParseIdentifierToken(); + node = syntaxFactory.DeclarationPattern(type, identifier); + } + } + if (node == null) + { + // We allow these expressions: "operand is a+b" (if both a and b are constant) + if (tk == SyntaxKind.PlusToken || tk == SyntaxKind.MinusToken) + { + this.Reset(ref resetPoint); + node = syntaxFactory.ConstantPattern(this.ParseExpression()); + } + // it is a typical is operator! + else + { + node = type; + } + } + this.Release(ref resetPoint); + } + else + { + // it still might be a pattern such as (operand is 3) or (operand is -3) + node = syntaxFactory.ConstantPattern(this.ParseExpression()); + } + return node; + } + + // Priority is the ExpressionSyntax. It might return ExpressionSyntax which might be a constant pattern such as 'case 3:' + // All constant expressions are converted to the constant pattern in the switch binder if it is a match statement. + // It is used for parsing patterns in the switch cases. It never returns constant pattern! + private CSharpSyntaxNode ParseExpressionOrPattern() + { + var tk = this.CurrentToken.Kind; + CSharpSyntaxNode node = null; + + if (tk == SyntaxKind.AsteriskToken) + { + var asteriskToken = this.EatToken(); + return syntaxFactory.WildCardPattern(asteriskToken); + } + + // If it is a nameof, skip the 'if' and parse as an expression. + if ((SyntaxFacts.IsPredefinedType(tk) || tk == SyntaxKind.IdentifierToken) && + this.CurrentToken.ContextualKind != SyntaxKind.NameOfKeyword) + { + var resetPoint = this.GetResetPoint(); + TypeSyntax type = this.ParseTypeCore(parentIsParameter: false, isOrAs: true, expectSizes: false, isArrayCreation: false); + + + tk = this.CurrentToken.Kind; + if (!type.IsMissing) + { + // X.Y.Z ( ... ) : RecursivePattern + if (tk == SyntaxKind.OpenParenToken) + { + node = syntaxFactory.RecursivePattern(type, this.ParseSubRecursivePatternList()); + } + // X.Y.Z { ... } : PropertyPattern + else if (tk == SyntaxKind.OpenBraceToken) + { + node = syntaxFactory.PropertyPattern(type, this.ParseSubPropertyPatternList()); + } + // X.Y.Z id + else if (this.IsTrueIdentifier()) + { + var identifier = ParseIdentifierToken(); + node = syntaxFactory.DeclarationPattern(type, identifier); + } + } + if (node == null) + { + // it is an expression for typical switch case. + // This can be transformed to the constant pattern in the SwitchBinder if there is a CaseMatchLabel in the sections. + this.Reset(ref resetPoint); + node = this.ParseExpression(); + } + this.Release(ref resetPoint); + } + else + { + node = this.ParseExpression(); + } + return node; + } + + private SubPropertyPatternListSyntax ParseSubPropertyPatternList() + { + var openBrace = this.EatToken(SyntaxKind.OpenBraceToken); + + var subPatterns = this.pool.AllocateSeparated(); + try + { + this.ParseSubPropertyPatternList(ref openBrace, subPatterns); + + var closeBrace = this.EatToken(SyntaxKind.CloseBraceToken); + return syntaxFactory.SubPropertyPatternList( + openBrace, + subPatterns, + closeBrace); + } + finally + { + this.pool.Free(subPatterns); + } + } + + private void ParseSubPropertyPatternList(ref SyntaxToken startToken, SeparatedSyntaxListBuilder list) + { + if (this.CurrentToken.Kind != SyntaxKind.CloseBraceToken) + { + tryAgain: + if (this.IsPossibleSubPropertyPattern() || this.CurrentToken.Kind == SyntaxKind.CommaToken) + { + // first argument + list.Add(this.ParseSubPropertyPattern()); + + // additional arguments + while (true) + { + if (this.CurrentToken.Kind == SyntaxKind.CloseBraceToken) + { + break; + } + else if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleSubPropertyPattern()) + { + list.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + + // check for exit case after legal trailing comma + if (this.CurrentToken.Kind == SyntaxKind.CloseBraceToken) + { + break; + } + + list.Add(this.ParseSubPropertyPattern()); + continue; + } + else if (this.SkipBadSubPatternListTokens(ref startToken, list, SyntaxKind.CommaToken, SyntaxKind.CloseBraceToken) == PostSkipAction.Abort) + { + break; + } + } + } + else if (this.SkipBadSubPatternListTokens(ref startToken, list, SyntaxKind.IdentifierToken, SyntaxKind.CloseBraceToken) == PostSkipAction.Continue) + { + goto tryAgain; + } + } + } + + private SubPropertyPatternSyntax ParseSubPropertyPattern() + { + var name = this.ParseIdentifierName(); + var operandToken = this.EatToken(SyntaxKind.IsKeyword); + + PatternSyntax pattern = this.CurrentToken.Kind == SyntaxKind.CommaToken ? + this.AddError(syntaxFactory.ConstantPattern(this.CreateMissingIdentifierName()), ErrorCode.ERR_MissingArgument) : + ParsePattern(); + + return syntaxFactory.SubPropertyPattern(name, operandToken, pattern); + } + + private bool IsPossibleSubPropertyPattern() + { + return (this.CurrentToken.Kind == SyntaxKind.IdentifierToken) && (this.PeekToken(1).Kind == SyntaxKind.IsKeyword); + } + + private SubRecursivePatternListSyntax ParseSubRecursivePatternList() + { + if (this.IsIncrementalAndFactoryContextMatches && this.CurrentNodeKind == SyntaxKind.SubRecursivePatternList) + { + return (SubRecursivePatternListSyntax)this.EatNode(); + } + + SyntaxToken openToken, closeToken; + SeparatedSyntaxList subPatterns; + ParseSubRecursivePatternList(out openToken, out subPatterns, out closeToken, SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken); + + return syntaxFactory.SubRecursivePatternList(openToken, subPatterns, closeToken); + } + + private void ParseSubRecursivePatternList( + out SyntaxToken openToken, + out SeparatedSyntaxList subPatterns, + out SyntaxToken closeToken, + SyntaxKind openKind, + SyntaxKind closeKind) + { + var open = this.EatToken(openKind); + var saveTerm = this.termState; + this.termState |= TerminatorState.IsEndOfArgumentList; + + SeparatedSyntaxListBuilder list = default(SeparatedSyntaxListBuilder); + try + { + if (this.CurrentToken.Kind != closeKind && this.CurrentToken.Kind != SyntaxKind.SemicolonToken) + { + tryAgain: + if (list.IsNull) + { + list = this.pool.AllocateSeparated(); + } + + if (this.IsPossibleArgumentExpression() || this.CurrentToken.Kind == SyntaxKind.CommaToken) + { + // first argument + list.Add(this.ParseSubRecursivePattern()); + + // additional arguments + while (true) + { + if (this.CurrentToken.Kind == closeKind || this.CurrentToken.Kind == SyntaxKind.SemicolonToken) + { + break; + } + else if (this.CurrentToken.Kind == SyntaxKind.CommaToken || this.IsPossibleArgumentExpression()) + { + list.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + list.Add(this.ParseSubRecursivePattern()); + continue; + } + else if (this.SkipBadSubPatternListTokens(ref open, list, SyntaxKind.CommaToken, closeKind) == PostSkipAction.Abort) + { + break; + } + } + } + else if (this.SkipBadSubPatternListTokens(ref open, list, SyntaxKind.IdentifierToken, closeKind) == PostSkipAction.Continue) + { + goto tryAgain; + } + } + + this.termState = saveTerm; + + openToken = open; + closeToken = this.EatToken(closeKind); + subPatterns = list.ToList(); + } + finally + { + if (!list.IsNull) + { + this.pool.Free(list); + } + } + } + + private SubRecursivePatternSyntax ParseSubRecursivePattern() + { + NameColonSyntax nameColon = null; + if (this.CurrentToken.Kind == SyntaxKind.IdentifierToken && this.PeekToken(1).Kind == SyntaxKind.ColonToken) + { + var name = this.ParseIdentifierName(); + var colon = this.EatToken(SyntaxKind.ColonToken); + nameColon = syntaxFactory.NameColon(name, colon); + } + + PatternSyntax pattern = this.CurrentToken.Kind == SyntaxKind.CommaToken ? + this.AddError(syntaxFactory.ConstantPattern(this.CreateMissingIdentifierName()), ErrorCode.ERR_MissingArgument) : + ParsePattern(); + + return syntaxFactory.SubRecursivePattern(nameColon, pattern); + } + + private PostSkipAction SkipBadSubPatternListTokens(ref SyntaxToken open, SeparatedSyntaxListBuilder list, SyntaxKind expected, SyntaxKind closeKind) where TNode : CSharpSyntaxNode + { + return this.SkipBadSeparatedListTokensWithExpectedKind(ref open, list, + p => p.CurrentToken.Kind != SyntaxKind.CommaToken && !p.IsPossiblePattern(), + p => p.CurrentToken.Kind == closeKind || p.CurrentToken.Kind == SyntaxKind.SemicolonToken || p.IsTerminator(), + expected); + } + + // It should not be accurate. This method is just used in the SkipBadSubPatternListTokens. + // It is very general way of checking whether there is a possible pattern. + private bool IsPossiblePattern() + { + var tk = this.CurrentToken.Kind; + switch (tk) + { + case SyntaxKind.AsteriskToken: + case SyntaxKind.StringLiteralToken: + case SyntaxKind.CharacterLiteralToken: + case SyntaxKind.NumericLiteralToken: + case SyntaxKind.NullKeyword: + case SyntaxKind.TrueKeyword: + case SyntaxKind.FalseKeyword: + case SyntaxKind.ArgListKeyword: + return true; + case SyntaxKind.IdentifierToken: + var next = this.PeekToken(1).Kind; + if (next == SyntaxKind.DotToken || next == SyntaxKind.OpenBraceToken || + next == SyntaxKind.OpenParenToken) + { + return true; + } + break; + } + return IsPossibleDeclarationExpression(false); + } + private ExpressionSyntax ParseTerm(uint precedence, bool contextRequiresVariable) { ExpressionSyntax expr = null; diff --git a/Src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs b/Src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs index 7cdc5388..d3e78523 100644 --- a/Src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs +++ b/Src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs @@ -33,6 +33,23 @@ internal abstract partial class SyntaxParser : IDisposable private BlendedNode[] blendedTokens; + private bool? isRecordsEnabled; + + // Iterating through preprocessorsymbols is expensive whenever a pattern matching related feature is trying to be parsed. + // We can provide a property and flag to prevent re-iterating everytime. + // I did not want to introduce a property and flag to this class. + internal bool IsRecordsEnabled + { + get + { + if (!isRecordsEnabled.HasValue) + { + isRecordsEnabled = this.Options.IsRecordsEnabled(); + } + return isRecordsEnabled.Value; + } + } + protected SyntaxParser( Lexer lexer, LexerMode mode, diff --git a/Src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs b/Src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs index 6c903712..a84cdb72 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/EnumConversions.cs @@ -13,6 +13,7 @@ internal static TypeKind ToTypeKind(this DeclarationKind kind) case DeclarationKind.Class: case DeclarationKind.Script: case DeclarationKind.ImplicitClass: + case DeclarationKind.Record: return TypeKind.Class; case DeclarationKind.Submission: diff --git a/Src/Compilers/CSharp/Portable/Symbols/FieldInitializer.cs b/Src/Compilers/CSharp/Portable/Symbols/FieldInitializer.cs index a9ff8dd6..cf92c724 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/FieldInitializer.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/FieldInitializer.cs @@ -32,9 +32,9 @@ internal struct FieldInitializerInfo { public readonly FieldInitializer Initializer; public readonly Binder Binder; - public readonly EqualsValueClauseSyntax EqualsValue; + public readonly CSharpSyntaxNode EqualsValue; - public FieldInitializerInfo(FieldInitializer initializer, Binder binder, EqualsValueClauseSyntax equalsValue) + public FieldInitializerInfo(FieldInitializer initializer, Binder binder, CSharpSyntaxNode equalsValue) { Initializer = initializer; Binder = binder; diff --git a/Src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs b/Src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs index b025aabd..251003a9 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/LocalDeclarationKind.cs @@ -49,5 +49,10 @@ internal enum LocalDeclarationKind : byte /// User defined local variable declared by . /// ForEachIterationVariable, + + /// + /// User defined local variable declared by . + /// + Pattern } } \ No newline at end of file diff --git a/Src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs index 0d942774..63c4928c 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/NamespaceOrTypeSymbol.cs @@ -217,6 +217,7 @@ internal SourceNamedTypeSymbol GetSourceTypeMember( { Debug.Assert( kind == SyntaxKind.ClassDeclaration || + kind == SyntaxKind.RecordDeclaration || kind == SyntaxKind.StructDeclaration || kind == SyntaxKind.InterfaceDeclaration || kind == SyntaxKind.EnumDeclaration || diff --git a/Src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs index 20fa1d37..10b1d2e7 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/PropertySymbol.cs @@ -471,5 +471,16 @@ public override int GetHashCode() } #endregion Equality + + // It is needed because SynthesizedBackingFieldSymbol needs to call its property's HasPointerType method. + // Previously, SynthesizedBackingFieldSymbol's property is a type of SourcePropertySymbol. + // Now, it is a type of PropertySymbol because it also needs to support SynthesizedPropertySymbol + internal virtual bool HasPointerType + { + get + { + return this.Type.IsPointerType(); + } + } } } diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs index d436a383..32ac472e 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/ModifierUtils.cs @@ -111,6 +111,8 @@ private static string ConvertSingleModifierToSyntaxText(DeclarationModifiers mod return SyntaxFacts.GetText(SyntaxKind.OverrideKeyword); case DeclarationModifiers.Async: return SyntaxFacts.GetText(SyntaxKind.AsyncKeyword); + case DeclarationModifiers.Record: + return SyntaxFacts.GetText(SyntaxKind.RecordKeyword); default: throw ExceptionUtilities.UnexpectedValue(modifier); } @@ -197,6 +199,9 @@ public static DeclarationModifiers ToDeclarationModifiers(this SyntaxTokenList m one = DeclarationModifiers.Volatile; break; + case SyntaxKind.RecordKeyword: + one = DeclarationModifiers.Record; + break; case SyntaxKind.ThisKeyword: case SyntaxKind.RefKeyword: case SyntaxKind.OutKeyword: diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs index 3d2ad983..7f8c8dc5 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/ParameterHelpers.cs @@ -97,6 +97,91 @@ public static ImmutableArray MakeParameters( return parameters; } + public static ImmutableArray MakeRecordParameters( + Binder binder, + Symbol owner, + RecordParameterListSyntax syntax, + bool allowRefOrOut, + out SyntaxToken arglistToken, + DiagnosticBag diagnostics) + { + arglistToken = default(SyntaxToken); + + int parameterIndex = 0; + int firstDefault = -1; + + var builder = ArrayBuilder.GetInstance(); + ImmutableArray parameters; + + foreach (var parameterSyntax in syntax.Parameters) + { + + SyntaxToken outKeyword; + SyntaxToken refKeyword; + SyntaxToken paramsKeyword; + SyntaxToken thisKeyword; + var refKind = GetModifiers(parameterSyntax.Modifiers, out outKeyword, out refKeyword, out paramsKeyword, out thisKeyword); + + if (parameterSyntax.IsArgList) + { + arglistToken = parameterSyntax.Identifier; + // The native compiler produces "Expected type" here, in the parser. Roslyn produces + // the somewhat more informative "arglist not valid" error. + if (paramsKeyword.CSharpKind() != SyntaxKind.None || outKeyword.CSharpKind() != SyntaxKind.None || + refKeyword.CSharpKind() != SyntaxKind.None || thisKeyword.CSharpKind() != SyntaxKind.None) + { + // CS1669: __arglist is not valid in this context + diagnostics.Add(ErrorCode.ERR_IllegalVarArgs, arglistToken.GetLocation()); + } + continue; + } + + if (parameterSyntax.Default != null && firstDefault == -1) + { + firstDefault = parameterIndex; + } + + Debug.Assert(parameterSyntax.Type != null); + var parameterType = binder.BindType(parameterSyntax.Type, diagnostics); + + if (!allowRefOrOut && (refKind != RefKind.None)) + { + var outOrRefKeyword = (outKeyword.CSharpKind() != SyntaxKind.None) ? outKeyword : refKeyword; + Debug.Assert(outOrRefKeyword.CSharpKind() != SyntaxKind.None); + + // error CS0631: ref and out are not valid in this context + diagnostics.Add(ErrorCode.ERR_IllegalRefParam, outOrRefKeyword.GetLocation()); + } + + var parameter = SourceParameterSymbol.Create( + binder, + owner, + parameterType, + parameterSyntax, + refKind, + parameterSyntax.Identifier, + parameterIndex, + (paramsKeyword.CSharpKind() != SyntaxKind.None), + parameterIndex == 0 && thisKeyword.CSharpKind() != SyntaxKind.None, + diagnostics); + + ReportRecordParameterErrors(owner, parameterSyntax, parameter, firstDefault, diagnostics); + + builder.Add(parameter); + ++parameterIndex; + } + + parameters = builder.ToImmutableAndFree(); + + var methodOwner = owner as MethodSymbol; + var typeParameters = (object)methodOwner != null ? + methodOwner.TypeParameters : + default(ImmutableArray); + + binder.ValidateParameterNameConflicts(typeParameters, parameters, diagnostics); + return parameters; + } + private static void ReportParameterErrors( Symbol owner, ParameterSyntax parameterSyntax, @@ -145,6 +230,54 @@ private static void ReportParameterErrors( } } + private static void ReportRecordParameterErrors( + Symbol owner, + RecordParameterSyntax parameterSyntax, + SourceParameterSymbol parameter, + int firstDefault, + DiagnosticBag diagnostics) + { + TypeSymbol parameterType = parameter.Type; + int parameterIndex = parameter.Ordinal; + bool isDefault = parameterSyntax.Default != null; + SyntaxToken thisKeyword = parameterSyntax.Modifiers.FirstOrDefault(SyntaxKind.ThisKeyword); + + if (thisKeyword.CSharpKind() == SyntaxKind.ThisKeyword && parameterIndex != 0) + { + // Report CS1100 on "this". Note that is a change from Dev10 + // which reports the error on the type following "this". + + // error CS1100: Method '{0}' has a parameter modifier 'this' which is not on the first parameter + diagnostics.Add(ErrorCode.ERR_BadThisParam, thisKeyword.GetLocation(), owner.Name); + } + else if (parameter.IsParams && owner.IsOperator()) + { + // error CS1670: params is not valid in this context + diagnostics.Add(ErrorCode.ERR_IllegalParams, parameterSyntax.Modifiers.First(t => t.CSharpKind() == SyntaxKind.ParamsKeyword).GetLocation()); + } + else if (parameter.IsParams && !parameterType.IsSingleDimensionalArray()) + { + // error CS0225: The params parameter must be a single dimensional array + diagnostics.Add(ErrorCode.ERR_ParamsMustBeArray, parameterSyntax.Modifiers.First(t => t.CSharpKind() == SyntaxKind.ParamsKeyword).GetLocation()); + } + else if (parameter.Type.IsStatic && !parameter.ContainingSymbol.ContainingType.IsInterfaceType()) + { + // error CS0721: '{0}': static types cannot be used as parameters + diagnostics.Add(ErrorCode.ERR_ParameterIsStaticClass, owner.Locations[0], parameter.Type); + } + else if (firstDefault != -1 && parameterIndex > firstDefault && !isDefault && !parameter.IsParams) + { + // error CS1737: Optional parameters must appear after all required parameters + Location loc = parameterSyntax.Identifier.GetNextToken(includeZeroWidth: true).GetLocation(); //could be missing + diagnostics.Add(ErrorCode.ERR_DefaultValueBeforeRequiredValue, loc); + } + else if (parameter.RefKind != RefKind.None && parameter.Type.IsRestrictedType()) + { + // CS1601: Cannot make reference to variable of type 'System.TypedReference' + diagnostics.Add(ErrorCode.ERR_MethodArgCantBeRefAny, parameterSyntax.Location, parameter.Type); + } + } + internal static bool ReportDefaultParameterErrors( Binder binder, Symbol owner, diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs index 06954a54..3c276afb 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceConstructorSymbol.cs @@ -14,10 +14,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal sealed class SourceConstructorSymbol : SourceMethodSymbol { - private ImmutableArray lazyParameters; - private TypeSymbol lazyReturnType; - private bool lazyIsVararg; - public static SourceConstructorSymbol CreateConstructorSymbol( SourceMemberContainerTypeSymbol containingType, ConstructorDeclarationSyntax syntax, @@ -35,6 +31,18 @@ public static SourceConstructorSymbol CreatePrimaryConstructorSymbol( return new SourceConstructorSymbol(containingType, syntax.GetLocation(), syntax, diagnostics); } + private ImmutableArray lazyParameters; + private TypeSymbol lazyReturnType; + private bool lazyIsVararg; + + public static SourceConstructorSymbol CreatePrimaryConstructorSymbol( + SourceMemberContainerTypeSymbol containingType, + RecordParameterListSyntax syntax, + DiagnosticBag diagnostics) + { + return new SourceConstructorSymbol(containingType, syntax.GetLocation(), syntax, diagnostics); + } + private SourceConstructorSymbol( SourceMemberContainerTypeSymbol containingType, Location location, @@ -47,6 +55,31 @@ private SourceConstructorSymbol( this.CheckModifiers(MethodKind.Constructor, location, diagnostics); } + private SourceConstructorSymbol( + SourceMemberContainerTypeSymbol containingType, + Location location, + RecordParameterListSyntax syntax, + DiagnosticBag diagnostics) : + base(containingType, syntax.GetReference(), GetPrimaryConstructorBlockSyntaxReferenceOrNull(syntax), ImmutableArray.Create(location)) + { + var declarationModifiers = (containingType.IsAbstract ? DeclarationModifiers.Protected : DeclarationModifiers.Public) | DeclarationModifiers.PrimaryCtor; + this.flags = MakeFlags(MethodKind.Constructor, declarationModifiers, returnsVoid: true, isExtensionMethod: false); + this.CheckModifiers(MethodKind.Constructor, location, diagnostics); + } + + private static SyntaxReference GetPrimaryConstructorBlockSyntaxReferenceOrNull(CSharpSyntaxNode syntax) + { + foreach (var m in ((TypeDeclarationSyntax)syntax.Parent).Members) + { + if (m.Kind == SyntaxKind.PrimaryConstructorBody) + { + return ((PrimaryConstructorBodySyntax)m).Body.GetReference(); + } + } + + return null; + } + private SourceConstructorSymbol( SourceMemberContainerTypeSymbol containingType, Location location, @@ -80,42 +113,48 @@ private SourceConstructorSymbol( } } - private static SyntaxReference GetPrimaryConstructorBlockSyntaxReferenceOrNull(ParameterListSyntax syntax) - { - foreach (var m in ((TypeDeclarationSyntax)syntax.Parent).Members) - { - if (m.Kind == SyntaxKind.PrimaryConstructorBody) - { - return ((PrimaryConstructorBodySyntax)m).Body.GetReference(); - } - } - - return null; - } - protected override void MethodChecks(DiagnosticBag diagnostics) { var syntax = (CSharpSyntaxNode)syntaxReference.GetSyntax(); var binderFactory = this.DeclaringCompilation.GetBinderFactory(syntaxReference.SyntaxTree); - ParameterListSyntax parameterList; + + ParameterListSyntax parameterList = null; + RecordParameterListSyntax recordParameterList = null; + Binder bodyBinder = null; + SyntaxToken arglistToken; if (syntax.Kind == SyntaxKind.ParameterList) { // Primary constructor case parameterList = (ParameterListSyntax)syntax; + + // NOTE: if we asked for the binder for the body of the constructor, we'd risk a stack overflow because + // we might still be constructing the member list of the containing type. However, getting the binder + // for the parameters should be safe. + } + else if (syntax.Kind == SyntaxKind.RecordParameterList) + { + recordParameterList = (RecordParameterListSyntax)syntax; } else { parameterList = ((ConstructorDeclarationSyntax)syntax).ParameterList; } - // NOTE: if we asked for the binder for the body of the constructor, we'd risk a stack overflow because - // we might still be constructing the member list of the containing type. However, getting the binder - // for the parameters should be safe. - var bodyBinder = binderFactory.GetBinder(parameterList).WithContainingMemberOrLambda(this); - SyntaxToken arglistToken; + if (parameterList != null) + { + bodyBinder = binderFactory.GetBinder(parameterList).WithContainingMemberOrLambda(this); this.lazyParameters = ParameterHelpers.MakeParameters(bodyBinder, this, parameterList, true, out arglistToken, diagnostics); + } + else + { + bodyBinder = binderFactory.GetBinder(recordParameterList).WithContainingMemberOrLambda(this); + this.lazyParameters = ParameterHelpers.MakeRecordParameters(bodyBinder, this, recordParameterList, true, out arglistToken, diagnostics); + } + + + this.lazyIsVararg = (arglistToken.CSharpKind() == SyntaxKind.ArgListKeyword); this.lazyReturnType = bodyBinder.GetSpecialType(SpecialType.System_Void, diagnostics, syntax); @@ -179,6 +218,10 @@ internal override int ParameterCount // Primary constructor return ((ParameterListSyntax)syntax).ParameterCount; } + else if (syntax.Kind == SyntaxKind.RecordParameterList) + { + return ((RecordParameterListSyntax)syntax).ParameterCount; + } return ((ConstructorDeclarationSyntax)syntax).ParameterList.ParameterCount; } diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs index cde9fc10..56f58288 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceLocalSymbol.cs @@ -244,7 +244,8 @@ internal void SetTypeSymbol(TypeSymbol newType) Debug.Assert((object)originalType == null || originalType.IsErrorType() && newType.IsErrorType() || - originalType == newType); + originalType == newType || + declarationKind == LocalDeclarationKind.Pattern); if ((object)originalType == null) { @@ -286,6 +287,9 @@ public override ImmutableArray DeclaringSyntaxReferences node = identifierToken.Parent.FirstAncestorOrSelf(); break; + case LocalDeclarationKind.Pattern: + node = identifierToken.Parent.FirstAncestorOrSelf(); + break; default: throw ExceptionUtilities.UnexpectedValue(declarationKind); } diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 4e71f4aa..89ca40e4 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -53,13 +53,13 @@ static SourceMemberContainerTypeSymbol() // y = IsManagedType. 2 bits. // d = FieldDefinitionsNoted. 1 bit private const int SpecialTypeMask = 0x3F; - private const int DeclarationModifiersMask = 0x1FFFFF; + private const int DeclarationModifiersMask = 0x1FFFFFF; private const int IsManagedTypeMask = 0x3; private const int SpecialTypeOffset = 0; private const int DeclarationModifiersOffset = 6; - private const int IsManagedTypeOffset = 26; - private const int FieldDefinitionsNotedOffset = 28; + private const int IsManagedTypeOffset = 28; + private const int FieldDefinitionsNotedOffset = 30; private int flags; @@ -198,7 +198,7 @@ private DeclarationModifiers MakeModifiers(TypeKind typeKind, DiagnosticBag diag case TypeKind.Class: case TypeKind.Submission: // static, sealed, and abstract allowed if a class - allowedModifiers |= DeclarationModifiers.Static | DeclarationModifiers.Sealed | DeclarationModifiers.Abstract | DeclarationModifiers.Unsafe; + allowedModifiers |= DeclarationModifiers.Static | DeclarationModifiers.Sealed | DeclarationModifiers.Abstract | DeclarationModifiers.Unsafe | DeclarationModifiers.Record; break; case TypeKind.Struct: case TypeKind.Interface: @@ -641,6 +641,14 @@ public override bool IsSealed } } + internal override bool IsRecord + { + get + { + return (this.DeclarationModifiers & DeclarationModifiers.Record) != 0; + } + } + public override bool IsAbstract { get @@ -2188,6 +2196,12 @@ private void AddDeclaredNontypeMembers(MembersAndInitializersBuilder builder, Di AddNonTypeMembers(builder, structDecl, structDecl.ParameterList, structDecl.Members, diagnostics); break; + case SyntaxKind.RecordDeclaration: + var recordDecl = (RecordDeclarationSyntax)syntax; + AddCompilerGeneratedMembersForRecord(builder, recordDecl, recordDecl.ParameterList, diagnostics); + AddNonTypeMembers(builder, recordDecl, null, recordDecl.Members, diagnostics); + break; + default: throw ExceptionUtilities.UnexpectedValue(syntax.CSharpKind()); } @@ -2740,8 +2754,8 @@ private void AddNonTypeMembers( SyntaxList members, DiagnosticBag diagnostics) { - bool havePrimaryCtor = false; + bool havePrimaryCtor = result.PrimaryCtor != null; // We might add the primaryCtor for the records beforehand. if (primaryCtorParameterList != null) { havePrimaryCtor = true; @@ -3051,6 +3065,89 @@ private void AddNonTypeMembers( AddInitializers(ref result.StaticInitializers, typeDeclaration, staticInitializers); } + private void AddCompilerGeneratedMembersForRecord(MembersAndInitializersBuilder result, RecordDeclarationSyntax recordDeclaration, RecordParameterListSyntax primaryCtorRecordParameterList, DiagnosticBag diagnostics) + { + ArrayBuilder instanceInitializers = null; + + var constructor = SourceConstructorSymbol.CreatePrimaryConstructorSymbol(this, primaryCtorRecordParameterList, diagnostics); + result.NonTypeNonIndexerMembers.Add(constructor); + + if ((object)result.PrimaryCtor == null) + { + result.PrimaryCtor = constructor; + } + else + { + diagnostics.Add(ErrorCode.ERR_SeveralPartialsDeclarePrimaryCtor, new SourceLocation(primaryCtorRecordParameterList)); + } + + var binder = this.GetBinder(primaryCtorRecordParameterList); + int i = 0; + var isParameterTypes = ArrayBuilder.GetInstance(); + // We need to store which property corresponds to which parameter. + // We use this information during creating the op_Is + var propertiesForParameters = ArrayBuilder.GetInstance(); + var isParameterNames = ArrayBuilder.GetInstance(); + + foreach (var param in primaryCtorRecordParameterList.Parameters) + { + var type = binder.BindType(param.Type, diagnostics); + + string propertyName; + Location propertyNameLocation; + if (param.ColonName != null) + { + var colonNameIdentifier = param.ColonName.Name.Identifier; + propertyName = colonNameIdentifier.ValueText; + propertyNameLocation = colonNameIdentifier.GetLocation(); + } + else + { + propertyName = param.Identifier.ValueText; + propertyNameLocation = param.Identifier.GetLocation(); + } + + if (!this.MemberNames.Contains(propertyName)) + { + var property = new SynthesizedPropertySymbol(propertyName, propertyNameLocation, this, type); + result.NonTypeNonIndexerMembers.Add(property); + result.NonTypeNonIndexerMembers.Add(property.BackingField); + AddInitializer(ref instanceInitializers, property.BackingField, param); + } + + isParameterTypes.Add(type); + isParameterNames.Add(param.Identifier.ValueText); + propertiesForParameters.Add(propertyName); + } + + + + if (!this.MemberNames.Contains(WellKnownMemberNames.IsOperatorName)) + { + var boolType = binder.Compilation.GetSpecialType(SpecialType.System_Boolean); + var isOperator = new SynthesizedIsOperator(this, isParameterTypes.ToImmutableAndFree(), isParameterNames.ToImmutableAndFree(), propertiesForParameters.ToImmutableAndFree(), boolType); + result.NonTypeNonIndexerMembers.Add(isOperator); + } + + if (!this.MemberNames.Contains(WellKnownMemberNames.ObjectGetHashCode)) + { + var intType = binder.Compilation.GetSpecialType(SpecialType.System_Int32); + var getHashCodeMethod = new SynthesizedGetHashCodeMethod(this, intType); + result.NonTypeNonIndexerMembers.Add(getHashCodeMethod); + } + + if (!this.MemberNames.Contains(WellKnownMemberNames.ObjectEquals)) + { + var boolType = binder.Compilation.GetSpecialType(SpecialType.System_Boolean); + var objectType = binder.Compilation.GetSpecialType(SpecialType.System_Object); + + var objectEquals = new SynthesizedEqualsMethod(this, boolType, objectType); + result.NonTypeNonIndexerMembers.Add(objectEquals); + } + + AddInitializers(ref result.InstanceInitializers, recordDeclaration, instanceInitializers); + } + private static bool IsGlobalCodeAllowed(CSharpSyntaxNode parent) { var parentKind = parent.Kind; diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs index 7f086d9a..78b978f4 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs @@ -362,6 +362,9 @@ private void CheckMembersAgainstBaseType( { case SymbolKind.Method: var method = (MethodSymbol)member; + + if (method.IsImplicitlyDeclared) + break; if (MethodSymbol.CanOverrideOrHide(method.MethodKind) && !method.IsAccessor()) { if (member.IsOverride) @@ -397,6 +400,8 @@ private void CheckMembersAgainstBaseType( var getMethod = property.GetMethod; var setMethod = property.SetMethod; + if (property.IsImplicitlyDeclared) + break; // Handle the accessors here, instead of in the loop, so that we can ensure that // they're checked *after* the corresponding property. if (member.IsOverride) diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs index dd343003..89dcfcad 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbol.cs @@ -59,14 +59,14 @@ static SourceMethodSymbol() private const int MethodKindOffset = 0; private const int DeclarationModifiersOffset = 5; - private const int ReturnsVoidOffset = 26; - private const int IsExtensionMethodOffset = 27; - private const int IsMetadataVirtualIgnoringInterfaceChangesOffset = 28; - private const int IsMetadataVirtualOffset = 29; - private const int IsMetadataVirtualLockedOffset = 30; + private const int ReturnsVoidOffset = 27; + private const int IsExtensionMethodOffset = 28; + private const int IsMetadataVirtualIgnoringInterfaceChangesOffset = 29; + private const int IsMetadataVirtualOffset = 30; + private const int IsMetadataVirtualLockedOffset = 31; private const int MethodKindMask = 0x1F << MethodKindOffset; - private const int DeclarationModifiersMask = 0x1FFFFF << DeclarationModifiersOffset; + private const int DeclarationModifiersMask = 0x1FFFFFF << DeclarationModifiersOffset; private const int ReturnsVoidMask = 0x1 << ReturnsVoidOffset; private const int IsExtensionMethodMask = 0x1 << IsExtensionMethodOffset; private const int IsMetadataVirtualIgnoringInterfaceChangesMask = 0x1 << IsMetadataVirtualIgnoringInterfaceChangesOffset; diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs index 3987d881..67054311 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamedTypeSymbol.cs @@ -88,7 +88,8 @@ internal SourceNamedTypeSymbol(NamespaceOrTypeSymbol containingSymbol, MergedTyp declaration.Kind == DeclarationKind.Interface || declaration.Kind == DeclarationKind.Enum || declaration.Kind == DeclarationKind.Delegate || - declaration.Kind == DeclarationKind.Class); + declaration.Kind == DeclarationKind.Class || + declaration.Kind == DeclarationKind.Record); if (containingSymbol.Kind == SymbolKind.NamedType) { @@ -108,6 +109,7 @@ private static SyntaxToken GetName(CSharpSyntaxNode node) case SyntaxKind.DelegateDeclaration: return ((DelegateDeclarationSyntax)node).Identifier; case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.StructDeclaration: return ((BaseTypeDeclarationSyntax)node).Identifier; @@ -146,6 +148,7 @@ private ImmutableArray MakeTypeParameters(DiagnosticBag dia switch (typeDecl.Kind) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: tpl = ((TypeDeclarationSyntax)typeDecl).TypeParameterList; @@ -313,6 +316,7 @@ private static SyntaxList GetConstraintClau switch (node.Kind) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: return ((TypeDeclarationSyntax)node).ConstraintClauses; diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs index b6e32f2b..570eca91 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceNamespaceSymbol.cs @@ -399,6 +399,7 @@ private NamespaceOrTypeSymbol BuildSymbol(MergedNamespaceOrTypeDeclaration decla case DeclarationKind.Enum: case DeclarationKind.Delegate: case DeclarationKind.Class: + case DeclarationKind.Record: return new SourceNamedTypeSymbol(this, (MergedTypeDeclaration)declaration, diagnostics); case DeclarationKind.Script: diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs index f2f860da..0b8db51c 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceParameterSymbol.cs @@ -30,7 +30,7 @@ public static SourceParameterSymbol Create( Binder context, Symbol owner, TypeSymbol parameterType, - ParameterSyntax syntax, + IParameterSyntax syntax, RefKind refKind, SyntaxToken identifier, int ordinal, diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 34a731c6..8ac570e4 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -396,7 +396,7 @@ public override TypeSymbol Type } } - internal bool HasPointerType + internal override bool HasPointerType { get { diff --git a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs index 734fd8ec..7a6e514e 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Source/SourceUserDefinedOperatorSymbolBase.cs @@ -175,6 +175,10 @@ protected override void MethodChecks(DiagnosticBag diagnostics) private void CheckValueParameters(DiagnosticBag diagnostics) { // SPEC: The parameters of an operator must be value parameters. + + if (name == WellKnownMemberNames.IsOperatorName) + return; + foreach (var p in this.Parameters) { if (p.RefKind != RefKind.None) diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedConstantHelperMethod.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedConstantHelperMethod.cs new file mode 100644 index 00000000..3ce72d99 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedConstantHelperMethod.cs @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Roslyn.Utilities; +using System.Collections.Immutable; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + /// + /// Represents an interactive code entry point that is inserted into the compilation if there is not an existing one. + /// + internal sealed class SynthesizedConstantHelperMethod : SynthesizedStaticMethodBaseForRecords + { + private readonly ImmutableArray parameters; + private readonly TypeSymbol returnType; + + internal SynthesizedConstantHelperMethod(string name, NamedTypeSymbol containingType, TypeSymbol param1Type, TypeSymbol param2Type, TypeSymbol returnType, TypeCompilationState compilationState, DiagnosticBag diagnostics) : + base(containingType, name) + { + Debug.Assert((object)containingType != null); + this.returnType = returnType; + this.parameters = ImmutableArray.Create(new SynthesizedParameterSymbol(this, param1Type, 0, RefKind.None, "o"), new SynthesizedParameterSymbol(this, param2Type, 1, RefKind.None, "v")); + + GenerateMethodBody(compilationState, diagnostics); + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + SyntheticBoundNodeFactory F = CreateBoundNodeFactory(compilationState, diagnostics); + + try + { + BoundBlock body; + var obj = F.Parameter(this.Parameters[0]); + var val = F.Parameter(this.Parameters[1]); + switch (this.Name) + { + // TODO: It is not complete. It needs other helpers for uint32, int64, etc. + // It can be optimized a lot! The current one can be served as a template. + case "<>Int32Helper": + { + body = F.Block( + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Byte)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Byte), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_SByte)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_SByte), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Int16)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Int16), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_UInt16)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_UInt16), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Int32)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Int32), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_UInt32)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_UInt32), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Int64)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Int64), obj), F.Cast(F.SpecialType(SpecialType.System_Int64), val)))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_UInt64)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_UInt64), obj), F.Cast(F.SpecialType(SpecialType.System_UInt64), val)))), + F.Return(F.Literal(false))); + break; + } + case "<>UInt32Helper": + { + body = F.Block( + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_UInt32)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_UInt32), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Int64)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Int64), obj), val))), + F.Return(F.Literal(false))); + break; + } + case "<>DoubleHelper": + { + body = F.Block( + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Double)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Double), obj), val))), + F.If( + F.Is(obj, F.SpecialType(SpecialType.System_Single)), + F.Return(F.ObjectEqual(F.Cast(F.SpecialType(SpecialType.System_Single), obj), val))), + F.Return(F.Literal(false))); + break; + } + default: + throw ExceptionUtilities.UnexpectedValue(this.Name); + } + F.CloseMethod(body); + } + catch (SyntheticBoundNodeFactory.MissingPredefinedMember ex) + { + diagnostics.Add(ex.Diagnostic); + F.CloseMethod(F.ThrowNull()); + } + } + + internal override bool HasSpecialName + { + get { return false; } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Private; } + } + + public override TypeSymbol ReturnType + { + get { return returnType; } + } + + public override MethodKind MethodKind + { + get { return MethodKind.Ordinary; } + } + + public override bool IsOverride + { + get { return false; } + } + + public override ImmutableArray Parameters + { + get { return parameters; } + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedEqualsMethod.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedEqualsMethod.cs new file mode 100644 index 00000000..303304f0 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedEqualsMethod.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + internal sealed class SynthesizedEqualsMethod : SynthesizedInstanceMethodBaseForRecords + { + private readonly TypeSymbol returnType; + private readonly ImmutableArray parameters; + + internal SynthesizedEqualsMethod(NamedTypeSymbol container, NamedTypeSymbol returnBoolType, NamedTypeSymbol paramObjectType) + : base(container, WellKnownMemberNames.ObjectEquals) + { + this.returnType = returnBoolType; + this.parameters = ImmutableArray.Create( + new SynthesizedParameterSymbol(this, paramObjectType, 0, RefKind.None, "value") + ); + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + SyntheticBoundNodeFactory F = this.CreateBoundNodeFactory(compilationState, diagnostics); + + // Method body: + // + // { + // $anonymous$ local = value as $anonymous$; + // return local != null + // && System.Collections.Generic.EqualityComparer.Default.Equals(this.backingFld_1, local.backingFld_1) + // ... + // && System.Collections.Generic.EqualityComparer.Default.Equals(this.backingFld_N, local.backingFld_N); + // } + + // Type and type expression + NamedTypeSymbol container = this.ContainingType; + + // local + BoundAssignmentOperator assignmentToTemp; + BoundLocal boundLocal = F.StoreToTemp(F.As(F.Parameter(this.parameters[0]), container), out assignmentToTemp); + + // Generate: statement <= 'local = value as $anonymous$' + BoundStatement assignment = F.ExpressionStatement(assignmentToTemp); + + // Generate expression for return statement + // retExpression <= 'local != null' + BoundExpression retExpression = F.Binary(BinaryOperatorKind.ObjectNotEqual, + F.SpecialType(SpecialType.System_Boolean), + F.Convert(F.SpecialType(SpecialType.System_Object), boundLocal), + F.Null(F.SpecialType(SpecialType.System_Object))); + + // prepare symbols + MethodSymbol equalityComparer_Equals = F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_EqualityComparer_T__Equals) as MethodSymbol; + MethodSymbol equalityComparer_get_Default = F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_EqualityComparer_T__get_Default) as MethodSymbol; + NamedTypeSymbol equalityComparerType = equalityComparer_Equals.ContainingType; + + foreach (var m in container.GetMembers()) + { + var property = m as SynthesizedPropertySymbol; + if (!ReferenceEquals(property,null)) + { + var backingField = property.BackingField; + + NamedTypeSymbol constructedEqualityComparer = equalityComparerType.Construct(backingField.Type); + + // Generate 'retExpression' = 'retExpression && System.Collections.Generic.EqualityComparer. + // Default.Equals(this.backingFld_index, local.backingFld_index)' + retExpression = F.LogicalAnd(retExpression, + F.Call(F.StaticCall(constructedEqualityComparer, + equalityComparer_get_Default.AsMember(constructedEqualityComparer)), + equalityComparer_Equals.AsMember(constructedEqualityComparer), + F.Field(F.This(), backingField), + F.Call(boundLocal, property.GetMethod))); + } + } + // Final return statement + BoundStatement retStatement = F.Return(retExpression); + + // Create a bound block + F.CloseMethod(F.Block(ImmutableArray.Create(boundLocal.LocalSymbol), assignment, retStatement)); + } + + public override ImmutableArray Parameters + { + get { return parameters; } + } + + internal override bool HasSpecialName + { + get { return false; } + } + + public override MethodKind MethodKind + { + get { return MethodKind.Ordinary; } + } + + public override TypeSymbol ReturnType + { + get { return this.returnType; } + } + + public override bool IsOverride + { + get { return true; } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Public; } + } + + internal sealed override bool IsMetadataVirtual(bool ignoreInterfaceImplementationChanges = false) + { + return true; + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedGetHashCodeMethod.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedGetHashCodeMethod.cs new file mode 100644 index 00000000..2fd00a88 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedGetHashCodeMethod.cs @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Symbols +{ + internal sealed class SynthesizedGetHashCodeMethod : SynthesizedInstanceMethodBaseForRecords + { + private readonly TypeSymbol returnType; + + internal SynthesizedGetHashCodeMethod(NamedTypeSymbol container, NamedTypeSymbol returnType) + : base(container, WellKnownMemberNames.ObjectGetHashCode) + { + this.returnType = returnType; + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + SyntheticBoundNodeFactory F = this.CreateBoundNodeFactory(compilationState, diagnostics); + + // Method body: + // + // HASH_FACTOR = 0xa5555529; + // INIT_HASH = (...((0 * HASH_FACTOR) + backingFld_1.Name.GetHashCode()) * HASH_FACTOR + // + backingFld_2.Name.GetHashCode()) * HASH_FACTOR + // + ... + // + backingFld_N.Name.GetHashCode() + // + // { + // return (...((INITIAL_HASH * HASH_FACTOR) + EqualityComparer.Default.GetHashCode(this.backingFld_1)) * HASH_FACTOR + // + EqualityComparer.Default.GetHashCode(this.backingFld_2)) * HASH_FACTOR + // ... + // + EqualityComparer.Default.GetHashCode(this.backingFld_N) + // } + + const int HASH_FACTOR = unchecked((int)0xa5555529); // (int)0xa5555529 + + // Type expression + var container = this.ContainingType; + + // INIT_HASH + int initHash = 0; + + foreach (var m in container.GetMembers()) + { + if (m is SynthesizedPropertySymbol) + { + var property = (SynthesizedPropertySymbol)m; + initHash = unchecked(initHash * HASH_FACTOR + property.BackingField.Name.GetHashCode()); + } + } + + // Generate expression for return statement + // retExpression <= 'INITIAL_HASH' + BoundExpression retExpression = F.Literal(initHash); + + // prepare symbols + MethodSymbol equalityComparer_GetHashCode = F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_EqualityComparer_T__GetHashCode) as MethodSymbol; + MethodSymbol equalityComparer_get_Default = F.Compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_EqualityComparer_T__get_Default) as MethodSymbol; + NamedTypeSymbol equalityComparerType = equalityComparer_GetHashCode.ContainingType; + + // bound HASH_FACTOR + BoundLiteral boundHashFactor = F.Literal(HASH_FACTOR); + + // Process fields + foreach (var m in container.GetMembers()) + { + var property = m as SynthesizedPropertySymbol; + if (!ReferenceEquals(property,null)) + { + var backingField = property.BackingField; + + NamedTypeSymbol constructedEqualityComparer = equalityComparerType.Construct(backingField.Type); + + // Generate 'retExpression' <= 'retExpression * HASH_FACTOR + retExpression = F.Binary(BinaryOperatorKind.IntMultiplication, F.SpecialType(SpecialType.System_Int32), retExpression, boundHashFactor); + + // Generate 'retExpression' <= 'retExpression + EqualityComparer.Default.GetHashCode(this.backingFld_index)' + retExpression = F.Binary(BinaryOperatorKind.IntAddition, + F.SpecialType(SpecialType.System_Int32), + retExpression, + F.Call( + F.StaticCall(constructedEqualityComparer, + equalityComparer_get_Default.AsMember(constructedEqualityComparer)), + equalityComparer_GetHashCode.AsMember(constructedEqualityComparer), + F.Field(F.This(), backingField))); + } + } + + // Create a bound block + F.CloseMethod(F.Block(F.Return(retExpression))); + } + + internal override bool HasSpecialName + { + get { return false; } + } + + public override MethodKind MethodKind + { + get { return MethodKind.Ordinary; } + } + + + public override TypeSymbol ReturnType + { + get { return this.returnType; } + } + + public override bool IsOverride + { + get { return true; } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Public; } + } + + internal sealed override bool IsMetadataVirtual(bool ignoreInterfaceImplementationChanges = false) + { + return true; + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedIsOperator.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedIsOperator.cs new file mode 100644 index 00000000..075ed765 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedIsOperator.cs @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + /// + /// Represents an interactive code entry point that is inserted into the compilation if there is not an existing one. + /// + internal sealed class SynthesizedIsOperator : SynthesizedStaticMethodBaseForRecords + { + private readonly ImmutableArray parameters; + private readonly TypeSymbol returnType; + private readonly ImmutableArray propertyNames; + + internal SynthesizedIsOperator(NamedTypeSymbol containingType, ImmutableArray parameterTypes, ImmutableArray parameterNames, ImmutableArray propertyNames, NamedTypeSymbol returnType) : + base(containingType, WellKnownMemberNames.IsOperatorName) + { + Debug.Assert((object)containingType != null); + + var paramBuilder = ArrayBuilder.GetInstance(); + + paramBuilder.Add(new SynthesizedParameterSymbol(this, containingType, 0, RefKind.None, "o")); + + for (int i = 0; i < parameterTypes.Length; i++) + { + paramBuilder.Add(new SynthesizedParameterSymbol(this, parameterTypes[i], i+1, RefKind.Out, parameterNames[i])); + } + + this.parameters = paramBuilder.ToImmutableAndFree(); + this.returnType = returnType; + this.propertyNames = propertyNames; + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + var F = CreateBoundNodeFactory(compilationState, diagnostics); + var statements = ArrayBuilder.GetInstance(); + var firstParameter = this.Parameters[0]; + + for (int i = 1; i < this.Parameters.Length; i++) + { + var currentParam = this.Parameters[i]; + var property = GetPropertyForThisParameter(i - 1); + + var assignment = F.Assignment(F.Parameter(currentParam), F.Call(F.Parameter(firstParameter), property.GetMethod)); + statements.Add(assignment); + } + statements.Add(F.Return(F.Literal(true))); + F.CloseMethod(F.Block(statements.ToImmutableAndFree())); + } + + public PropertySymbol GetPropertyForThisParameter(int i) + { + var temp = this.ContainingType.GetMembers(propertyNames[i]); + + Debug.Assert(temp.Length == 1 && temp[0] is PropertySymbol); + return (PropertySymbol) temp[0]; + } + + public override ImmutableArray Parameters + { + get { return parameters; } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Public; } + } + + public override TypeSymbol ReturnType + { + get { return returnType; } + } + + public override MethodKind MethodKind + { + get { return MethodKind.BuiltinOperator; } + } + + public override bool IsOverride + { + get { return false; } + } + + internal override bool HasSpecialName + { + get { return false; } + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedMethodBaseForRecords.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedMethodBaseForRecords.cs new file mode 100644 index 00000000..17a95d0d --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedMethodBaseForRecords.cs @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + + internal abstract class SynthesizedInstanceMethodBaseForRecords : SynthesizedMethodBaseForRecords + { + private ParameterSymbol lazyThisParameter; + + public SynthesizedInstanceMethodBaseForRecords(NamedTypeSymbol containingType, string name) : + base(containingType, name) + { } + + public sealed override bool IsStatic + { + get { return false; } + } + + internal override bool TryGetThisParameter(out ParameterSymbol thisParameter) + { + if ((object)lazyThisParameter == null) + { + Interlocked.CompareExchange(ref lazyThisParameter, new ThisParameterSymbol(this), null); + } + + thisParameter = lazyThisParameter; + return true; + } + + internal sealed override Microsoft.Cci.CallingConvention CallingConvention + { + get { return Microsoft.Cci.CallingConvention.HasThis; } + } + } + + internal abstract class SynthesizedStaticMethodBaseForRecords : SynthesizedMethodBaseForRecords + { + public SynthesizedStaticMethodBaseForRecords(NamedTypeSymbol containingType, string name) : + base(containingType, name) + { } + + internal sealed override Microsoft.Cci.CallingConvention CallingConvention + { + get { return 0; } + } + + public sealed override bool IsStatic + { + get { return true; } + } + } + + internal abstract class SynthesizedMethodBaseForRecords : MethodSymbol + { + private readonly NamedTypeSymbol containingType; + private readonly string name; + + public SynthesizedMethodBaseForRecords(NamedTypeSymbol containingType, string name) + { + this.containingType = containingType; + this.name = name; + } + + internal sealed override bool GenerateDebugInfo + { + get { return false; } + } + + public sealed override int Arity + { + get { return 0; } + } + + public sealed override Symbol ContainingSymbol + { + get { return this.containingType; } + } + + public override NamedTypeSymbol ContainingType + { + get + { + return this.containingType; + } + } + + public override bool ReturnsVoid + { + get { return this.ReturnType.SpecialType == SpecialType.System_Void; } + } + + public override ImmutableArray Locations + { + get { return ImmutableArray.Empty; } + } + + public abstract override Accessibility DeclaredAccessibility + { + get; + } + + public sealed override bool IsVirtual + { + get { return false; } + } + + public sealed override bool IsAsync + { + get { return false; } + } + + internal sealed override System.Reflection.MethodImplAttributes ImplementationAttributes + { + get { return default(System.Reflection.MethodImplAttributes); } + } + + public sealed override bool IsExtensionMethod + { + get { return false; } + } + + public sealed override bool HidesBaseMethodsByName + { + get { return false; } + } + + public sealed override bool IsVararg + { + get { return false; } + } + + public sealed override ImmutableArray TypeArguments + { + get { return ImmutableArray.Empty; } + } + + public sealed override ImmutableArray TypeParameters + { + get { return ImmutableArray.Empty; } + } + + internal sealed override bool IsExplicitInterfaceImplementation + { + get { return false; } + } + + public sealed override ImmutableArray ExplicitInterfaceImplementations + { + get { return ImmutableArray.Empty; } + } + + public sealed override ImmutableArray ReturnTypeCustomModifiers + { + get { return ImmutableArray.Empty; } + } + + public override Symbol AssociatedSymbol + { + get { return null; } + } + + public sealed override bool IsAbstract + { + get { return false; } + } + + public sealed override bool IsSealed + { + get { return false; } + } + + public sealed override bool IsExtern + { + get { return false; } + } + + public sealed override string Name + { + get { return this.name; } + } + + internal sealed override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) + { + return false; + } + + internal sealed override bool RequiresSecurityObject + { + get { return false; } + } + + public sealed override DllImportData GetDllImportData() + { + return null; + } + + internal sealed override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation + { + get { return null; } + } + + internal sealed override bool HasDeclarativeSecurity + { + get { return false; } + } + + internal sealed override IEnumerable GetSecurityInformation() + { + throw ExceptionUtilities.Unreachable; + } + + internal sealed override ImmutableArray GetAppliedConditionalSymbols() + { + return ImmutableArray.Empty; + } + + internal override bool SynthesizesLoweredBoundBody + { + get + { + return true; + } + } + + internal override bool IsMetadataVirtual(bool ignoreInterfaceImplementationChanges = false) + { + return false; + } + + internal override bool IsMetadataFinal() + { + return false; + } + + public override ImmutableArray Parameters + { + get { return ImmutableArray.Empty; } + } + + protected SyntheticBoundNodeFactory CreateBoundNodeFactory(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + var F = new SyntheticBoundNodeFactory(this, this.GetNonNullSyntaxNode(), compilationState, diagnostics); + F.CurrentMethod = this; + return F; + } + + public abstract override bool IsOverride + { + get; + } + + internal override LexicalSortKey GetLexicalSortKey() + { + return LexicalSortKey.NotInSource; + } + + internal sealed override ObsoleteAttributeData ObsoleteAttributeData + { + get { return null; } + } + + public override ImmutableArray DeclaringSyntaxReferences + { + get + { + return ImmutableArray.Empty; + } + } + + public sealed override bool IsImplicitlyDeclared + { + get { return true; } + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPropertySymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPropertySymbol.cs new file mode 100644 index 00000000..362abc56 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedPropertySymbol.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Symbols +{ + + internal sealed class SynthesizedPropertySymbol : PropertySymbol + { + private readonly NamedTypeSymbol containingType; + private readonly TypeSymbol type; + private readonly string name; + private readonly Location location; + private readonly SynthesizedPropertyGetAccessorSymbol getMethod; + private readonly FieldSymbol backingField; + + internal SynthesizedPropertySymbol(string name, Location location, NamedTypeSymbol containingType, TypeSymbol type) + { + this.containingType = containingType; + this.type = type; + this.name = name; + this.location = location; + + this.backingField = new SynthesizedBackingFieldSymbol(this, + name: GeneratedNames.MakeBackingFieldName(this.name), + isReadOnly: true, + isStatic: false, + hasInitializer: true); + + this.getMethod = new SynthesizedPropertyGetAccessorSymbol(this); + } + + public override TypeSymbol Type + { + get { return this.type; } + } + + public override string Name + { + get { return this.name; } + } + + internal override bool HasSpecialName + { + get { return false; } + } + + public override bool IsImplicitlyDeclared + { + get { return true; } + } + + public override ImmutableArray Locations + { + get + { + return ImmutableArray.Create(location); + } + } + + public override ImmutableArray DeclaringSyntaxReferences + { + get + { + return GetDeclaringSyntaxReferenceHelper(this.Locations); + } + } + + public override bool IsStatic + { + get { return false; } + } + + public override bool IsOverride + { + get { return false; } + } + + public override bool IsVirtual + { + get { return false; } + } + + public override bool IsIndexer + { + get { return false; } + } + + public override bool IsSealed + { + get { return true; } + } + + public override bool IsAbstract + { + get { return false; } + } + + internal sealed override ObsoleteAttributeData ObsoleteAttributeData + { + get { return null; } + } + + public override ImmutableArray Parameters + { + get { return ImmutableArray.Empty; } + } + + public override MethodSymbol SetMethod + { + get { return null; } + } + + public override ImmutableArray TypeCustomModifiers + { + get { return ImmutableArray.Empty; } + } + + internal override Microsoft.Cci.CallingConvention CallingConvention + { + get { return Microsoft.Cci.CallingConvention.HasThis; } + } + + public override ImmutableArray ExplicitInterfaceImplementations + { + get { return ImmutableArray.Empty; } + } + + public override Symbol ContainingSymbol + { + get { return this.containingType; } + } + + public override NamedTypeSymbol ContainingType + { + get + { + return this.containingType; + } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Public; } + } + + internal override bool MustCallMethodsDirectly + { + get { return false; } + } + + public override bool IsExtern + { + get { return false; } + } + + public override MethodSymbol GetMethod + { + get { return this.getMethod; } + } + + public FieldSymbol BackingField + { + get { return this.backingField; } + } + + internal override LexicalSortKey GetLexicalSortKey() + { + return new LexicalSortKey(location, this.DeclaringCompilation); + } + + internal override bool IsDefinedInSourceTree(SyntaxTree tree, TextSpan? definedWithinSpan, CancellationToken cancellationToken = default(CancellationToken)) + { + return true; + } + } + + internal sealed class SynthesizedPropertyGetAccessorSymbol : SynthesizedInstanceMethodBaseForRecords + { + SynthesizedPropertySymbol property; + + internal SynthesizedPropertyGetAccessorSymbol(SynthesizedPropertySymbol property) : + base(containingType: property.ContainingType, + name: SourcePropertyAccessorSymbol.GetAccessorName(property.Name, getNotSet: true, isWinMdOutput: false)) + { + this.property = property; + } + + internal override void GenerateMethodBody(TypeCompilationState compilationState, DiagnosticBag diagnostics) + { + var F = CreateBoundNodeFactory(compilationState, diagnostics); + F.CloseMethod(F.Block(F.Return(F.Field(F.This(), property.BackingField)))); + } + + public override MethodKind MethodKind + { + get { return MethodKind.PropertyGet; } + } + + public override TypeSymbol ReturnType + { + get { return this.property.Type; } + } + + public override Symbol AssociatedSymbol + { + get { return this.property; } + } + + public override ImmutableArray Locations + { + get + { + // The accessor for a anonymous type property has the same location as the property. + return this.property.Locations; + } + } + + internal override void AddSynthesizedAttributes(ModuleCompilationState compilationState, ref ArrayBuilder attributes) + { + // Do not call base.AddSynthesizedAttributes. + // Dev11 does not emit DebuggerHiddenAttribute in property accessors + } + + internal override bool HasSpecialName + { + get { return true; } + } + + public override Accessibility DeclaredAccessibility + { + get { return Accessibility.Public; } + } + + public override bool IsOverride + { + get { return false; } + } + } +} diff --git a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index 8ea2526c..17abfcb8 100644 --- a/Src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/Src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -15,11 +15,11 @@ namespace Microsoft.CodeAnalysis.CSharp.Symbols /// internal sealed class SynthesizedBackingFieldSymbol : SynthesizedFieldSymbolBase { - private readonly SourcePropertySymbol property; + private readonly PropertySymbol property; private readonly bool hasInitializer; public SynthesizedBackingFieldSymbol( - SourcePropertySymbol property, + PropertySymbol property, string name, bool isReadOnly, bool isStatic, diff --git a/Src/Compilers/CSharp/Portable/Syntax/RecordParameterListSyntax.cs b/Src/Compilers/CSharp/Portable/Syntax/RecordParameterListSyntax.cs new file mode 100644 index 00000000..37e4be76 --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Syntax/RecordParameterListSyntax.cs @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 RecordParameterListSyntax + { + internal int ParameterCount + { + get + { + return this.Parameters.Count; + } + } + } +} \ No newline at end of file diff --git a/Src/Compilers/CSharp/Portable/Syntax/RecordParameterSyntax.cs b/Src/Compilers/CSharp/Portable/Syntax/RecordParameterSyntax.cs new file mode 100644 index 00000000..4fdc9e8d --- /dev/null +++ b/Src/Compilers/CSharp/Portable/Syntax/RecordParameterSyntax.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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 +{ + internal interface IParameterSyntax + { + EqualsValueClauseSyntax Default { get; } + SyntaxList AttributeLists { get; } + SyntaxReference GetReference(); + } + + public partial class RecordParameterSyntax : IParameterSyntax + { + internal bool IsArgList + { + get + { + return this.Type == null && this.Identifier.CSharpContextualKind() == SyntaxKind.ArgListKeyword; + } + } + } + + public partial class ParameterSyntax : IParameterSyntax + { + } +} \ No newline at end of file diff --git a/Src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/Src/Compilers/CSharp/Portable/Syntax/Syntax.xml index c891ca7c..00bfb2ca 100644 --- a/Src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/Src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -1023,6 +1023,27 @@ Creates an NameColonSyntax node. + + + + + + SyntaxToken representing colon. + + + + + + IdentifierNameSyntax representing the identifer name. + + + + Class which represents the syntax node for name colon syntax. + + + Creates an NameColonSyntax node. + + @@ -2043,6 +2064,42 @@ Creates a DefaultSwitchLabelSyntax node. + + + + + + Gets the case keyword token. + + + + + + Gets a PatternSyntax that represents the pattern that gets matched for the case label. + + + + + + + Gets the with keyword token. + + + + + + Gets an ExpressionSyntax that represents the condition after the with keyword. + + + + + + Represents a case label within a switch statement. + + + Creates a CaseMatchLabelSyntax node. + + @@ -2398,6 +2455,37 @@ + + + Record type declaration syntax. + + + + + + + Gets the class keyword token. + + + + + + + + + + + + + + + + + + + + + Class type declaration syntax. @@ -2855,6 +2943,7 @@ + @@ -3140,6 +3229,25 @@ + + + Parameter list syntax. + + + + + Gets the open paren token. + + + + + + + Gets the close paren token. + + + + Parameter list syntax with surrounding brackets. @@ -3184,6 +3292,36 @@ + + + Parameter syntax. + + + + + Gets the attribute declaration list. + + + + + Gets the modifier list. + + + + + + Gets the identifier. + + + + + + + NameColonSyntax node representing the optional name arguments. + + + + @@ -3896,4 +4034,126 @@ Creates an NameOfExpressionSyntax node. + + + + + ExpressionSyntax node representing the expression on the left of the binary operator. + + + + + + + + ExpressionSyntax node representing the expression on the right of the binary operator. + + + + Class which represents an expression that has a binary operator. + + + Creates an BinaryExpressionSyntax node. + + + + + + + + + + + + + + + + + + ExpressionSyntax node representing the constant expression. + + + + + + + + + Gets the identifier. + + + + + + + + + + SubPatternListSyntax node representing the list of patterns in a recursive pattern. + + + + + + + + + SyntaxToken representing open parenthesis. + + + + + SeparatedSyntaxList of SubPatternsSyntax representing the list of subpatterns. + + + + + + SyntaxToken representing close parenthesis. + + + + + + + + NameColonSyntax node representing the optional name arguments. + + + + + PatternSyntax node representing the pattern. + + + + + + + + + + + + + + SyntaxToken representing open brace. + + + + + + + SyntaxToken representing close brace. + + + + + + + + + + + \ No newline at end of file diff --git a/Src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/Src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 4862d1c1..0f5c07d2 100644 --- a/Src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/Src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -516,7 +516,35 @@ public enum SyntaxKind : ushort IncompleteMember = 8652, ArrowExpressionClause = 8653, - // expression - ImplicitElementAccess = 8654 + ImplicitElementAccess = 8654, + // Records + RecordDeclaration = 8655, + RecordKeyword = 8656, + RecordParameterList = 8657, + RecordParameter = 8658, + + // Extended is operator: match + MatchExpression = 8659, + + // Extended switch statement: match + MatchStatement = 8660, + CaseMatchLabel = 8661, + + // Patterns + ConstantPattern = 8662, + DeclarationPattern = 8663, + WildCardPattern = 8664, + RecursivePattern = 8665, + PropertyPattern = 8666, + + // Auxiliary nodes for RecursivePattern + SubRecursivePattern = 8667, + SubRecursivePatternList = 8668, + ColonName = 8669, + + // Auxiliary nodes for PropertyPattern + SubPropertyPatternList = 8670, + SubPropertyPattern = 8671, + } } diff --git a/Src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs b/Src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs index ef15df15..5a8e4359 100644 --- a/Src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs +++ b/Src/Compilers/CSharp/Portable/Syntax/SyntaxKindFacts.cs @@ -292,6 +292,7 @@ public static bool IsTypeDeclaration(SyntaxKind kind) case SyntaxKind.DelegateDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: return true; @@ -324,6 +325,7 @@ public static bool IsNamespaceMemberDeclaration(SyntaxKind kind) switch (kind) { case SyntaxKind.ClassDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.DelegateDeclaration: @@ -1049,6 +1051,7 @@ public static bool IsContextualKeyword(SyntaxKind kind) case SyntaxKind.NameOfKeyword: case SyntaxKind.AsyncKeyword: case SyntaxKind.AwaitKeyword: + case SyntaxKind.RecordKeyword: return true; default: return false; @@ -1146,6 +1149,8 @@ public static SyntaxKind GetContextualKeywordKind(string text) return SyntaxKind.AwaitKeyword; case "nameof": return SyntaxKind.NameOfKeyword; + case "record": + return SyntaxKind.RecordKeyword; default: return SyntaxKind.None; } @@ -1539,6 +1544,8 @@ public static string GetText(SyntaxKind kind) return "await"; case SyntaxKind.NameOfKeyword: return "nameof"; + case SyntaxKind.RecordKeyword: + return "record"; default: return string.Empty; } diff --git a/Src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj b/Src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj index afd5b0d8..4b231eb0 100644 --- a/Src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj +++ b/Src/Compilers/CSharp/Test/Semantic/CSharpCompilerSemanticTest.csproj @@ -129,6 +129,7 @@ + diff --git a/Src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs b/Src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs new file mode 100644 index 00000000..a6eb258e --- /dev/null +++ b/Src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs @@ -0,0 +1,958 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class PatternMatchingTests : CSharpTestBase + { + [Fact] + public void MatchExpressionConstantPattern() + { + var source = @" +using System; +class Program +{ + enum Days { Sun, Mon, Tue } + static void Main(string[] args) + { + // Object to Literals + object o; + o = (int)2; + if (o is 2) + Console.WriteLine(""int 2""); + + o = (uint)2; + if (o is 2) + Console.WriteLine(""uint 2""); + + o = (double)2.5; + if (o is 2.5) + Console.WriteLine(""double 2.5""); + + o = (float)2.5; + if (o is 2.5) + Console.WriteLine(""float 2.5""); + + o = (string)""2""; + if (o is ""2"") + Console.WriteLine(""string 2""); + + o = Days.Mon; + if (o is Days.Mon) + Console.WriteLine(""enum Days.Mon""); + + o = true; + if (o is true) + Console.WriteLine(""bool true""); + + o = (int)2; + if (o is ""2"") + Console.WriteLine(""FAIL""); + else if (o is Days.Sun) + Console.WriteLine(""FAIL""); + else if (o is 3.5) + Console.WriteLine(""FAIL""); + + // Constant variables and binary expression + const int const2 = 2; + o = (int)2; + if (o is const2) + Console.WriteLine(""const""+ const2); + + if (o is const2 + const2 - const2) + Console.WriteLine(""const2 + const2 - const2""); + + if (o is -(-const2)) + Console.WriteLine(""-(-const2)""); + + if (o is 1 + 1) + Console.WriteLine(""1+1""); + + // Constant expressions + if (o is sizeof(int)) + Console.WriteLine(""FAIL""); + + string b = ""b""; + if (b is nameof(b)) + Console.WriteLine(""nameof(b)""); + + // null + Test t = null; + if (t is null) + Console.WriteLine(""null""); + + int a = 3; + if (a is null) + Console.WriteLine(""FAIL""); + + // parsed as type + bool test = t is Test; + test = t is Program.Test; + test = o is int; + test = o is int?; + } + class Test { } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +int 2 +uint 2 +double 2.5 +float 2.5 +string 2 +enum Days.Mon +bool true +const2 +const2 + const2 - const2 +-(-const2) +1+1 +nameof(b) +null").VerifyDiagnostics(); + } + + + [Fact] + public void MatchStatementConstantPattern() + { + var source = @" +using System; +class Program +{ + enum Days { Sun, Mon, Tue } + const int const2 = 2; + static void Main(string[] args) + { + MatchConstant((object)2); + MatchConstant(Days.Mon); + MatchConstant((float)2.5); + object o=null; + MatchConstant(o); + MatchConstant(true); + MatchConstant(sizeof(int)); + } + static void MatchConstant(object o) + { + switch (o) + { + case 2: + Console.WriteLine(""int 2""); + break; + case 2.5: + Console.WriteLine(""double 2.5""); + break; + case ""2"": + Console.WriteLine(""string 2""); + break; + case Days.Mon: + Console.WriteLine(""Days.Mon""); + break; + case true: + Console.WriteLine(""bool true""); + break; + case null: + Console.WriteLine(""null""); + break; + case const2: + Console.WriteLine(""const2""); + break; + case const2 + const2 - const2: + Console.WriteLine(""const2 + const2 - const2""); + break; + case 1 + 1: + Console.WriteLine(""1+1""); + break; + case sizeof(int): + Console.WriteLine(""sizeof(int)""); + break; + case nameof(o): + Console.WriteLine(""nameof(o)""); + break; + case int a : + Console.WriteLine(""""); + break; + } + } + class Test { } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +int 2 +Days.Mon +double 2.5 +null +bool true +sizeof(int)").VerifyDiagnostics(); + } + + [Fact] + public void MatchExpressionStatementSimplePatternDiagnostics() + { + var source = @" +using System; +class Program +{ + enum Days { Sun, Mon, Tue } + static void Main(string[] args) + { + object o = null; + int b = 3; + switch (b) + { + case o: break; + case 2.5: break; + case ""2"": break; + case Days.Sun: break; + case null: break; + case int s : break; + case string f: break; + case Super a : break; + case typeof(int): break; + case string: break; + } + Sub sub = null; + Super super = null; + if (sub is Super s2) + Console.Write(s2); + if (super is Sub s2) + Console.Write(s2); + + bool r = o is int; + r = o is b++; + r = o is b; + r = b is 4.5; + r = b is ""3""; + } + class Super { } + class Sub : Super { } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + compilation.VerifyDiagnostics( + // (21,24): error CS1001: Identifier expected + // case string: break; + Diagnostic(ErrorCode.ERR_IdentifierExpected, ":").WithLocation(21, 24), + // (31,19): error CS1002: ; expected + // r = o is b++; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "++").WithLocation(31, 19), + // (31,21): error CS1525: Invalid expression term ';' + // r = o is b++; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ";").WithArguments(";").WithLocation(31, 21), + // (12,18): error CS0150: A constant value is expected + // case o: break; + Diagnostic(ErrorCode.ERR_ConstantExpected, "o").WithLocation(12, 18), + // (13,18): error CS0029: Cannot implicitly convert type 'double' to 'int' + // case 2.5: break; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "2.5").WithArguments("double", "int").WithLocation(13, 18), + // (14,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // case "2": break; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""2""").WithArguments("string", "int").WithLocation(14, 18), + // (15,18): error CS0029: Cannot implicitly convert type 'Program.Days' to 'int' + // case Days.Sun: break; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Days.Sun").WithArguments("Program.Days", "int").WithLocation(15, 18), + // (18,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // case string f: break; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "string f").WithArguments("string", "int").WithLocation(18, 18), + // (19,18): error CS0029: Cannot implicitly convert type 'Program.Super' to 'int' + // case Super a : break; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Super a").WithArguments("Program.Super", "int").WithLocation(19, 18), + // (20,18): error CS0150: A constant value is expected + // case typeof(int): break; + Diagnostic(ErrorCode.ERR_ConstantExpected, "typeof(int)").WithLocation(20, 18), + // (21,18): error CS8047: A declaration expression is not permitted in this context. + // case string: break; + Diagnostic(ErrorCode.ERR_DeclarationExpressionOutOfContext, "string").WithLocation(21, 18), + // (25,20): error CS0029: Cannot implicitly convert type 'Program.Super' to 'Program.Sub' + // if (sub is Super s2) + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Super s2").WithArguments("Program.Super", "Program.Sub").WithLocation(25, 20), + // (31,18): error CS0118: 'b' is a variable but is used like a type + // r = o is b++; + Diagnostic(ErrorCode.ERR_BadSKknown, "b").WithArguments("b", "variable", "type").WithLocation(31, 18), + // (32,18): error CS0118: 'b' is a variable but is used like a type + // r = o is b; + Diagnostic(ErrorCode.ERR_BadSKknown, "b").WithArguments("b", "variable", "type").WithLocation(32, 18), + // (33,18): error CS0029: Cannot implicitly convert type 'double' to 'int' + // r = b is 4.5; + Diagnostic(ErrorCode.ERR_NoImplicitConv, "4.5").WithArguments("double", "int").WithLocation(33, 18), + // (34,18): error CS0029: Cannot implicitly convert type 'string' to 'int' + // r = b is "3"; + Diagnostic(ErrorCode.ERR_NoImplicitConv, @"""3""").WithArguments("string", "int").WithLocation(34, 18)); + + } + + [Fact] + public void MatchStatementAndExpression_Patterns_WithoutRecords() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + object o = (int)2; + Expr exp = new Add(new Add(new Const(3), new Const(4)), new Const(5)); + Verify(3.5); + Verify(""2""); + Verify(new Const(31)); + Verify(new Const(100)); + Verify(new Add(new Const(2), new Const(2))); + Verify(new Add(new Add(new Const(2), new Const(0)), new Const(3))); + Verify(new Add(new Add(new Const(1), new Const(2)), + new Add(new Const(0), new Const(1231)))); + Verify(new Add(new Add(new Const(1), new Const(2)), + new Add(new Const(123), new Const(3)))); + Verify(new Add(new Add(new Const(31), new Const(322)), + new Add(new Const(123), new Const(322)))); + } + + static void Verify(object o) + { + bool r = MatchExpressions(o) == MatchStatements(o); + Console.WriteLine(r + "" "" + MatchStatements(o)); + } + static string MatchStatements(object o) + { + switch (o) + { // from specific to more general: + case Add(Add(Const(1), Const(2)), Add(Const(0), Const(int z))) : + return ""(1 + 2) + (0 + "" + z + "")""; + case Add(Add(Const(1), Const(2)), Add(Const(*), Const(int u))) : + return ""(1 + 2) + (x + "" + u + "")""; + case Add(var x, Const(2)) : + return x + "" + 2""; + case Add(var x, Const(*)) : + return x + "" + const""; + case Const(100) : + return ""const 100""; + case Const(int e) : + return ""const "" + e; + case Const(*) : + return ""const x""; + case Add a : + return a.Left + "" + "" + a.right; + case double d : + return ""double "" + d; + default: + return ""None""; + } + } + static string MatchExpressions(object o) + { + if (o is Add(Add(Const(1), Const(2)), Add(Const(0), Const(int z)))) + return ""(1 + 2) + (0 + "" + z + "")""; + else if (o is Add(Add(Const(1), Const(2)), Add(Const(*), Const(int u)))) + return ""(1 + 2) + (x + "" + u + "")""; + else if (o is Add(var x, Const(2))) + return x + "" + 2""; + else if (o is Add(var x, Const(*))) + return x + "" + const""; + else if (o is Const(100)) + return ""const 100""; + else if (o is Const(int e)) + return ""const "" + e; + else if (o is Const(*)) + return ""const x""; + else if (o is Add a) + return a.Left + "" + "" + a.right; + else if (o is double d) + return ""double "" + d; + else + return ""None""; + } +} + +public abstract class Expr { } +public class Const(int x) : Expr +{ + public int X { get; } = x; + public static bool operator is(Const a, out int x) + { + x = a.X; + return true; + } +} +public class Add(Expr left, Expr right) : Expr +{ + public Expr Left { get; } = left; + public Expr right { get; } = right; + public static bool operator is(Add a, out Expr left, out Expr right) + { + left = a.Left; + right = a.right; + return true; + } +} +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +True double 3.5 +True None +True const 31 +True const 100 +True Const + 2 +True Add + const +True (1 + 2) + (0 + 1231) +True (1 + 2) + (x + 3) +True Add + Add").VerifyDiagnostics(); + } + + [Fact] + public void MatchStatementAndExpression_Patterns_WithRecords() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + object o = (int)2; + Expr exp = new Add(new Add(new Const(3), new Const(4)), new Const(5)); + Verify(3.5); + Verify(""2""); + Verify(new Const(31)); + Verify(new Const(100)); + Verify(new Add(new Const(2), new Const(2))); + Verify(new Add(new Add(new Const(2), new Const(0)), new Const(3))); + Verify(new Add(new Add(new Const(1), new Const(2)), + new Add(new Const(0), new Const(1231)))); + Verify(new Add(new Add(new Const(1), new Const(2)), + new Add(new Const(123), new Const(3)))); + Verify(new Add(new Add(new Const(31), new Const(322)), + new Add(new Const(123), new Const(322)))); + } + + static void Verify(object o) + { + bool r = MatchExpressions(o) == MatchStatements(o); + Console.WriteLine(r + "" "" + MatchStatements(o)); + } + static string MatchStatements(object o) + { + switch (o) + { // from specific to more general: + case Add(Add(Const(1), Const(2)), Add(Const(0), Const(int z))) : + return ""(1 + 2) + (0 + "" + z + "")""; + case Add(Add(Const(1), Const(2)), Add(Const(*), Const(int u))) : + return ""(1 + 2) + (x + "" + u + "")""; + case Add(var x, Const(2)) : + return x + "" + 2""; + case Add(var x, Const(*)) : + return x + "" + const""; + case Const(100) : + return ""const 100""; + case Const(int e) : + return ""const "" + e; + case Const(*) : + return ""const x""; + case Add a : + return a.Left + "" + "" + a.right; + case double d : + return ""double "" + d; + default: + return ""None""; + } + } + static string MatchExpressions(object o) + { + if (o is Add(Add(Const(1), Const(2)), Add(Const(0), Const(int z)))) + return ""(1 + 2) + (0 + "" + z + "")""; + else if (o is Add(Add(Const(1), Const(2)), Add(Const(*), Const(int u)))) + return ""(1 + 2) + (x + "" + u + "")""; + else if (o is Add(var x, Const(2))) + return x + "" + 2""; + else if (o is Add(var x, Const(*))) + return x + "" + const""; + else if (o is Const(100)) + return ""const 100""; + else if (o is Const(int e)) + return ""const "" + e; + else if (o is Const(*)) + return ""const x""; + else if (o is Add a) + return a.Left + "" + "" + a.right; + else if (o is double d) + return ""double "" + d; + else + return ""None""; + } +} +public abstract class Expr { } +public record class Const(int x : X) : Expr { } +public record class Add(Expr left : Left, Expr right) : Expr { } +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +True double 3.5 +True None +True const 31 +True const 100 +True Const + 2 +True Add + const +True (1 + 2) + (0 + 1231) +True (1 + 2) + (x + 3) +True Add + Add").VerifyDiagnostics(); + } + + [Fact] + public void CartesianPolar_CustomIsOperator() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + var c = new Cartesian(3, 4); + if (c is Polar(double e, var d)) + Console.WriteLine(""Cartesian ""+e +"", "" +d); + c = new Cartesian(6, 8); + if (c is Polar(10.0, double d)) + Console.WriteLine(""Tan: ""+d); + } +} +public class Polar +{ + public static bool operator is(Cartesian c, out double R, out double Theta) + { + R = Math.Sqrt(c.X * c.X + c.Y * c.Y); + Theta = Math.Atan2(c.Y, c.X); + return c.X != 0 || c.Y != 0; + } +} +public record class Cartesian(double x :X, double y :Y) { } +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +Cartesian 5, 0.927295218001612 +Tan: 0.927295218001612").VerifyDiagnostics(); + } + + [Fact] + public void ExpressionSimplification_NamedPatterns_WithRecords() + { + var source = @" + +using System; +class Program +{ + static void Main(string[] args) + { + // exp = (0 * (1 + 2)) + 0 + Expr exp = new Add(new Mult(new Const(0), + new Add(new Const(1), + new Const(2))), + new Const(0)); + Expr expected = new Const(0); + Console.WriteLine(""Depth: "" + depth(exp)); + exp = Simplify(exp); + Console.WriteLine(""Depth after simp: "" + depth(exp) + "" isCorrect: "" + exp.Equals(expected)); + // exp = (0 + (1 + (2 * 0))) + (1 * (0 + (2 * 0))) + exp = new Add(new Add(new Const(0), + new Add(new Const(1), + new Mult(new Const(2), + new Const(0)))), + new Mult(new Const(1), + new Add(new Const(0), + new Mult(new Const(2), + new Const(0))))); + expected = new Const(1); + Console.WriteLine(""Depth: "" + depth(exp)); + exp = Simplify(exp); + Console.WriteLine(""Depth after simp: "" + depth(exp) + "" isCorrect: "" + exp.Equals(expected)); + } + + static int depth(Expr e) + { + switch (e) + { + case Add(var leftAdd, right: var rightAdd) : return 1 + Math.Max(depth(leftAdd), depth(rightAdd)); + case Mult(left: var leftMult, right: var rightMult) : return 1 + Math.Max(depth(leftMult), depth(rightMult)); + default: return 0; + } + } + static Expr Simplify(Expr e) + { + Expr newNode = null; + switch (e) + { + case Const c : return c; + case Mult(Const(x: 0), right: *) : + case Mult(right: Const(0), left: *) : return new Const(0); + case Add(Const(0), right: var right) : return Simplify(right); + case Add(var left, Const(x: 0)) : return Simplify(left); + case Mult mult : + newNode = new Mult(Simplify(mult.Left), Simplify(mult.Right)); + break; + case Add add : + newNode = new Add(Simplify(add.Left), Simplify(add.Right)); + } + Expr simp = Simplify(newNode); + return simp.Equals(newNode) ? newNode : Simplify(simp); + } +} +public abstract class Expr { } +public record class Const(int x : X) : Expr { } +public record class Add(Expr left : Left, Expr right : Right) : Expr { } +public record class Mult(Expr left : Left, Expr right : Right) : Expr { } + +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +Depth: 3 +Depth after simp: 0 isCorrect: True +Depth: 4 +Depth after simp: 0 isCorrect: True").VerifyDiagnostics(); + } + + [Fact] + public void Generated_NamedProperties_GetHashCode_Equals_RecordsTest() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + Add addExpr1 = new Add(new Const(0), new Const(0)); + Mult multExpr1 = new Mult(new Const(0), new Const(0)); + + Const constE = (Const) addExpr1.Left; + int x = constE.X; + Expr right = addExpr1.right; + right = multExpr1.Right; + constE = (Const)multExpr1.left; + + Add addExpr2 = new Add(new Const(0), new Const(0)); + if(addExpr1.Equals(addExpr2)) + Console.WriteLine(""Equal""); + if(addExpr2.Equals(addExpr1)) + Console.WriteLine(""Equal""); + addExpr2 = new Add(new Const(0), new Const(1)); + if(addExpr1.GetHashCode() != addExpr2.GetHashCode()) + Console.WriteLine(""Not Equal""); + } +} +public abstract class Expr { } +public record class Const (int x : X) : Expr {} +public record class Add (Expr left: Left, Expr right) : Expr {} +public record class Mult (Expr left, Expr right: Right) : Expr {} +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +Equal +Equal +Not Equal").VerifyDiagnostics(); + } + + [Fact] + public void OverloadResolutionForOpIs() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + // exp = (0 * (1 + 2)) + Expr exp = new Mult(new Const(0), new Add(new Const(1), new Const(2))); + if (3 is Const(Const f)) + Console.WriteLine(f.X); + if (exp is Add(right: double d, left: int i)) + Console.WriteLine(i+""+""+d); + if (exp is Mult(right: Add e, left: Const(0))) + Console.WriteLine(e); + if (exp is Mult(Const(0), Add(Test t, Const e))) + Console.WriteLine(t+""*""+e.X); + } +} +public class Test { } +public abstract class Expr { } +public record class Const(int x : X) : Expr +{ + public static bool operator is(Const e, out int x) + { + x = e.X; + return true; + } + public static bool operator is(Const e, out Expr f) + { + f = null; + return true; + } + public static bool operator is(int e, out Const f) + { + f = new Const(3); + return true; + } +} +public record class Add(Expr left, Expr right) : Expr +{ + public static bool operator is(Add e, out Expr left, out Expr right) + { + left = e.left; + right = e.right; + return true; + } + public static bool operator is(int e, out Expr left, out Expr right) + { + left = new Const(31); + right = new Const(31); + return true; + } + public static bool operator is(Test e, out Test left, out Expr right) + { + left = new Test(); + right = new Const(31); + return true; + } + public static bool operator is(int e, out Test left, out Expr right) + { + left = new Test(); + right = new Const(3); + return true; + } + public static bool operator is(Expr e, out int left, out double right) + { + left = 2; + right = 3; + return true; + } +} +public record class Mult(Expr left, Expr right) : Expr +{ + public static bool operator is(Mult e, out Expr left, out Expr right) + { + left = e.left; + right = e.right; + return true; + } + + public static bool operator is(Mult e, out Expr left, out Test right) + { + left = e.left; + right = new Test(); + return true; + } +} +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +3 +2+3 +Add +Test*31").VerifyDiagnostics(); + } + + [Fact] + public void OpIsTypeCheckingDiagnosis() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + // exp = (0 * (1 + 2)) + Expr exp = new Mult(new Const(0), new Add(new Const(1), new Const(2))); + bool b= 3 is Const(Const f, int y)); + b = exp is Const(f: Test()); + b = exp is Const(g: *); + b = exp is Mult(Const(0), Add(Const(int s), Const(2))); + + // should give an error. right is not Test in all opIs + b = exp is Add(right: Test c, left: *); + b = exp is Add(*, Test t2); + } +} +public class Test { } +public abstract class Expr { } +public record class Const(int x : X) : Expr +{ + public static bool operator is(Const e, out int x) + { + x = e.X; + return true; + } + public static bool operator is(Const e, out Expr f) + { + f = null; + return true; + } + public static bool operator is(int e, out Const f) + { + f = new Const(3); + return true; + } +} +public record class Add(Expr left, Expr right) : Expr +{ + public static bool operator is(int e, out Expr left, out Expr right) + { + left = new Const(31); + right = new Const(31); + return true; + } + public static bool operator is(Test e, out Test left, out Expr right) + { + left = new Test(); + right = new Const(31); + return true; + } + public static bool operator is(int e, out Test left, out Expr right) + { + left = new Test(); + right = new Const(3); + return true; + } + public static bool operator is(Expr e, out int left, out double right) + { + left = 2; + right = 3; + return true; + } +} +public record class Mult(Expr left, Expr right) : Expr +{ + public static bool operator is(Mult e, out Expr left, out Expr right) + { + left = e.left; + right = e.right; + return true; + } + + public static bool operator is(Mult e, out Expr left, out Test right) + { + left = e.left; + right = new Test(); + return true; + } +}"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + compilation.VerifyDiagnostics( + // (9,43): error CS1002: ; expected + // bool b= 3 is Const(Const f, int y)); + Diagnostic(ErrorCode.ERR_SemicolonExpected, ")").WithLocation(9, 43), + // (9,43): error CS1513: } expected + // bool b= 3 is Const(Const f, int y)); + Diagnostic(ErrorCode.ERR_RbraceExpected, ")").WithLocation(9, 43), + // (9,22): error CS0117: 'Const' does not contain a definition for 'is operator' + // bool b= 3 is Const(Const f, int y)); + Diagnostic(ErrorCode.ERR_NoSuchMember, "Const(Const f, int y)").WithArguments("Const", "is operator").WithLocation(9, 22), + // (10,29): error CS0117: 'Test' does not contain a definition for 'is operator' + // b = exp is Const(f: Test()); + Diagnostic(ErrorCode.ERR_NoSuchMember, "Test()").WithArguments("Test", "is operator").WithLocation(10, 29), + // (11,20): error CS0117: 'Const' does not contain a definition for 'is operator' + // b = exp is Const(g: *); + Diagnostic(ErrorCode.ERR_NoSuchMember, "Const(g: *)").WithArguments("Const", "is operator").WithLocation(11, 20), + // (12,35): error CS0029: Cannot implicitly convert type 'int' to 'Test' + // b = exp is Mult(Const(0), Add(Const(int s), Const(2))); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Add").WithArguments("int", "Test").WithLocation(12, 35), + // (15,31): error CS0029: Cannot implicitly convert type 'Test' to 'double' + // b = exp is Add(right: Test c, left: *); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Test c").WithArguments("Test", "double").WithLocation(15, 31), + // (16,27): error CS0029: Cannot implicitly convert type 'Test' to 'double' + // b = exp is Add(*, Test t2); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "Test t2").WithArguments("Test", "double").WithLocation(16, 27), + // (9,34): warning CS0168: The variable 'f' is declared but never used + // bool b= 3 is Const(Const f, int y)); + Diagnostic(ErrorCode.WRN_UnreferencedVar, "f").WithArguments("f").WithLocation(9, 34), + // (9,41): warning CS0168: The variable 'y' is declared but never used + // bool b= 3 is Const(Const f, int y)); + Diagnostic(ErrorCode.WRN_UnreferencedVar, "y").WithArguments("y").WithLocation(9, 41), + // (12,49): warning CS0168: The variable 's' is declared but never used + // b = exp is Mult(Const(0), Add(Const(int s), Const(2))); + Diagnostic(ErrorCode.WRN_UnreferencedVar, "s").WithArguments("s").WithLocation(12, 49), + // (15,36): warning CS0168: The variable 'c' is declared but never used + // b = exp is Add(right: Test c, left: *); + Diagnostic(ErrorCode.WRN_UnreferencedVar, "c").WithArguments("c").WithLocation(15, 36), + // (16,32): warning CS0168: The variable 't2' is declared but never used + // b = exp is Add(*, Test t2); + Diagnostic(ErrorCode.WRN_UnreferencedVar, "t2").WithArguments("t2").WithLocation(16, 32), + // (2,1): hidden CS8019: Unnecessary using directive. + // using System; + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using System;").WithLocation(2, 1)); + + } + + [Fact] + public void PropertyPattern() + { + var source = @" +using System; +class Program +{ + static void Main(string[] args) + { + + Expr exp = new Const(3); + + if (exp is Const {X is 3}) + Console.WriteLine(""OK""); + + if (exp is Const{X is var s}) + Console.WriteLine(s); + + exp = new Add(new Const(0), new Const(2)); + + if (exp is Add(Const{ X is 0}, Const(var s))) + Console.WriteLine(s); + + if(exp is Add { Left is Const(0)}) + Console.WriteLine(""OK""); + + if(exp is Add { Left is Const { X is 0}, right is Const(2) }) + Console.WriteLine(""OK""); + } +} +public abstract class Expr { } +public record class Const(int x : X) : Expr { } +public record class Add(Expr left : Left, Expr right) : Expr { } +"; + + var compilation = CreateCompilationWithMscorlib(source, options: TestOptions.ReleaseExe, + parseOptions: TestOptions.Regular.WithLanguageVersion(LanguageVersion.Experimental).WithPreprocessorSymbols("RECORDS")); + + var verifier = CompileAndVerify(compilation, expectedOutput: @" +OK +3 +2 +OK +OK").VerifyDiagnostics(); + } + } +} diff --git a/Src/Compilers/CSharp/Test/Semantic/packages.config b/Src/Compilers/CSharp/Test/Semantic/packages.config index ac5d8237..59c41321 100644 --- a/Src/Compilers/CSharp/Test/Semantic/packages.config +++ b/Src/Compilers/CSharp/Test/Semantic/packages.config @@ -1,12 +1,11 @@  - - + + - \ No newline at end of file diff --git a/Src/Compilers/CSharp/Test/Syntax/packages.config b/Src/Compilers/CSharp/Test/Syntax/packages.config index ac5d8237..59c41321 100644 --- a/Src/Compilers/CSharp/Test/Syntax/packages.config +++ b/Src/Compilers/CSharp/Test/Syntax/packages.config @@ -1,12 +1,11 @@  - - + + - \ No newline at end of file diff --git a/Src/Compilers/CSharp/csc/packages.config b/Src/Compilers/CSharp/csc/packages.config index 323b87e6..adacd7e2 100644 --- a/Src/Compilers/CSharp/csc/packages.config +++ b/Src/Compilers/CSharp/csc/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/Src/Compilers/Core/MSBuildTasks/packages.config b/Src/Compilers/Core/MSBuildTasks/packages.config index 323b87e6..adacd7e2 100644 --- a/Src/Compilers/Core/MSBuildTasks/packages.config +++ b/Src/Compilers/Core/MSBuildTasks/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/Src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/Src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 51ca2417..8e406f1d 100644 --- a/Src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/Src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -248,6 +248,11 @@ public static class WellKnownMemberNames /// public const string LikeOperatorName = "op_Like"; + /// + /// The name assigned to the Is operator + /// + public const string IsOperatorName = "op_Is"; + /// /// The required name for the GetEnumerator method used in a ForEach statement. /// diff --git a/Src/Compilers/PackageFiles/packages.config b/Src/Compilers/PackageFiles/packages.config index 89dd20ac..feebd31d 100644 --- a/Src/Compilers/PackageFiles/packages.config +++ b/Src/Compilers/PackageFiles/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/Src/Compilers/Test/Utilities/CSharp/CSharpTrackingDiagnosticAnalyzer.cs b/Src/Compilers/Test/Utilities/CSharp/CSharpTrackingDiagnosticAnalyzer.cs index a789baa1..28562934 100644 --- a/Src/Compilers/Test/Utilities/CSharp/CSharpTrackingDiagnosticAnalyzer.cs +++ b/Src/Compilers/Test/Utilities/CSharp/CSharpTrackingDiagnosticAnalyzer.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Test.Utilities public class CSharpTrackingDiagnosticAnalyzer : TrackingDiagnosticAnalyzer { static readonly Regex omittedSyntaxKindRegex = - new Regex(@"Using|Extern|Parameter|Constraint|Specifier|Initializer|Global|Method|Destructor|MemberBindingExpression|ElementBindingExpression|ArrowExpressionClause|NameOfExpression"); + new Regex(@"Using|Extern|Parameter|Constraint|Specifier|Initializer|Global|Method|Destructor|MemberBindingExpression|ElementBindingExpression|ArrowExpressionClause|NameOfExpression|ConstantPattern|DeclarationPattern|WildCardPattern|RecursivePattern|PropertyPattern|ColonName|RecordDeclaration|MatchExpression|SubRecursivePattern|SubPropertyPattern|MatchStatement|CaseMatchLabel"); protected override bool IsOnCodeBlockSupported(SymbolKind symbolKind, MethodKind methodKind, bool returnsVoid) {