提交 52fc8439 编写于 作者: V vsadov 提交者: VSadov

added tuple type symbol

Rebased onto future branch
Fixed Tuple field signatures
Fixed tests affected by tuple parsing
上级 f39ba171
......@@ -510,6 +510,7 @@
<Compile Include="Symbols\AnonymousTypes\SynthesizedSymbols\AnonymousType.TemplateSymbol.cs" />
<Compile Include="Symbols\AnonymousTypes\SynthesizedSymbols\AnonymousType.ToStringMethodSymbol.cs" />
<Compile Include="Symbols\AnonymousTypes\SynthesizedSymbols\AnonymousType.TypeParameterSymbol.cs" />
<Compile Include="Symbols\TupleTypeSymbol.cs" />
<Compile Include="Symbols\ArrayTypeSymbol.cs" />
<Compile Include="Symbols\AssemblySymbol.cs" />
<Compile Include="Symbols\Attributes\AttributeData.cs" />
......
......@@ -131,7 +131,7 @@ public override ImmutableArray<Symbol> GetMembers()
/// no members with this name, returns an empty ImmutableArray. Never returns Null.</returns>
public override ImmutableArray<Symbol> GetMembers(string name)
{
return ImmutableArray<Symbol>.Empty;
return GetMembers().WhereAsArray(m => m.Name == name);
}
internal sealed override IEnumerable<FieldSymbol> GetFieldsToEmit()
......
......@@ -28,6 +28,11 @@ public virtual void VisitArrayType(ArrayTypeSymbol symbol)
DefaultVisit(symbol);
}
public virtual void VisitTupleType(TupleTypeSymbol symbol)
{
DefaultVisit(symbol);
}
public virtual void VisitAssembly(AssemblySymbol symbol)
{
DefaultVisit(symbol);
......
......@@ -28,6 +28,11 @@ public virtual TResult VisitArrayType(ArrayTypeSymbol symbol)
return DefaultVisit(symbol);
}
public virtual TResult VisitTupleType(TupleTypeSymbol symbol)
{
return DefaultVisit(symbol);
}
public virtual TResult VisitAssembly(AssemblySymbol symbol)
{
return DefaultVisit(symbol);
......
......@@ -104,6 +104,18 @@ public virtual TResult VisitArrayType(ArrayTypeSymbol symbol, TArgument argument
return DefaultVisit(symbol, argument);
}
/// <summary>
/// Called when visiting an <see cref="TupleTypeSymbol" />; Override this with specific
/// implementation; Calling <see cref="DefaultVisit" /> if it's not overridden
/// </summary>
/// <param name="symbol">The visited symbol</param>
/// <param name="argument">Additional argument</param>
/// <returns></returns>
public virtual TResult VisitTupleType(TupleTypeSymbol symbol, TArgument argument)
{
return DefaultVisit(symbol, argument);
}
/// <summary>
/// Called when visiting a <see cref="PointerTypeSymbol" />; Override this with specific
/// implementation; Calling <see cref="DefaultVisit" /> if it's not overridden
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
/// <summary>
/// A TupleTypeSymbol represents a tuple type, such as (int, byte) or (int a, long b).
/// </summary>
internal sealed class TupleTypeSymbol : TypeSymbol, ITupleTypeSymbol
{
private readonly NamedTypeSymbol _underlyingType;
private readonly ImmutableArray<TupleFieldSymbol> _fields;
/// <summary>
/// Create a new TupleTypeSymbol from its declaration in source.
/// </summary>
internal TupleTypeSymbol(
ImmutableArray<TypeSymbol> elementTypes,
ImmutableArray<string> elementNames,
CSharpSyntaxNode syntax,
Binder binder,
DiagnosticBag diagnostics)
{
this._underlyingType = GetTupleUnderlyingTypeAndFields(
elementTypes,
elementNames,
syntax,
binder,
diagnostics,
out this._fields);
}
private NamedTypeSymbol GetTupleUnderlyingTypeAndFields(
ImmutableArray<TypeSymbol> elementTypes,
ImmutableArray<string> elementNames,
CSharpSyntaxNode syntax,
Binder binder,
DiagnosticBag diagnostics,
out ImmutableArray<TupleFieldSymbol> fields
)
{
NamedTypeSymbol underlyingType;
fields = ImmutableArray<TupleFieldSymbol>.Empty;
switch (elementTypes.Length)
{
case 2:
{
var tupleType = binder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2, diagnostics, syntax);
underlyingType = tupleType.Construct(elementTypes);
var underlyingField1 = Binder.GetWellKnownTypeMember(binder.Compilation, WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item1, diagnostics, syntax: syntax) as FieldSymbol;
var underlyingField2 = Binder.GetWellKnownTypeMember(binder.Compilation, WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item2, diagnostics, syntax: syntax) as FieldSymbol;
fields = ImmutableArray.Create(
new TupleFieldSymbol(elementNames.IsEmpty ? "Item1" : elementNames[0],
this,
elementTypes[0],
underlyingField1?.AsMember(underlyingType)),
new TupleFieldSymbol(elementNames.IsEmpty ? "Item2" : elementNames[1],
this,
elementTypes[1],
underlyingField2?.AsMember(underlyingType))
);
return underlyingType;
}
default:
{
// TODO: VS if this eventually still stays reachable, need to make some error type symbol
var tupleType = binder.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2, diagnostics, syntax);
underlyingType = tupleType.Construct(elementTypes);
break;
}
}
return underlyingType;
}
// TODO: VS does it make sense to this this two-stage?
// get underlying type (above),
// then if all ok
// get underlying fields here.
// NOTE: underlying fields do not need the underlying type and in case of an erro still can be created.
private ImmutableArray<TupleFieldSymbol> GetTupleFields(
ImmutableArray<string> elementNames,
NamedTypeSymbol _underlyingType)
{
throw new NotImplementedException();
}
internal override NamedTypeSymbol BaseTypeNoUseSiteDiagnostics
{
get
{
return _underlyingType.BaseTypeNoUseSiteDiagnostics;
}
}
internal override ImmutableArray<NamedTypeSymbol> InterfacesNoUseSiteDiagnostics(ConsList<Symbol> basesBeingResolved = null)
{
return _underlyingType.InterfacesNoUseSiteDiagnostics(basesBeingResolved);
}
public override bool IsReferenceType
{
get
{
return _underlyingType.IsReferenceType;
}
}
public override bool IsValueType
{
get
{
return _underlyingType.IsReferenceType;
}
}
internal sealed override bool IsManagedType
{
get
{
return _underlyingType.IsManagedType;
}
}
internal sealed override ObsoleteAttributeData ObsoleteAttributeData
{
get { return _underlyingType.ObsoleteAttributeData; }
}
public override ImmutableArray<Symbol> GetMembers()
{
// TODO: members
return ImmutableArray<Symbol>.Empty;
}
public override ImmutableArray<Symbol> GetMembers(string name)
{
// TODO: members
return ImmutableArray<Symbol>.Empty;
}
public override ImmutableArray<NamedTypeSymbol> GetTypeMembers()
{
return ImmutableArray<NamedTypeSymbol>.Empty;
}
public override ImmutableArray<NamedTypeSymbol> GetTypeMembers(string name)
{
return ImmutableArray<NamedTypeSymbol>.Empty;
}
public override ImmutableArray<NamedTypeSymbol> GetTypeMembers(string name, int arity)
{
return ImmutableArray<NamedTypeSymbol>.Empty;
}
public override SymbolKind Kind
{
get
{
return SymbolKind.TupleType;
}
}
public override TypeKind TypeKind
{
get
{
return TypeKind.Tuple;
}
}
public override Symbol ContainingSymbol
{
get
{
return null;
}
}
public override ImmutableArray<Location> Locations
{
get
{
return ImmutableArray<Location>.Empty;
}
}
public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
{
get
{
return ImmutableArray<SyntaxReference>.Empty;
}
}
internal override TResult Accept<TArgument, TResult>(CSharpSymbolVisitor<TArgument, TResult> visitor, TArgument argument)
{
return visitor.VisitTupleType(this, argument);
}
public override void Accept(CSharpSymbolVisitor visitor)
{
visitor.VisitTupleType(this);
}
public override TResult Accept<TResult>(CSharpSymbolVisitor<TResult> visitor)
{
return visitor.VisitTupleType(this);
}
internal override bool Equals(TypeSymbol t2, bool ignoreCustomModifiers, bool ignoreDynamic)
{
return this.Equals(t2 as TupleTypeSymbol, ignoreCustomModifiers, ignoreDynamic);
}
internal bool Equals(TupleTypeSymbol other)
{
return Equals(other, false, false);
}
private bool Equals(TupleTypeSymbol other, bool ignoreCustomModifiers, bool ignoreDynamic)
{
if (ReferenceEquals(this, other))
{
return true;
}
if ((object)other == null || !other._underlyingType.Equals(_underlyingType, ignoreCustomModifiers, ignoreDynamic))
{
return false;
}
// Make sure field names are the same.
if (!ignoreDynamic)
{
var fields = this._fields;
var otherFields = other._fields;
var count = fields.Length;
for (int i = 0; i < count; i++)
{
if (fields[i].Name != otherFields[i].Name)
{
return false;
}
}
}
return true;
}
public override int GetHashCode()
{
return _underlyingType.GetHashCode();
}
public override Accessibility DeclaredAccessibility
{
get
{
return Accessibility.NotApplicable;
}
}
public override bool IsStatic
{
get
{
return false;
}
}
public override bool IsAbstract
{
get
{
return false;
}
}
public override bool IsSealed
{
get
{
return true;
}
}
#region Use-Site Diagnostics
internal override DiagnosticInfo GetUseSiteDiagnostic()
{
DiagnosticInfo result = null;
// check element type
if (DeriveUseSiteDiagnosticFromType(ref result, this._underlyingType))
{
return result;
}
return result;
}
internal override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo result, Symbol owner, ref HashSet<TypeSymbol> checkedTypes)
{
return _underlyingType.GetUnificationUseSiteDiagnosticRecursive(ref result, owner, ref checkedTypes);
}
#endregion
#region ITupleTypeSymbol Members
#endregion
#region ISymbol Members
public override void Accept(SymbolVisitor visitor)
{
visitor.VisitTupleType(this);
}
public override TResult Accept<TResult>(SymbolVisitor<TResult> visitor)
{
return visitor.VisitTupleType(this);
}
#endregion
}
internal sealed class TupleFieldSymbol : FieldSymbol
{
private readonly string _name;
private readonly FieldSymbol _underlyingFieldOpt;
private readonly TypeSymbol _type;
private readonly TupleTypeSymbol _containingTuple;
/// <summary>
/// Missing underlying field is handled for error recovery
/// A tuple without backing fields is usable for binding purposes, since we know its name and tyoe,
/// but caller is supposed to report some kind of error at declaration.
/// </summary>
public TupleFieldSymbol(string name, TupleTypeSymbol containingTuple, TypeSymbol type, FieldSymbol underlyingFieldOpt)
{
this._name = name;
this._containingTuple = containingTuple;
this._type = type;
this._underlyingFieldOpt = underlyingFieldOpt;
}
public override Symbol AssociatedSymbol
{
get
{
return null;
}
}
public override Symbol ContainingSymbol
{
get
{
return _containingTuple;
}
}
public override ImmutableArray<CustomModifier> CustomModifiers
{
get
{
return _underlyingFieldOpt?.CustomModifiers ?? ImmutableArray<CustomModifier>.Empty;
}
}
public override Accessibility DeclaredAccessibility
{
get
{
return _underlyingFieldOpt?.DeclaredAccessibility ?? Accessibility.Public;
}
}
public override bool IsConst
{
get
{
return _underlyingFieldOpt?.IsConst ?? false;
}
}
public override bool IsReadOnly
{
get
{
return _underlyingFieldOpt?.IsReadOnly ?? false;
}
}
public override bool IsStatic
{
get
{
return _underlyingFieldOpt?.IsStatic ?? false;
}
}
public override bool IsVolatile
{
get
{
return _underlyingFieldOpt?.IsVolatile ?? false;
}
}
public override ImmutableArray<Location> Locations
{
get
{
return ImmutableArray<Location>.Empty;
}
}
public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
{
get
{
return ImmutableArray<SyntaxReference>.Empty;
}
}
internal override bool HasRuntimeSpecialName
{
get
{
return _underlyingFieldOpt?.HasRuntimeSpecialName ?? false;
}
}
internal override bool HasSpecialName
{
get
{
return _underlyingFieldOpt?.HasSpecialName ?? false;
}
}
internal override bool IsNotSerialized
{
get
{
return _underlyingFieldOpt?.IsNotSerialized ?? false;
}
}
internal override MarshalPseudoCustomAttributeData MarshallingInformation
{
get
{
return _underlyingFieldOpt?.MarshallingInformation;
}
}
internal override ObsoleteAttributeData ObsoleteAttributeData
{
get
{
return _underlyingFieldOpt?.ObsoleteAttributeData;
}
}
internal override int? TypeLayoutOffset
{
get
{
return _underlyingFieldOpt?.TypeLayoutOffset;
}
}
internal override ConstantValue GetConstantValue(ConstantFieldsInProgress inProgress, bool earlyDecodingWellKnownAttributes)
{
return _underlyingFieldOpt?.GetConstantValue(inProgress, earlyDecodingWellKnownAttributes);
}
internal override TypeSymbol GetFieldType(ConsList<FieldSymbol> fieldsBeingBound)
{
return _type;
}
}
}
......@@ -557,6 +557,7 @@ public void AllWellKnownTypes()
continue;
case WellKnownType.System_FormattableString:
case WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory:
case WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2:
// Not yet in the platform.
continue;
}
......@@ -596,6 +597,8 @@ public void AllWellKnownTypeMembers()
// C# can't embed VB core.
continue;
case WellKnownMember.System_Array__Empty:
case WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item1:
case WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item2:
// Not available yet, but will be in upcoming release.
continue;
}
......
......@@ -600,6 +600,7 @@
<Compile Include="Symbols\CommonAttributeDataExtensions.cs" />
<Compile Include="Symbols\CustomModifier.cs" />
<Compile Include="Symbols\IAliasSymbol.cs" />
<Compile Include="Symbols\ITupleTypeSymbol.cs" />
<Compile Include="Symbols\IArrayTypeSymbol.cs" />
<Compile Include="Symbols\IAssemblySymbol.cs" />
<Compile Include="Symbols\IDynamicTypeSymbol.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// Represents a tuple.
/// </summary>
/// <remarks>
/// This interface is reserved for implementation by its associated APIs. We reserve the right to
/// change it in the future.
/// </remarks>
public interface ITupleTypeSymbol : ITypeSymbol
{
// not sure what will be here.
// "UnderlyingType" ?
}
}
......@@ -101,5 +101,10 @@ public enum SymbolKind
/// Symbol is a preprocessing/conditional compilation constant.
/// </summary>
Preprocessing = 18,
/// <summary>
/// Symbol is a tuple type.
/// </summary>
TupleType = 19,
}
}
......@@ -23,6 +23,11 @@ public virtual void VisitArrayType(IArrayTypeSymbol symbol)
DefaultVisit(symbol);
}
public virtual void VisitTupleType(ITupleTypeSymbol symbol)
{
DefaultVisit(symbol);
}
public virtual void VisitAssembly(IAssemblySymbol symbol)
{
DefaultVisit(symbol);
......
......@@ -26,6 +26,11 @@ public virtual TResult VisitArrayType(IArrayTypeSymbol symbol)
return DefaultVisit(symbol);
}
public virtual TResult VisitTupleType(ITupleTypeSymbol symbol)
{
return DefaultVisit(symbol);
}
public virtual TResult VisitAssembly(IAssemblySymbol symbol)
{
return DefaultVisit(symbol);
......
......@@ -79,5 +79,10 @@ public enum TypeKind : byte
/// Type is an interactive submission.
/// </summary>
Submission = 12,
/// <summary>
/// Type is a tuple.
/// </summary>
Tuple = 13,
}
}
......@@ -354,6 +354,9 @@ internal enum WellKnownMember
System_Runtime_GCLatencyMode__SustainedLowLatency,
System_Runtime_CompilerServices_Tuple_T1_T2__Item1,
System_Runtime_CompilerServices_Tuple_T1_T2__Item2,
System_String__Format_IFormatProvider,
Count
}
......
......@@ -2519,6 +2519,18 @@ static WellKnownMembers()
0, // Arity
(byte)SignatureTypeCode.TypeHandle, (byte)WellKnownType.System_Runtime_GCLatencyMode, // Field Signature
// System_Runtime_CompilerServices_Tuple_T1_T2__Item1
(byte)MemberFlags.Field, // Flags
(byte)WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2, // DeclaringTypeId
0, // Arity
(byte)SignatureTypeCode.GenericTypeParameter, 0, // Field Signature
// System_Runtime_CompilerServices_Tuple_T1_T2__Item2
(byte)MemberFlags.Field, // Flags
(byte)WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2, // DeclaringTypeId
0, // Arity
(byte)SignatureTypeCode.GenericTypeParameter, 1, // Field Signature
// System_String__Format_IFormatProvider
(byte)(MemberFlags.Method | MemberFlags.Static), // Flags
(byte)SpecialType.System_String, // DeclaringTypeId
......@@ -2825,6 +2837,8 @@ static WellKnownMembers()
"CurrentManagedThreadId", // System_Environment__CurrentManagedThreadId
".ctor", // System_ComponentModel_EditorBrowsableAttribute__ctor
"SustainedLowLatency", // System_Runtime_GCLatencyMode__SustainedLowLatency
"Item1", // System_Runtime_CompilerServices_Tuple_T1_T2__Item1
"Item2", // System_Runtime_CompilerServices_Tuple_T1_T2__Item2
"Format", // System_String__Format_IFormatProvider
};
......
......@@ -244,6 +244,9 @@ internal enum WellKnownType
System_Environment,
System_Runtime_GCLatencyMode,
System_Runtime_CompilerServices_Tuple_T1_T2,
System_IFormatProvider,
Available,
......@@ -485,7 +488,10 @@ internal static class WellKnownTypes
"System.Environment",
"System.Runtime.GCLatencyMode",
"System.IFormatProvider"
"System.Runtime.CompilerServices.Tuple`2",
"System.IFormatProvider",
};
private readonly static Dictionary<string, WellKnownType> s_nameToTypeIdMap = new Dictionary<string, WellKnownType>((int)Count);
......
......@@ -498,7 +498,9 @@ End Namespace
Case WellKnownType.Microsoft_VisualBasic_CompilerServices_EmbeddedOperators
' Only present when embedding VB Core.
Continue For
Case WellKnownType.System_FormattableString, WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory
Case WellKnownType.System_FormattableString,
WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory,
WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2
' Not available on all platforms.
Continue For
End Select
......@@ -526,7 +528,9 @@ End Namespace
WellKnownType.Microsoft_VisualBasic_Interaction
' Not embedded, so not available.
Continue For
Case WellKnownType.System_FormattableString, WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory
Case WellKnownType.System_FormattableString,
WellKnownType.System_Runtime_CompilerServices_FormattableStringFactory,
WellKnownType.System_Runtime_CompilerServices_Tuple_T1_T2
' Not available on all platforms.
Continue For
End Select
......@@ -560,7 +564,9 @@ End Namespace
Case WellKnownMember.Count
' Not a real value.
Continue For
Case WellKnownMember.System_Array__Empty
Case WellKnownMember.System_Array__Empty,
WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item1,
WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item2
' Not available yet, but will be in upcoming release.
Continue For
End Select
......@@ -637,7 +643,9 @@ End Namespace
WellKnownMember.Microsoft_VisualBasic_Interaction__CallByName
' The type is not embedded, so the member is not available.
Continue For
Case WellKnownMember.System_Array__Empty
Case WellKnownMember.System_Array__Empty,
WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item1,
WellKnownMember.System_Runtime_CompilerServices_Tuple_T1_T2__Item2
' Not available yet, but will be in upcoming release.
Continue For
End Select
......
......@@ -720,7 +720,7 @@ static void Main(string[] args)
}
[WorkItem(539536, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/539536")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
[Fact(Skip = "Tuples"), Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)]
public async Task BugFix5538()
{
await TestAsync(
......
......@@ -363,7 +363,7 @@ public async Task TestInLambdaDeclaration()
public async Task TestInLambdaDeclaration2()
{
await VerifyKeywordAsync(AddInsideMethod(
@"var q = (a, $$"));
@"var q = (ref int a, $$"));
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
......
......@@ -438,7 +438,7 @@ public async Task TestInLambdaDeclaration()
public async Task TestInLambdaDeclaration2()
{
await VerifyKeywordAsync(AddInsideMethod(
@"var q = (a, $$"));
@"var q = (ref int a, $$"));
}
[Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)]
......
......@@ -553,7 +553,7 @@ public async Task TestInUnsafeOperator()
{
await VerifyKeywordAsync(
@"class C {
unsafe operator int ++(C c) {
unsafe int operator ++(C c) {
$$");
}
......
......@@ -60,7 +60,7 @@ public IEnumerable<Entry> CallLog
#region Analysis
private static readonly Regex s_omittedSyntaxKindRegex =
new Regex(@"None|Trivia|Token|Keyword|List|Xml|Cref|Compilation|Namespace|Class|Struct|Enum|Interface|Delegate|Field|Property|Indexer|Event|Operator|Constructor|Access|Incomplete|Attribute|Filter|InterpolatedString.*");
new Regex(@"None|Trivia|Token|Keyword|List|Xml|Cref|Compilation|Namespace|Class|Struct|Enum|Interface|Delegate|Field|Property|Indexer|Event|Operator|Constructor|Access|Incomplete|Attribute|Filter|InterpolatedString|TupleType|TupleElement|TupleExpression.*");
private bool FilterByAbstractName(Entry entry, string abstractMemberName)
{
......
......@@ -443,11 +443,11 @@ public async Task TestMissingMethodName()
{
class C1
{
static void (int x)
static void (ref int x)
{
$$}
}
}", "N1.C1.?", 2);
}", "N1.C1", 4);
}
[Fact, Trait(Traits.Feature, Traits.Features.DebuggingLocationName)]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册