asynchronousai's picture
Upload 33 files
1ae2e8e verified
using System.Text;
using System.Collections.Immutable;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
namespace RobloxCS
{
internal class CodeGeneratorFlags
{
public bool ShouldCallGetAssemblyType { get; set; }
public bool ClientEntryPointDefined { get; set; }
public bool ServerEntryPointDefined { get; set; }
}
internal sealed class CodeGenerator : CSharpSyntaxWalker
{
private readonly SyntaxTree _tree;
private readonly ConfigData _config;
private readonly MemberCollectionResult _members;
private readonly string _inputDirectory;
private readonly CSharpCompilation _compiler;
private readonly SemanticModel _semanticModel;
private readonly SymbolAnalyzerResults _symbolAnalysis;
private readonly INamespaceSymbol _globalNamespace;
private readonly INamespaceSymbol _runtimeLibNamespace;
private readonly RojoProject? _rojoProject;
private readonly int _indentSize;
private readonly CodeGeneratorFlags _flags = new CodeGeneratorFlags
{
ShouldCallGetAssemblyType = true,
ClientEntryPointDefined = false,
ServerEntryPointDefined = false
};
private readonly StringBuilder _output = new StringBuilder();
private int _indent = 0;
public CodeGenerator(
SyntaxTree tree,
CSharpCompilation compiler,
RojoProject? rojoProject,
MemberCollectionResult members,
ConfigData config,
string inputDirectory,
int indentSize = 4
)
{
_tree = tree;
_config = config;
_members = members;
_inputDirectory = inputDirectory;
_compiler = compiler;
_semanticModel = compiler.GetSemanticModel(tree);
var symbolAnalyzer = new SymbolAnalyzer(tree, _semanticModel);
_symbolAnalysis = symbolAnalyzer.Analyze();
_globalNamespace = compiler.GlobalNamespace;
_runtimeLibNamespace = _globalNamespace.GetNamespaceMembers().FirstOrDefault(ns => ns.Name == Utility.RuntimeAssemblyName)!;
_rojoProject = rojoProject;
_indentSize = indentSize;
}
public string GenerateLua()
{
WriteHeader();
Visit(_tree.GetRoot());
WriteFooter();
return _output.ToString().Trim();
}
private void WriteHeader()
{
Write($"local CS = ");
WriteRequire(GetLuaRuntimeLibPath());
WriteLine();
foreach (var (namespaceName, declaringFiles) in GetNamespaceFilePaths())
{
var symbol = _tree.GetRoot()
.DescendantNodes()
.Select(node => _semanticModel.GetTypeInfo(node).Type)
.OfType<INamespaceOrTypeSymbol>()
.Where(namespaceSymbol => namespaceSymbol.ContainingAssembly != null && namespaceSymbol.ContainingAssembly.Name == _config.CSharpOptions.AssemblyName)
.FirstOrDefault(symbol => symbol.Name == namespaceName);
var namespaceSymbol = symbol == null ? null : (symbol.IsNamespace ? symbol : symbol.ContainingNamespace);
if (namespaceSymbol == null || !_symbolAnalysis.TypeHasMemberUsedAsValue(namespaceSymbol))
{
continue;
}
WriteLine($"-- using {namespaceName};");
foreach (var csharpFilePath in declaringFiles)
{
WriteLine($"require({GetRequirePath(csharpFilePath)})");
}
WriteLine();
}
}
private void WriteFooter()
{
if (!_tree.FilePath.EndsWith(".client.cs") && !_tree.FilePath.EndsWith(".server.cs"))
{
WriteLine("return {}");
}
}
private string GetLuaRuntimeLibPath()
{
if (_rojoProject == null)
{
return $"\"{Utility.LuaRuntimeModuleName}\"";
}
else
{
return RojoReader.ResolveInstancePath(_rojoProject, $"include/{Utility.LuaRuntimeModuleName}.lua")!;
}
}
private string GetRequirePath(string longCSharpFilePath)
{
var inputDirectory = _inputDirectory.EndsWith('/') ? _inputDirectory : _inputDirectory + '/';
var csharpFilePath = longCSharpFilePath.Replace(inputDirectory, "").Replace(_config.SourceFolder, _config.OutputFolder);
if (csharpFilePath.StartsWith('/'))
{
csharpFilePath = csharpFilePath.Substring(1);
}
if (_rojoProject == null)
{
return "\"./" + csharpFilePath.Replace(".cs", "") + '"';
}
else
{
return RojoReader.ResolveInstancePath(_rojoProject, csharpFilePath)!;
}
}
private Dictionary<string, HashSet<string>> GetNamespaceFilePaths()
{
var globalNamespaceSymbols = _tree.GetRoot()
.DescendantNodes()
.Select(node => _semanticModel.GetTypeInfo(node).Type)
.OfType<INamedTypeSymbol>()
.Where(namespaceSymbol => namespaceSymbol.ContainingAssembly != null && namespaceSymbol.ContainingAssembly.Name == _config.CSharpOptions.AssemblyName);
return new Dictionary<string, HashSet<string>>(
Utility.FilterDuplicates(globalNamespaceSymbols, SymbolEqualityComparer.Default)
.OfType<INamedTypeSymbol>()
.Select(namespaceSymbol => KeyValuePair.Create(namespaceSymbol.Name, GetPathsForSymbolDeclarations(namespaceSymbol)))
.Where(pair => !string.IsNullOrEmpty(pair.Key) && !pair.Value.Contains(_tree.FilePath))
);
}
private HashSet<string> GetPathsForSymbolDeclarations(ISymbol symbol)
{
var filePaths = new HashSet<string>();
foreach (var syntaxReference in symbol.DeclaringSyntaxReferences)
{
var syntaxTree = syntaxReference.SyntaxTree;
var filePath = syntaxTree.FilePath;
if (!string.IsNullOrEmpty(filePath))
{
filePaths.Add(filePath);
}
}
return filePaths;
}
public override void VisitRefExpression(RefExpressionSyntax node)
{
Logger.UnsupportedError(node, "Refs");
}
public override void VisitUnsafeStatement(UnsafeStatementSyntax node)
{
Logger.UnsupportedError(node, "Unsafe contexts");
}
public override void VisitUsingStatement(UsingStatementSyntax node)
{
Logger.UnsupportedError(node, "Using statements");
}
public override void VisitAttribute(AttributeSyntax node)
{
if (GetName(node) == "Native")
{
WriteLine("@native");
}
}
public override void VisitAttributeList(AttributeListSyntax node)
{
base.VisitAttributeList(node);
}
public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node)
{
foreach (var attributeList in node.AttributeLists)
{
Visit(attributeList);
}
Write($"local function {GetName(node)}");
Visit(node.ParameterList);
_indent++;
Visit(node.Body);
WriteDefaultReturn(node.Body);
_indent--;
WriteLine("end");
}
public override void VisitCastExpression(CastExpressionSyntax node)
{
// ignore
Visit(node.Expression);
}
public override void VisitThrowStatement(ThrowStatementSyntax node)
{
Throw(node.Expression);
}
public override void VisitThrowExpression(ThrowExpressionSyntax node)
{
Throw(node.Expression);
}
private void Throw(ExpressionSyntax? exception)
{
if (exception == null)
{
WriteLine("_catch_rethrow()");
}
else
{
Visit(exception);
Write(":Throw(");
Write((exception.Parent?.Parent is TryStatementSyntax).ToString().ToLower());
WriteLine(')');
}
}
public override void VisitTryStatement(TryStatementSyntax node)
{
WriteLine("CS.try(function()");
_indent++;
Visit(node.Block);
_indent--;
Write("end, ");
if (node.Finally != null)
{
WriteLine("function()");
_indent++;
Visit(node.Finally);
_indent--;
Write("end");
}
else
{
Write("nil");
}
WriteLine(", {");
_indent++;
foreach (var catchBlock in node.Catches)
{
WriteLine('{');
_indent++;
Write("exceptionClass = ");
if (catchBlock.Declaration != null)
{
Write('"');
Write(catchBlock.Declaration.Type.ToString());
Write('"');
WriteLine(", ");
}
Write("block = function(");
Write(catchBlock.Declaration != null ? (GetName(catchBlock.Declaration) + ": CS.Exception") : "_");
WriteLine(", _catch_rethrow: () -> nil)");
_indent++;
Visit(catchBlock.Block);
_indent--;
WriteLine("end");
_indent--;
WriteLine('}');
}
_indent--;
WriteLine("})");
}
public override void VisitForEachVariableStatement(ForEachVariableStatementSyntax node)
{
Visit(node.Variable);
}
public override void VisitForEachStatement(ForEachStatementSyntax node)
{
Write("for _, ");
Write(GetName(node));
Write(" in ");
Visit(node.Expression);
WriteLine(" do");
_indent++;
Visit(node.Statement);
_indent--;
WriteLine("end");
}
public override void VisitForStatement(ForStatementSyntax node)
{
var hasDeclaration = node.Declaration != null;
if (hasDeclaration)
{
WriteLine("do");
_indent++;
Visit(node.Declaration);
}
foreach (var initializer in node.Initializers)
{
Visit(initializer);
}
Write("while ");
if (node.Condition != null)
{
Visit(node.Condition);
}
else
{
Write("true");
}
WriteLine(" do");
_indent++;
Visit(node.Statement);
foreach (var incrementor in node.Incrementors)
{
Visit(incrementor);
}
_indent--;
WriteLine("end");
if (hasDeclaration)
{
_indent--;
WriteLine("end");
}
}
public override void VisitLabeledStatement(LabeledStatementSyntax node)
{
Logger.UnsupportedError(node, "Labels & goto statements");
}
public override void VisitGotoStatement(GotoStatementSyntax node)
{
Logger.UnsupportedError(node, "Labels & goto statements");
}
public override void VisitDoStatement(DoStatementSyntax node)
{
WriteLine("repeat");
_indent++;
Visit(node.Statement);
_indent--;
Write("until ");
Visit(node.Condition);
WriteLine();
}
public override void VisitWhileStatement(WhileStatementSyntax node)
{
Write("while ");
Visit(node.Condition);
WriteLine(" do");
_indent++;
Visit(node.Statement);
_indent--;
WriteLine("end");
}
public override void VisitIfStatement(IfStatementSyntax node)
{
Write("if ");
Visit(node.Condition);
WriteLine(" then");
_indent++;
WritePatternDeclarations(node.Condition);
Visit(node.Statement);
_indent--;
if (node.Else != null)
{
var isElseIf = node.Else.Statement.IsKind(SyntaxKind.IfStatement);
Write("else");
if (!isElseIf)
{
WriteLine();
_indent++;
}
Visit(node.Else);
if (!isElseIf)
{
_indent--;
WriteLine("end");
}
}
else
{
WriteLine("end");
}
}
public override void VisitSwitchStatement(SwitchStatementSyntax node)
{
var condition = node.Expression;
WriteLine("repeat");
_indent++;
var checkNoFallthrough = (StatementSyntax statement) =>
!statement.IsKind(SyntaxKind.BreakStatement)
&& !statement.IsKind(SyntaxKind.ReturnStatement)
&& !statement.DescendantNodes().All(descendant => !descendant.IsKind(SyntaxKind.BreakStatement) && !descendant.IsKind(SyntaxKind.ReturnStatement));
if (node.Sections.Count > 0 && !node.Sections.Any(section => section.Statements.Any(checkNoFallthrough))) // TODO: check for break/return
{
WriteLine("local _fallthrough = false");
}
foreach (var section in node.Sections)
{
var statementKinds = section.Statements.Select(stmt => stmt.Kind());
HashSet<SyntaxKind> supportedPatterns = [
SyntaxKind.VarPattern,
SyntaxKind.DeclarationPattern
];
var caseLabels = section.Labels.Where(label =>
!label.IsKind(SyntaxKind.DefaultSwitchLabel)
&& !(label is CasePatternSwitchLabelSyntax casePattern
&& supportedPatterns.Contains(casePattern.Pattern.Kind()))
);
foreach (var label in caseLabels)
{
Write("if ");
if (label != caseLabels.FirstOrDefault())
{
Write("_fallthrough or ");
}
var expressions = label.ChildNodes();
var expression = expressions.First();
Write('(');
Visit(condition);
var op = expression.IsKind(SyntaxKind.NotPattern) ?
"~="
: expression is RelationalPatternSyntax relationalPattern ?
Utility.GetMappedOperator(relationalPattern.OperatorToken.Text)
: "==";
Write($" {op} ");
Visit(expression);
if (label is CasePatternSwitchLabelSyntax casePattern && casePattern.WhenClause != null)
{
Write(" and ");
Visit(casePattern.WhenClause.Condition);
}
WriteLine(") then");
_indent++;
if (label != caseLabels.Last())
{
WriteLine("_fallthrough = true");
}
else
{
foreach (var statement in section.Statements)
{
Visit(statement);
if (statement == section.Statements.Last() && !statement.IsKind(SyntaxKind.ReturnStatement))
{
WriteLine("break");
}
}
}
_indent--;
WriteLine("end");
}
}
void visitDefaultStatements(SwitchLabelSyntax defaultLabel)
{
var section = (SwitchSectionSyntax)defaultLabel.Parent!;
foreach (var statement in section.Statements)
{
Visit(statement);
}
}
var casePatternLabels = node.Sections.SelectMany(section => section.Labels.Where(label => label.IsKind(SyntaxKind.CasePatternSwitchLabel)));
foreach (var casePatternLabel in casePatternLabels)
{
var pattern = casePatternLabel.ChildNodes().FirstOrDefault();
if (pattern.IsKind(SyntaxKind.VarPattern) || pattern.IsKind(SyntaxKind.DeclarationPattern))
{
Visit(pattern);
Write(" = ");
Visit(condition);
WriteLine();
visitDefaultStatements(casePatternLabel);
}
}
var defaultLabel = node.Sections.SelectMany(section => section.Labels.Where(label => label.IsKind(SyntaxKind.DefaultSwitchLabel))).FirstOrDefault();
if (defaultLabel != null)
{
visitDefaultStatements(defaultLabel);
}
_indent--;
WriteLine("until true");
}
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
{
Write("function");
Visit(node.ParameterList);
WriteLine();
_indent++;
if (node.Block != null || node.ExpressionBody.IsKind(SyntaxKind.SimpleAssignmentExpression))
{
Visit(node.Body);
}
else
{
Write("return ");
Visit(node.ExpressionBody);
WriteLine();
}
_indent--;
Write("end");
}
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
{
Write("function(");
Visit(node.Parameter);
Write(')');
WriteLine();
_indent++;
if (node.Block != null || node.ExpressionBody.IsKind(SyntaxKind.SimpleAssignmentExpression))
{
Visit(node.Body);
}
else
{
Write("return ");
Visit(node.ExpressionBody);
WriteLine();
}
_indent--;
Write("end");
}
public override void VisitBracketedArgumentList(BracketedArgumentListSyntax node)
{
Write('[');
if (node.Arguments.Count > 1)
{
Logger.CodegenError(node.Arguments.Last(), "Cannot have more than one argument between brackets.");
}
foreach (var argument in node.Arguments)
{
if (argument.Expression is LiteralExpressionSyntax numericLiteral && numericLiteral.IsKind(SyntaxKind.NumericLiteralExpression))
{
int.TryParse(numericLiteral.Token.ValueText, out var indexValue);
Write((indexValue + 1).ToString());
}
else
{
Visit(argument);
var typeSymbol = _semanticModel.GetTypeInfo(argument.Expression).Type;
if (typeSymbol != null && Constants.INTEGER_TYPES.Contains(typeSymbol.Name))
{
Write(" + 1");
}
}
}
Write(']');
}
public override void VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node)
{
Write("if ");
Visit(node.Expression);
Write(" == nil then nil else ");
Visit(node.WhenNotNull);
}
public override void VisitConditionalExpression(ConditionalExpressionSyntax node)
{
Write("if ");
Visit(node.Condition);
Write(" then ");
Visit(node.WhenTrue);
Write(" else ");
Visit(node.WhenFalse);
}
public override void VisitParenthesizedExpression(ParenthesizedExpressionSyntax node)
{
Write('(');
Visit(node.Expression);
Write(')');
}
public override void VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node)
{
Visit(node.Operand);
if (node.IsKind(SyntaxKind.SuppressNullableWarningExpression))
{
var typeSymbol = _semanticModel.GetTypeInfo(node.Operand).Type;
if (typeSymbol == null)
{
Write(" :: any");
}
return;
}
var mappedOperator = Utility.GetMappedOperator(node.OperatorToken.Text);
WriteLine($" {mappedOperator} 1");
}
public override void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node)
{
var mappedOperator = Utility.GetMappedOperator(node.OperatorToken.Text);
var bit32Method = Utility.GetBit32MethodName(mappedOperator);
if (bit32Method != mappedOperator)
{
Write($"bit32.{bit32Method}(");
Visit(node.Operand);
Write(")");
return;
}
Write(mappedOperator);
Visit(node.Operand);
}
public override void VisitBinaryExpression(BinaryExpressionSyntax node)
{
var operatorText = node.OperatorToken.Text;
var leftType = _semanticModel.GetTypeInfo(node.Left).Type;
var rightType = _semanticModel.GetTypeInfo(node.Right).Type;
var perTypeOperators = Constants.PER_TYPE_BINARY_OPERATOR_MAP;
if (
(leftType != null && perTypeOperators.Any(pair => pair.Key.Contains(leftType.Name)))
|| (rightType != null && perTypeOperators.Any(pair => pair.Key.Contains(rightType.Name)))
)
{
var typeList = perTypeOperators.FirstOrDefault(pair => pair.Key.Contains(leftType!.Name) || pair.Key.Contains(rightType!.Name)).Key;
var operatorMap = perTypeOperators[typeList];
if (operatorText == operatorMap.Item1)
{
operatorText = operatorMap.Item2;
}
}
if (Constants.IGNORED_BINARY_OPERATORS.Contains(operatorText))
{
Visit(node.Left);
return;
}
var mappedOperator = Utility.GetMappedOperator(operatorText);
switch (mappedOperator)
{
case "??":
Write("if ");
Visit(node.Left);
Write(" == nil then ");
Visit(node.Right);
Write(" else ");
Visit(node.Left);
return;
}
var bit32Method = Utility.GetBit32MethodName(mappedOperator);
if (bit32Method != mappedOperator)
{
Write($"bit32.{bit32Method}(");
Visit(node.Left);
Write(", ");
Visit(node.Right);
Write(")");
if ((leftType == null || rightType == null) || (!Constants.UNSUPPORTED_BITWISE_TYPES.Contains(leftType.Name) && !Constants.UNSUPPORTED_BITWISE_TYPES.Contains(rightType.Name)))
return;
Logger.CodegenWarning(node, "Using 128/64 bit integers when performing binary operations on Luau may result in undefined behaviour.");
WriteLine();
Write("--> rbxcsc warn: long/Int64 or Int128/UInt128 bit operations may lead to undefined behaviour (above this warning).");
return;
}
Visit(node.Left);
Write($" {mappedOperator} ");
Visit(node.Right);
}
public override void VisitTupleExpression(TupleExpressionSyntax node)
{
WriteListTable(node.Arguments.Select(arg => arg.Expression).ToList());
}
public override void VisitCollectionExpression(CollectionExpressionSyntax node)
{
Write('{');
foreach (var element in node.Elements)
{
var children = element.ChildNodes();
foreach (var child in children)
{
Visit(child);
}
if (element != node.Elements.Last())
{
Write(", ");
}
}
Write('}');
}
public override void VisitArrayCreationExpression(ArrayCreationExpressionSyntax node)
{
Visit(node.Initializer);
}
public override void VisitImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node)
{
Visit(node.Initializer);
}
public override void VisitInitializerExpression(InitializerExpressionSyntax node)
{
WriteListTable(node.Expressions.ToList());
}
public override void VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node)
{
if (node.NameEquals != null)
{
Write(GetName(node.NameEquals));
Write(" = ");
}
Visit(node.Expression);
}
public override void VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node)
{
WriteLine('{');
_indent++;
foreach (var initializer in node.Initializers)
{
Visit(initializer);
if (initializer != node.Initializers.Last())
{
Write(", ");
}
WriteLine();
}
_indent--;
WriteLine('}');
}
public override void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node)
{
Visit(node.Type);
Write(".new");
Visit(node.ArgumentList);
}
public override void VisitUsingDirective(UsingDirectiveSyntax node)
{
// do nothing (for now)
}
public override void VisitContinueStatement(ContinueStatementSyntax node)
{
Write("continue");
}
public override void VisitReturnStatement(ReturnStatementSyntax node)
{
Write("return ");
Visit(node.Expression);
WriteLine();
}
public override void VisitExpressionStatement(ExpressionStatementSyntax node)
{
base.VisitExpressionStatement(node);
WriteLine();
}
public override void VisitArgumentList(ArgumentListSyntax node)
{
Write('(');
foreach (var argument in node.Arguments)
{
Visit(argument);
if (argument != node.Arguments.Last())
{
Write(", ");
}
}
Write(')');
}
public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
if (node.Expression is MemberAccessExpressionSyntax memberAccess)
{
var objectType = _semanticModel.GetTypeInfo(memberAccess.Expression).Type;
var name = GetName(memberAccess.Name);
switch (name)
{
case "ToString":
Write("tostring(");
Visit(memberAccess.Expression);
Write(')');
return;
case "Equals":
Visit(memberAccess.Expression);
Write(" == ");
var value = node.ArgumentList.Arguments.FirstOrDefault();
if (value != null)
{
Visit(value);
}
else
{
Write("nil");
}
return;
case "Length":
if (objectType == null || !Constants.LENGTH_READABLE_TYPES.Contains(objectType.Name)) return;
Write('#');
Visit(memberAccess.Expression);
return;
case "ToLower":
case "ToUpper":
case "Replace":
case "Split":
if (objectType == null || objectType.Name.ToLower() != "string") return;
Write('(');
Visit(memberAccess.Expression);
Write(')');
Write(':');
var methodName = GetName(memberAccess.Name);
var mappedMethodName = Constants.MAPPED_STRING_METHODS.GetValueOrDefault(methodName, methodName);
Write(mappedMethodName);
Visit(node.ArgumentList);
return;
case "Create":
{
if (objectType == null || objectType.Name != "Instance") break;
var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
if (symbol is IMethodSymbol methodSymbol)
{
if (!methodSymbol.IsGenericMethod)
Logger.CompilerError("Attempt to macro Instance.Create<T>() but it is not generic");
var arguments = node.ArgumentList.Arguments;
var instanceType = methodSymbol.TypeArguments.First();
Visit(memberAccess.Expression);
Write($".new(\"{instanceType.Name}\"");
if (arguments.Count > 0)
{
Write(", ");
foreach (var argument in arguments)
{
Visit(argument.Expression);
if (argument != arguments.Last())
{
Write(", ");
}
}
}
Write(')');
return;
}
break;
}
case "GetService":
case "FindFirstChildOfClass":
case "FindFirstChildWhichIsA":
case "FindFirstAncestorOfClass":
case "FindFirstAncestorWhichIsA":
case "IsA":
{
if (objectType == null) return;
var superclasses = objectType.AllInterfaces.ToList();
if (objectType.Name != "Instance" && !superclasses.Select(@interface => @interface.Name).Contains("Instance")) return;
var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
var methodSymbol = (IMethodSymbol)symbol!;
if (!methodSymbol.IsGenericMethod)
Logger.CompilerError($"Attempt to macro {objectType.Name}.{name}<T>() but it is not generic");
var arguments = node.ArgumentList.Arguments;
var instanceType = methodSymbol.TypeArguments.First();
Visit(memberAccess.Expression);
Write($":{name}(\"{instanceType.Name}\"");
if (arguments.Count > 0)
{
Write(", ");
foreach (var argument in arguments)
{
Visit(argument.Expression);
if (argument != arguments.Last())
{
Write(", ");
}
}
}
else
{
Write(')');
}
return;
}
}
}
else if (node.Expression is IdentifierNameSyntax || node.Expression is GenericNameSyntax)
{
var name = GetName(node.Expression);
switch (name)
{
case "TypeOf":
Write("typeof");
Visit(node.ArgumentList);
return;
case "ToNumber":
case "ToFloat":
case "ToDouble":
case "ToInt":
case "ToUInt":
case "ToShort":
case "ToUShort":
case "ToByte":
case "ToSByte":
Write("tonumber");
Visit(node.ArgumentList);
return;
case "nameof":
Write('"');
Write(_semanticModel.GetConstantValue(node).Value?.ToString() ?? $"??? nameof({node}) ???");
Write('"');
return;
}
}
Visit(node.Expression);
Visit(node.ArgumentList);
}
public override void VisitThisExpression(ThisExpressionSyntax node)
{
Write("self");
}
public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
var leftIsLiteral = node.Expression is LiteralExpressionSyntax;
var objectSymbol = _semanticModel.GetSymbolInfo(node.Expression).Symbol?.OriginalDefinition;
var objectType = _semanticModel.GetTypeInfo(node.Expression).Type;
var nameType = _semanticModel.GetSymbolInfo(node.Name).Symbol?.OriginalDefinition;
var operatorText = '.';
if (objectSymbol != null)
{
if (objectType is INamedTypeSymbol objectDefinitionSymbol && (objectDefinitionSymbol.Name == "Services" || objectDefinitionSymbol.AllInterfaces.Select(@interface => @interface.Name).Contains("Services")))
{
Write("game:GetService(\"");
Visit(node.Name);
Write("\")");
return;
}
if (nameType is IMethodSymbol methodSymbol)
{
operatorText = methodSymbol.IsStatic ? '.' : ':';
}
}
var usings = GetUsings();
var containingNamespace = objectSymbol?.ContainingNamespace;
if (objectSymbol != null && (objectSymbol.Kind == SymbolKind.Namespace || (objectSymbol.Kind == SymbolKind.NamedType && objectSymbol.IsStatic)))
{
var namespaceName = objectSymbol.ToDisplayString();
var filePathsContainingType = objectSymbol.Locations
.Where(location => location.SourceTree != null && location.SourceTree.FilePath != _tree.FilePath)
.Select(location => location.SourceTree!.FilePath);
var isNoFullQualificationType = Constants.NO_FULL_QUALIFICATION_TYPES.Contains(namespaceName) || (containingNamespace != null ? Constants.NO_FULL_QUALIFICATION_TYPES.Contains(containingNamespace.Name) : false);
var noFullQualification = isNoFullQualificationType && (objectType == null || !Constants.GLOBAL_LIBRARIES.Contains(objectType.Name));
var typeIsImported = usings.Any(usingDirective => usingDirective.Name != null && Utility.GetNamesFromNode(usingDirective).Any(name => namespaceName.StartsWith(name)));
if (noFullQualification && namespaceName != "System")
{
if (node.Expression is IdentifierNameSyntax identifier)
{
// TODO: check parent classes of parent class
var parentClass = FindFirstAncestor<ClassDeclarationSyntax>(node);
var parentClassSymbol = parentClass != null ? _semanticModel.GetDeclaredSymbol(parentClass) : null;
if (parentClass != null && SymbolEqualityComparer.Default.Equals(objectType, parentClassSymbol))
{
Write("class");
}
else
{
if (GetName(node.Name) == "Globals") return;
Visit(node.Name);
}
}
else if (node.Expression is GenericNameSyntax genericName)
{
Visit(genericName);
}
else if (node.Expression is MemberAccessExpressionSyntax memberAccess)
{
if (namespaceName == Utility.RuntimeAssemblyName && Utility.GetNamesFromNode(memberAccess.Expression).LastOrDefault() != "Globals")
{
Visit(memberAccess.Name);
Write('.');
}
Visit(node.Name);
}
else
{
throw new NotSupportedException("Unsupported node.Expression type for a NO_FULL_QUALIFICATION_TYPES member");
}
return;
}
}
if (objectSymbol?.OriginalDefinition is ILocalSymbol typeSymbol)
{
switch (typeSymbol.Type.Name)
{
case "ValueTuple":
var name = GetName(node.Name);
if (name.StartsWith("Item"))
{
var itemIndex = name.Split("Item").Last();
if (leftIsLiteral)
{
Write('(');
}
Visit(node.Expression);
if (leftIsLiteral)
{
Write(')');
}
Write($"[{itemIndex}]");
return;
}
break;
}
}
if (leftIsLiteral)
{
Write('(');
}
Visit(node.Expression);
if (leftIsLiteral)
{
Write(')');
}
Write(operatorText);
Visit(node.Name);
}
private List<UsingDirectiveSyntax> GetUsings()
{
var root = _tree.GetRoot();
var usings = root.DescendantNodes().OfType<UsingDirectiveSyntax>().ToList();
var compilationUnit = root as CompilationUnitSyntax;
if (compilationUnit != null)
{
foreach (var usingDirective in compilationUnit.Usings)
{
usings.Add(usingDirective);
}
}
return usings;
}
private void FullyQualifyMemberAccess(INamespaceSymbol? namespaceType, List<UsingDirectiveSyntax> usings)
{
if (namespaceType == null) return;
var typeIsImported = usings.Any(usingDirective => namespaceType.ContainingNamespace != null && usingDirective.Name != null && Utility.GetNamesFromNode(usingDirective).Contains(namespaceType.ContainingNamespace.Name));
Write($"CS.getAssemblyType(\"{namespaceType.Name}\").");
_flags.ShouldCallGetAssemblyType = false;
}
public override void VisitIsPatternExpression(IsPatternExpressionSyntax node)
{
var objectType = _semanticModel.GetTypeInfo(node.Expression).Type;
var superclasses = objectType == null ? [] : objectType.AllInterfaces.ToList();
var pattern = node.Pattern;
void writeTypePattern(TypeSyntax type)
{
var typeSymbol = _semanticModel.GetTypeInfo(type).Type;
var mappedType = Utility.GetMappedType(type.ToString());
HashSet<TypeKind> valueTypes = [TypeKind.Class, TypeKind.Struct, TypeKind.Enum];
var isValueType = typeSymbol != null && valueTypes.Contains(typeSymbol.TypeKind);
if (!isValueType)
{
Write('"');
}
Write(mappedType);
if (!isValueType)
{
Write('"');
}
}
(bool, Action) getPatternWriter()
{
if (pattern is TypePatternSyntax typePattern)
{
return (true, () => writeTypePattern(typePattern.Type));
}
else if (pattern is DeclarationPatternSyntax declarationPattern)
{
return (true, () => writeTypePattern(declarationPattern.Type));
}
else if (pattern is VarPatternSyntax varPattern)
{
return (true, () => Write("true"));
}
else
{
return (false, () => Visit(pattern));
}
}
var (willBeHandled, writePattern) = getPatternWriter();
if (!willBeHandled && pattern.IsKind(SyntaxKind.NotPattern))
{
Write("not ");
}
Write("CS.is(");
Visit(node.Expression);
Write(", ");
writePattern();
Write(')');
}
public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
{
Visit(node.Designation);
WriteTypeAnnotation(node.Type);
}
public override void VisitDiscardDesignation(DiscardDesignationSyntax node)
{
// do nothing (discard)
}
public override void VisitSingleVariableDesignation(SingleVariableDesignationSyntax node)
{
Write($"local {GetName(node)}");
}
public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
Visit(node.Declaration);
}
public override void VisitVariableDeclaration(VariableDeclarationSyntax node)
{
foreach (var declarator in node.Variables)
{
Visit(declarator);
}
}
public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
{
var localDeclaration = FindFirstAncestor<LocalDeclarationStatementSyntax>(node);
var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(node);
if (
localDeclaration != null
&& classDeclaration != null
&& !IsDescendantOf<MethodDeclarationSyntax>(node)
&& !IsDescendantOf<PropertyDeclarationSyntax>(node))
{
var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(localDeclaration.Modifiers, SyntaxKind.StaticKeyword);
Write(isStatic ? "class" : "self");
Write(".");
}
else
{
Write("local ");
}
Write($"{GetName(node)}");
var parent = node.Parent;
if (parent is VariableDeclarationSyntax declaration)
{
WriteTypeAnnotation(declaration.Type);
}
if (node.Initializer != null)
{
Write(" = ");
Visit(node.Initializer);
if (node.Initializer.Value.IsKind(SyntaxKind.IsPatternExpression))
{
WriteLine();
}
WritePatternDeclarations(node.Initializer.Value);
}
WriteLine();
}
public override void VisitAssignmentExpression(AssignmentExpressionSyntax node)
{
Visit(node.Left);
Write(" = ");
Visit(node.Right);
if (node.Right.IsKind(SyntaxKind.IsPatternExpression))
{
WriteLine();
}
WritePatternDeclarations(node.Right);
}
public override void VisitGenericName(GenericNameSyntax node)
{
WriteName(node, node.Identifier);
}
public override void VisitIdentifierName(IdentifierNameSyntax node)
{
WriteName(node, node.Identifier);
}
public override void VisitInterpolatedStringText(InterpolatedStringTextSyntax node)
{
Write(node.TextToken.Text);
}
public override void VisitInterpolation(InterpolationSyntax node)
{
Write('{');
Visit(node.Expression);
Write('}');
}
public override void VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node)
{
Write('`');
foreach (var content in node.Contents)
{
Visit(content);
}
Write('`');
}
public override void VisitLiteralExpression(LiteralExpressionSyntax node)
{
switch (node.Kind())
{
case SyntaxKind.StringLiteralExpression:
case SyntaxKind.Utf8StringLiteralExpression:
case SyntaxKind.CharacterLiteralExpression:
Write($"\"{node.Token.ValueText}\"");
break;
case SyntaxKind.TrueLiteralExpression:
case SyntaxKind.FalseLiteralExpression:
case SyntaxKind.NumericLiteralExpression:
Write(node.Token.ValueText);
break;
case SyntaxKind.DefaultLiteralExpression:
var typeSymbol = _semanticModel.GetTypeInfo(node).Type;
if (typeSymbol == null) break;
if (Constants.INTEGER_TYPES.Contains(typeSymbol.Name) || Constants.DECIMAL_TYPES.Contains(typeSymbol.Name))
{
Write("0");
break;
}
switch (typeSymbol.Name)
{
case "char":
case "Char":
case "string":
case "String":
Write("\"\"");
break;
case "bool":
case "Boolean":
Write("false");
break;
default:
Write("nil");
break;
}
break;
case SyntaxKind.NullLiteralExpression:
Write("nil");
break;
}
base.VisitLiteralExpression(node);
}
public override void VisitParameter(ParameterSyntax node)
{
if (node.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.ParamsKeyword)))
{
Write("...");
if (node.Type != null)
WriteTypeAnnotation(node.Type);
return;
}
Write(GetName(node));
if (node.Type != null)
{
WriteTypeAnnotation(node.Type);
}
}
public override void VisitParameterList(ParameterListSyntax node)
{
var callable = node.Parent;
Write('(');
{
if (callable is MethodDeclarationSyntax method && !HasSyntax(method.Modifiers, SyntaxKind.StaticKeyword))
{
Write('_');
if (node.Parameters.Count > 0)
{
Write(", ");
}
}
}
ParameterSyntax? vaargParameter = null;
foreach (var parameter in node.Parameters)
{
Visit(parameter);
if (parameter != node.Parameters.Last())
{
Write(", ");
}
else
{
if (parameter.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.ParamsKeyword))) vaargParameter = parameter;
}
}
Write(')');
switch (callable)
{
case ConstructorDeclarationSyntax constructor:
WriteLine($": {GetName(constructor)}");
break;
case MethodDeclarationSyntax method:
WriteTypeAnnotation(method.ReturnType, true);
break;
case LocalFunctionStatementSyntax localFunction:
WriteTypeAnnotation(localFunction.ReturnType, true);
break;
default:
WriteLine();
break;
}
_indent++;
if (vaargParameter != null)
{
WriteLine("--> rbxcsc: vararg parameter conversion");
Write($"local {GetName(vaargParameter)} = {{ ... }}");
WriteLine();
}
foreach (var parameter in node.Parameters)
{
if (parameter.Default == null) continue;
var name = GetName(parameter);
Write(name);
Write(" = ");
Write($"if {name} == nil then ");
Visit(parameter.Default);
WriteLine($" else {name}");
}
_indent--;
}
public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
{
var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
var allNames = Utility.GetNamesFromNode(node);
var firstName = allNames.First();
allNames.Remove(firstName);
var nativeAttribute = _config.EmitNativeAttributeOnClassOrNamespaceCallbacks ? "@native " : "";
WriteLine($"{(isWithinNamespace ? "namespace:" : "CS.")}namespace(\"{firstName}\", {nativeAttribute}function(namespace: CS.Namespace)");
_indent++;
if (allNames.Count > 0)
{
foreach (var name in allNames)
{
WriteLine($"namespace:namespace(\"{name}\", {nativeAttribute}function(namespace: CS.Namespace)");
_indent++;
foreach (var member in node.Members)
{
Visit(member);
}
_indent--;
WriteLine("end)");
}
}
else
{
foreach (var member in node.Members)
{
Visit(member);
}
}
_indent--;
WriteLine("end)");
WriteLine();
}
public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
{
var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
Write($"CS.enum(\"{GetName(node)}\", {{");
if (node.Members.Count > 0)
{
WriteLine();
_indent++;
var firstValue = node.Members.FirstOrDefault()?.EqualsValue?.Value;
var lastIndex = firstValue != null ? (firstValue as LiteralExpressionSyntax)?.Token.Value as int? ?? -1 : -1;
foreach (var enumMember in node.Members)
{
Write(GetName(enumMember));
Write(" = ");
if (enumMember.EqualsValue != null)
{
Visit(enumMember.EqualsValue);
lastIndex = (int)((LiteralExpressionSyntax)enumMember.EqualsValue.Value).Token.Value!;
}
else
{
var index = (enumMember.EqualsValue?.Value is LiteralExpressionSyntax literal ? literal.Token.Value as int? : null) ?? lastIndex + 1;
lastIndex = index;
Write(index.ToString());
}
WriteLine(enumMember != node.Members.Last() ? ", " : "");
}
_indent--;
}
WriteLine($"}}, {(isWithinNamespace ? "namespace" : "nil")})");
}
public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
{
foreach (var member in node.Members)
{
if (HasSyntax(member.Modifiers, SyntaxKind.VirtualKeyword))
{
Logger.UnsupportedError(node, "Virtual methods on interfaces");
}
}
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
if (HasSyntax(node.Modifiers, SyntaxKind.AbstractKeyword))
{
Logger.UnsupportedError(node, "Abstract classes");
return;
}
if (HasSyntax(node.Modifiers, SyntaxKind.PartialKeyword))
{
Logger.UnsupportedError(node, "Partial classes");
return;
}
var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(node);
var isStatic = HasSyntax(node.Modifiers, SyntaxKind.StaticKeyword);
var className = GetName(node);
var nativeAttribute = _config.EmitNativeAttributeOnClassOrNamespaceCallbacks ? "@native " : "";
WriteLine($"{(isWithinNamespace ? "namespace:" : "CS.")}class(\"{className}\", {nativeAttribute}function(namespace: CS.Namespace)");
_indent++;
Write($"local class = CS.classDef(\"{GetName(node)}\", ");
Write(isWithinNamespace ? "namespace" : "nil");
// TODO: check if superclass or mixin
var ancestorIdentifiers = (node.BaseList?.Types ?? []).Select(ancestor =>
{
var typeSymbol = _semanticModel.GetTypeInfo(ancestor.Type).Type;
return $"\"{Regex.Replace(typeSymbol?.ToString() ?? ancestor.Type.ToString(), @"<[^>]*>", "")}\"";
});
if (ancestorIdentifiers.Count() > 0)
{
Write(", ");
}
Write(string.Join(", ", ancestorIdentifiers));
WriteLine(')');
WriteLine();
InitializeFields(
node.Members
.OfType<FieldDeclarationSyntax>()
.Where(member => isStatic || HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword))
);
InitializeProperties(
node.Members
.OfType<PropertyDeclarationSyntax>()
.Where(member => isStatic || HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword))
);
var constructors = node.Members.OfType<ConstructorDeclarationSyntax>().ToList();
constructors.Sort((a, b) => a.ParameterList.Parameters.Count - b.ParameterList.Parameters.Count);
var constructor = constructors.FirstOrDefault();
if (!isStatic)
{
if (constructor == null)
{
CreateDefaultConstructor(node);
}
else
{
VisitConstructorDeclaration(constructor);
}
}
var methods = node.Members.OfType<MethodDeclarationSyntax>();
var staticMethods = methods.Where(method => HasSyntax(method.Modifiers, SyntaxKind.StaticKeyword));
foreach (var method in staticMethods)
{
Visit(method);
}
var isEntryPointClass = GetName(node) == _config.CSharpOptions.EntryPointName;
if (isEntryPointClass)
{
var filePath = Path.TrimEndingDirectorySeparator(_tree.FilePath);
var isClientFile = filePath.EndsWith(".client.cs");
if ((_flags.ClientEntryPointDefined && isClientFile) || (_flags.ServerEntryPointDefined && !isClientFile))
{
Logger.CodegenError(node, $"No more than one main method can be defined on the {(isClientFile ? "client" : "server")}.");
}
if (isClientFile)
{
_flags.ClientEntryPointDefined = true;
}
else
{
_flags.ServerEntryPointDefined = true;
}
var mainMethod = methods.FirstOrDefault(method => GetName(method) == _config.CSharpOptions.MainMethodName);
if (mainMethod == null)
{
Logger.CodegenError(node.Identifier, $"No main method \"{_config.CSharpOptions.MainMethodName}\" found in entry point class");
return;
}
if (!HasSyntax(mainMethod.Modifiers, SyntaxKind.StaticKeyword))
{
Logger.CodegenError(node.Identifier, $"Main method must be static.");
}
WriteLine();
WriteLine("if namespace == nil then");
_indent++;
WriteLine($"class.{_config.CSharpOptions.MainMethodName}()");
_indent--;
WriteLine("else");
_indent++;
WriteLine($"namespace[\"$onLoaded\"](namespace, class.{_config.CSharpOptions.MainMethodName})");
_indent--;
Write("end");
}
WriteLine();
WriteLine($"return class");
_indent--;
WriteLine("end)");
}
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
if (HasSyntax(node.Modifiers, SyntaxKind.ExternKeyword))
{
Logger.UnsupportedError(node, "Extern methods", useYet: false);
}
foreach (var attributeList in node.AttributeLists)
{
Visit(attributeList);
}
var isStatic = HasSyntax(node.Modifiers, SyntaxKind.StaticKeyword);
var isMetamethod = Constants.METAMETHODS.Contains(GetName(node));
var objectName = "self";
if (isStatic)
{
objectName = "class";
}
else if (isMetamethod)
{
objectName = "mt";
}
var name = GetName(node);
Write($"function {objectName}.{name}");
Visit(node.ParameterList);
_indent++;
Visit(node.Body);
WriteDefaultReturn(node.Body);
_indent--;
WriteLine("end");
}
public override void VisitBaseExpression(BaseExpressionSyntax node)
{
Write("self[\"$superclass\"]");
}
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
// TODO: struct support?
foreach (var attributeList in node.AttributeLists)
{
Visit(attributeList);
}
Write($"function class.new");
Visit(node.ParameterList);
_indent++;
VisitConstructorBody(FindFirstAncestor<ClassDeclarationSyntax>(node)!, node.Body, node.Initializer?.ArgumentList);
_indent--;
WriteLine("end");
}
private void VisitConstructorBody(ClassDeclarationSyntax parentClass, BlockSyntax? block, ArgumentListSyntax? initializerArguments)
{
var isWithinNamespace = IsDescendantOf<NamespaceDeclarationSyntax>(parentClass);
WriteLine("local mt = {}");
Write("local self = CS.classInstance(class, mt");
if (isWithinNamespace)
{
Write(", namespace");
}
WriteLine(')'); // TODO: (when typedefs are generated for classes) $") :: {GetName(parentClass)}"
WriteLine();
if (initializerArguments != null)
{
Write("self[\"$base\"]");
Visit(initializerArguments);
}
var isNotStatic = (MemberDeclarationSyntax member) => !HasSyntax(parentClass.Modifiers, SyntaxKind.StaticKeyword) && !HasSyntax(member.Modifiers, SyntaxKind.StaticKeyword);
var isNotAbstract = (MemberDeclarationSyntax member) => !HasSyntax(member.Modifiers, SyntaxKind.AbstractKeyword);
var nonStaticFields = parentClass.Members
.Where(isNotStatic)
.Where(isNotAbstract)
.OfType<FieldDeclarationSyntax>();
var nonStaticProperties = parentClass.Members
.Where(isNotStatic)
.Where(isNotAbstract)
.OfType<PropertyDeclarationSyntax>();
var nonStaticMethods = parentClass.Members
.Where(isNotStatic)
.Where(isNotAbstract)
.OfType<MethodDeclarationSyntax>();
InitializeFields(nonStaticFields);
InitializeProperties(nonStaticProperties);
if (block != null)
{
WriteLine();
Visit(block);
}
if (nonStaticMethods.Count() > 0)
{
WriteLine();
}
foreach (var method in nonStaticMethods)
{
Visit(method);
}
WriteLine();
WriteLine("return self");
}
private void CreateDefaultConstructor(ClassDeclarationSyntax node)
{
WriteLine($"function class.new()");
_indent++;
VisitConstructorBody(node, null, null);
_indent--;
WriteLine("end");
}
private void InitializeFields(IEnumerable<FieldDeclarationSyntax> fields)
{
foreach (var field in fields)
{
var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(field)!;
var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(field.Modifiers, SyntaxKind.StaticKeyword);
foreach (var declarator in field.Declaration.Variables)
{
if (declarator.Initializer == null) continue;
Write($"{(isStatic ? "class" : "self")}.{GetName(declarator)} = ");
Visit(declarator.Initializer);
WriteLine();
}
}
}
private void InitializeProperties(IEnumerable<PropertyDeclarationSyntax> properties)
{
foreach (var property in properties)
{
if (property.Initializer == null) continue;
var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(property)!;
var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(property.Modifiers, SyntaxKind.StaticKeyword);
Write($"{(isStatic ? "class" : "self")}.{GetName(property)} = ");
Visit(property.Initializer);
WriteLine();
}
}
private void WriteName(SyntaxNode node, SyntaxToken identifier)
{
var identifierText = identifier.ValueText;
var originalIdentifierName = identifier.Text;
if (identifierText == "var") return;
if (Constants.LUAU_KEYWORDS.Contains(identifierText))
{
Logger.CodegenError(node, $"Using reserved Luau keywords as identifier names is unsupported!");
}
var isWithinClass = IsDescendantOf<ClassDeclarationSyntax>(node);
var prefix = "";
if (isWithinClass)
{
// Check the fields in classes that this node is a descendant of for the identifier name
var ancestorClasses = GetAncestors<ClassDeclarationSyntax>(node);
for (int i = 0; i < ancestorClasses.Length; i++)
{
var ancestorClass = ancestorClasses[i];
var fields = ancestorClass.Members.OfType<FieldDeclarationSyntax>();
foreach (var field in fields)
{
var classDeclaration = FindFirstAncestor<ClassDeclarationSyntax>(field)!;
var isStatic = HasSyntax(classDeclaration.Modifiers, SyntaxKind.StaticKeyword) || HasSyntax(field.Modifiers, SyntaxKind.StaticKeyword);
foreach (var declarator in field.Declaration.Variables)
{
var name = GetName(declarator);
if (name != identifierText) continue;
prefix = (isStatic ? "class" : "self") + '.';
}
}
}
}
if (prefix == "")
{
var symbol = _semanticModel.GetSymbolInfo(node).Symbol;
var parentNamespace = FindFirstAncestor<NamespaceDeclarationSyntax>(node);
var parentNamespaceSymbol = parentNamespace != null ? _semanticModel.GetDeclaredSymbol(parentNamespace) : null;
var pluginClassesNamespace = _runtimeLibNamespace.GetNamespaceMembers().FirstOrDefault(ns => ns.Name == "PluginClasses");
var runtimeNamespaceIncludesIdentifier = symbol != null ? (
IsDescendantOfNamespaceSymbol(symbol, _runtimeLibNamespace)
|| (pluginClassesNamespace != null && IsDescendantOfNamespaceSymbol(symbol, pluginClassesNamespace))
) : false;
HashSet<SyntaxKind> fullyQualifiedParentKinds = [SyntaxKind.SimpleMemberAccessExpression, SyntaxKind.ObjectCreationExpression];
if (
symbol != null
&& symbol is ITypeSymbol typeSymbol
&& node.Parent != null
&& fullyQualifiedParentKinds.Contains(node.Parent.Kind())
&& typeSymbol.ContainingNamespace != null
&& (parentNamespace != null ? Utility.GetNamesFromNode(parentNamespace.Name).LastOrDefault() != typeSymbol.ContainingNamespace.Name : true)
&& !Constants.NO_FULL_QUALIFICATION_TYPES.Contains(typeSymbol.ContainingNamespace.Name)
)
{
var usings = GetUsings();
FullyQualifyMemberAccess(typeSymbol.ContainingNamespace, usings);
}
var parentAccessExpression = FindFirstAncestor<MemberAccessExpressionSyntax>(node);
var isLeftSide = parentAccessExpression == null ? true : node == parentAccessExpression.Expression;
var parentBlocks = GetAncestors<SyntaxNode>(node);
var localScopeIncludesIdentifier = parentBlocks.Any(block =>
{
var descendants = block.DescendantNodes();
var localFunctions = descendants.OfType<LocalFunctionStatementSyntax>();
var variableDesignations = descendants.OfType<VariableDesignationSyntax>();
var variableDeclarators = descendants.OfType<VariableDeclaratorSyntax>();
var forEachStatements = descendants.OfType<ForEachStatementSyntax>();
var forStatements = descendants.OfType<ForStatementSyntax>();
var parameters = descendants.OfType<ParameterSyntax>();
var catchDeclarations = descendants.OfType<CatchDeclarationSyntax>();
var checkNamePredicate = (SyntaxNode node) => TryGetName(node) == identifierText;
return localFunctions.Any(checkNamePredicate)
|| variableDesignations.Any(checkNamePredicate)
|| variableDeclarators.Any(checkNamePredicate)
|| parameters.Any(checkNamePredicate)
|| catchDeclarations.Any(checkNamePredicate)
|| forEachStatements.Any(checkNamePredicate)
|| forStatements.Any(forStatement => forStatement.Initializers.Count() > 0);
});
if (isLeftSide && !localScopeIncludesIdentifier && !runtimeNamespaceIncludesIdentifier)
{
var namespaceSymbol = parentNamespace != null ? _semanticModel.GetDeclaredSymbol(parentNamespace) : null;
var namespaceIncludesIdentifier = namespaceSymbol != null && Utility.FindMember(namespaceSymbol, originalIdentifierName) != null;
var parentClass = FindFirstAncestor<ClassDeclarationSyntax>(node);
var classSymbol = parentClass != null ? _semanticModel.GetDeclaredSymbol(parentClass) : null;
var classMemberSymbol = classSymbol != null ? Utility.FindMemberDeep(classSymbol, originalIdentifierName) : null;
if (namespaceIncludesIdentifier)
{
Write($"namespace[\"$getMember\"](namespace, \"{identifierText}\")");
}
else if (classMemberSymbol != null)
{
Write($"{(classMemberSymbol.IsStatic ? "class" : "self")}.{identifierText}");
}
else
{
if (_flags.ShouldCallGetAssemblyType)
{
Write($"CS.getAssemblyType(\"{identifierText}\")");
}
else
{
Write(identifierText);
_flags.ShouldCallGetAssemblyType = true;
}
}
}
else
{
Write(identifierText);
}
}
else
{
Write(prefix + identifierText);
}
}
private void WriteListTable(List<ExpressionSyntax> expressions)
{
Write('{');
foreach (var expression in expressions)
{
Visit(expression);
if (expression != expressions.Last())
{
Write(", ");
}
}
Write('}');
}
private void WriteDefaultReturn(BlockSyntax? block)
{
if (block != null && !block.Statements.Any(stmt => stmt.IsKind(SyntaxKind.ReturnStatement)))
{
WriteLine("return nil :: any");
}
}
private void WritePatternDeclarations(SyntaxNode node)
{
if (node is IsPatternExpressionSyntax isPattern)
{
void writeInitializer()
{
Write(" = ");
Visit(isPattern.Expression);
WriteLine();
}
if (isPattern.Pattern is DeclarationPatternSyntax declarationPattern)
{
Visit(declarationPattern.Designation);
writeInitializer();
}
else if (isPattern.Pattern is VarPatternSyntax varPattern)
{
Visit(varPattern.Designation);
writeInitializer();
}
}
}
private void WriteTypeAnnotation(TypeSyntax type, bool isReturnType = false)
{
if (!type.IsVar)
{
var mappedType = Utility.GetMappedType(type.ToString());
Write($": {mappedType}");
if (isReturnType)
{
WriteLine();
}
}
}
private void WriteRequire(string path)
{
WriteLine($"require({path})");
}
private void WriteLine()
{
WriteLine("");
}
private void WriteLine(char text)
{
WriteLine(text.ToString());
}
private void WriteLine(string text)
{
if (text == null)
{
_output.AppendLine();
return;
}
WriteTab();
_output.AppendLine(text);
}
private void Write(char text)
{
Write(text.ToString());
}
private void Write(string text)
{
WriteTab();
_output.Append(text);
}
private void WriteTab()
{
_output.Append(MatchLastCharacter('\n') ? GetTabString() : "");
}
private string GetTabString()
{
return string.Concat(Enumerable.Repeat(" ", _indentSize * _indent));
}
private void RemoveLastCharacters(int amount)
{
_output.Remove(_output.Length - amount, amount);
}
private bool HasSyntax(SyntaxTokenList tokens, SyntaxKind syntax)
{
return tokens.Any(token => token.IsKind(syntax));
}
private bool IsDescendantOfNamespaceSymbol(ISymbol symbol, INamespaceSymbol ancestor)
{
var namespaceSymbol = symbol.ContainingNamespace;
while (namespaceSymbol != null)
{
if (SymbolEqualityComparer.Default.Equals(namespaceSymbol, ancestor))
{
return true;
}
namespaceSymbol = namespaceSymbol.ContainingNamespace;
}
return false;
}
private bool IsDescendantOf<T>(SyntaxNode node) where T : SyntaxNode
{
return FindFirstAncestor<T>(node) != null;
}
private T? FindFirstAncestor<T>(SyntaxNode node) where T : SyntaxNode
{
return GetAncestors<T>(node).FirstOrDefault();
}
private T[] GetAncestors<T>(SyntaxNode node) where T : SyntaxNode
{
return node.Ancestors().OfType<T>().ToArray();
}
private string? TryGetName(SyntaxNode node)
{
return Utility.GetNamesFromNode(node).FirstOrDefault();
}
private string GetName(SyntaxNode node)
{
return Utility.GetNamesFromNode(node).First();
}
private bool MatchLastCharacter(char character)
{
if (_output.Length == 0) return false;
return _output[_output.Length - 1] == character;
}
}
}