diff --git a/src/Draco.Chr/Constraints/PropagationHistory.cs b/src/Draco.Chr/Constraints/PropagationHistory.cs index ee089c387..b4faee3f7 100644 --- a/src/Draco.Chr/Constraints/PropagationHistory.cs +++ b/src/Draco.Chr/Constraints/PropagationHistory.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Draco.Chr.Rules; diff --git a/src/Draco.Compiler.Cli/Program.cs b/src/Draco.Compiler.Cli/Program.cs index 68671e1cb..c31dae52a 100644 --- a/src/Draco.Compiler.Cli/Program.cs +++ b/src/Draco.Compiler.Cli/Program.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.CommandLine; -using System.CommandLine.Parsing; using System.IO; using System.Linq; using Draco.Compiler.Api; diff --git a/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs b/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs index 23186003c..e55fdc014 100644 --- a/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs +++ b/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs @@ -999,4 +999,52 @@ func outer(x: int32): int32 { Assert.Equal(3, result); } + + [Fact] + public void ClassHelloWorld() + { + var assembly = CompileToAssembly(""" + import System.Console; + + class Foo { + func bar() { + WriteLine("Hello, World!"); + } + } + """); + + var stringWriter = new StringWriter(); + _ = Invoke(assembly: assembly, stdout: stringWriter, methodName: "bar", moduleName: "FreeFunctions.Foo"); + + Assert.Equal($"Hello, World!{Environment.NewLine}", stringWriter.ToString(), ignoreLineEndingDifferences: true); + } + + [Fact] + public void InstanceField() + { + var assembly = CompileToAssembly(""" + import System.Console; + + func bar(): int32 { + var foo = Foo(); + foo.increment(); + return foo.get(); + } + + class Foo { + field var i: int32; + + public func increment(this) { + this.i += 1; + } + + public func get(this): int32 { + return this.i; + } + } + """); + + var value = Invoke(assembly: assembly, methodName: "bar"); + Assert.Equal(1, value); + } } diff --git a/src/Draco.Compiler.Tests/Services/CodeCompletionTests.cs b/src/Draco.Compiler.Tests/Services/CodeCompletionTests.cs index 3be9967ea..bbe202ddb 100644 --- a/src/Draco.Compiler.Tests/Services/CodeCompletionTests.cs +++ b/src/Draco.Compiler.Tests/Services/CodeCompletionTests.cs @@ -38,13 +38,13 @@ public void TestLocalCompletionGlobalVariable() { // TODO: Can we get rid of all this filtering by filtering in the completion service? var completions = GetCompletionWords(""" - val global = 5; + val globalVar = 5; func main(){ var local = gl| } """); - AssertCompletions(completions, "global"); + AssertCompletions(completions, "globalVar"); } [Fact] @@ -78,11 +78,11 @@ func main(){ public void TestGlobalCompletionGlobalVariable() { var completions = GetCompletionWords(""" - val global = 5; + val globalVar = 5; val x = gl| """); - AssertCompletions(completions, "global"); + AssertCompletions(completions, "globalVar"); } [Fact] diff --git a/src/Draco.Compiler.Tests/Syntax/ParserTests.cs b/src/Draco.Compiler.Tests/Syntax/ParserTests.cs index 54938a974..4c13aa6fd 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParserTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParserTests.cs @@ -1957,7 +1957,7 @@ func foo(@Attr2("this is a parameter") x: int32) {} this.T(TokenKind.ParenOpen); this.N>(); { - this.N(); + this.N(); { this.N>(); this.N(); @@ -2088,7 +2088,7 @@ func foo(x: List<>) {} this.T(TokenKind.ParenOpen); this.N>(); { - this.N(); + this.N(); { this.T(TokenKind.Identifier, "x"); this.T(TokenKind.Colon); @@ -2113,4 +2113,37 @@ func foo(x: List<>) {} } } } + + [Fact] + public void TestClassDeclaration() + { + this.ParseDeclaration(""" + class Foo { + } + """); + + this.N(); + { + this.T(TokenKind.KeywordClass); + this.T(TokenKind.Identifier, "Foo"); + this.N(); + } + } + + [Fact] + public void TestValueClassDeclaration() + { + this.ParseDeclaration(""" + value class Foo { + } + """); + + this.N(); + { + this.T(TokenKind.KeywordValue); + this.T(TokenKind.KeywordClass); + this.T(TokenKind.Identifier, "Foo"); + this.N(); + } + } } diff --git a/src/Draco.Compiler/Api/Semantics/SemanticModel.cs b/src/Draco.Compiler/Api/Semantics/SemanticModel.cs index 0488ea5fc..62f39ceb8 100644 --- a/src/Draco.Compiler/Api/Semantics/SemanticModel.cs +++ b/src/Draco.Compiler/Api/Semantics/SemanticModel.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -194,6 +193,16 @@ public ImmutableArray GetAllAccessibleSymbols(SyntaxNode? node) .SingleOrDefault(sym => sym.DeclaringSyntax == syntax); return symbol; } + case SourceClassSymbol classSymbol: + { + // Could be the class itself + if (classSymbol.DeclaringSyntax == syntax) return containingSymbol; + + // Search for the corresponding syntax + var symbol = classSymbol.Members + .SingleOrDefault(sym => sym.DeclaringSyntax == syntax); + return symbol; + } default: return null; } diff --git a/src/Draco.Compiler/Api/Services/CodeCompletion/CompletionService.cs b/src/Draco.Compiler/Api/Services/CodeCompletion/CompletionService.cs index 318d3c1f2..38b9c3ed1 100644 --- a/src/Draco.Compiler/Api/Services/CodeCompletion/CompletionService.cs +++ b/src/Draco.Compiler/Api/Services/CodeCompletion/CompletionService.cs @@ -87,7 +87,7 @@ public ImmutableArray GetCompletions(SemanticModel semanticModel // Type expression NameTypeSyntax => CompletionContext.Type, // Parameter name declaration - ParameterSyntax => CompletionContext.None, + NormalParameterSyntax => CompletionContext.None, // Global declaration UnexpectedDeclarationSyntax => CompletionContext.Declaration, // Declaring identifier diff --git a/src/Draco.Compiler/Api/Syntax/Extensions/SyntaxNodeTraversalExtensions.cs b/src/Draco.Compiler/Api/Syntax/Extensions/SyntaxNodeTraversalExtensions.cs index f8dec1fd9..e85657c11 100644 --- a/src/Draco.Compiler/Api/Syntax/Extensions/SyntaxNodeTraversalExtensions.cs +++ b/src/Draco.Compiler/Api/Syntax/Extensions/SyntaxNodeTraversalExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index 593758a7b..0db8a485b 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -101,9 +101,9 @@ public static SeparatedSyntaxList ParameterList(params Paramete public static ParameterSyntax Parameter(string name, TypeSyntax type) => Parameter([], name, type); public static ParameterSyntax Parameter(IEnumerable attributes, string name, TypeSyntax type) => - Parameter(SyntaxList(attributes), null, Identifier(name), Colon, type); + NormalParameter(SyntaxList(attributes), null, Identifier(name), Colon, type); public static ParameterSyntax VariadicParameter(string name, TypeSyntax type) => - Parameter(SyntaxList(), Ellipsis, Identifier(name), Colon, type); + NormalParameter(SyntaxList(), Ellipsis, Identifier(name), Colon, type); public static SeparatedSyntaxList GenericParameterList(IEnumerable parameters) => SeparatedSyntaxList(Comma, parameters); @@ -173,57 +173,118 @@ public static VariableDeclarationSyntax ValDeclaration( string name, TypeSyntax? type = null, ExpressionSyntax? value = null) => - VariableDeclaration([], null, false, false, name, type, value); + VariableDeclaration( + attributes: [], null, + isGlobal: false, + isField: false, + isMutable: false, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax VarDeclaration( string name, TypeSyntax? type = null, - ExpressionSyntax? value = null) => - VariableDeclaration([], null, false, true, name, type, value); + ExpressionSyntax? value = null) => VariableDeclaration( + attributes: [], + visibility: null, + isGlobal: false, + isField: false, + isMutable: true, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax FieldValDeclaration( string name, TypeSyntax? type = null, - ExpressionSyntax? value = null) => - VariableDeclaration([], null, true, false, name, type, value); + ExpressionSyntax? value = null) => VariableDeclaration( + attributes: [], + visibility: null, + isGlobal: false, + isField: true, + isMutable: false, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax FieldVarDeclaration( string name, TypeSyntax? type = null, - ExpressionSyntax? value = null) => - VariableDeclaration([], null, true, true, name, type, value); + ExpressionSyntax? value = null) => VariableDeclaration( + attributes: [], + visibility: null, + isGlobal: false, + isField: true, + isMutable: true, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax ValDeclaration( Visibility? visibility, string name, TypeSyntax? type = null, ExpressionSyntax? value = null) => - VariableDeclaration([], visibility, false, false, name, type, value); + VariableDeclaration( + attributes: [], + visibility: visibility, + isGlobal: false, + isField: false, + isMutable: false, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax VarDeclaration( Visibility? visibility, string name, TypeSyntax? type = null, ExpressionSyntax? value = null) => - VariableDeclaration([], visibility, false, true, name, type, value); + VariableDeclaration( + attributes: [], + visibility: visibility, + isGlobal: false, + isField: false, + isMutable: true, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax FieldValDeclaration( Visibility? visibility, string name, TypeSyntax? type = null, ExpressionSyntax? value = null) => - VariableDeclaration([], visibility, true, false, name, type, value); + VariableDeclaration( + attributes: [], + visibility: visibility, + isGlobal: false, + isField: true, + isMutable: false, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax FieldVarDeclaration( Visibility? visibility, string name, TypeSyntax? type = null, ExpressionSyntax? value = null) => - VariableDeclaration([], visibility, true, true, name, type, value); + VariableDeclaration( + attributes: [], + visibility: visibility, + isGlobal: false, + isField: true, + isMutable: true, + name: name, + type: type, + value: value); public static VariableDeclarationSyntax VariableDeclaration( IEnumerable attributes, Visibility? visibility, + bool isGlobal, bool isField, bool isMutable, string name, @@ -231,6 +292,7 @@ public static VariableDeclarationSyntax VariableDeclaration( ExpressionSyntax? value = null) => VariableDeclaration( attributes, Visibility(visibility), + isGlobal ? TokenKind.KeywordGlobal : null, isField ? TokenKind.KeywordField : null, isMutable ? TokenKind.KeywordVar : TokenKind.KeywordVal, name, diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs index 078baffe7..3661d6972 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs @@ -157,7 +157,7 @@ _ when IsMissing(node) => false, ModuleDeclarationSyntax md => !IsMissing(md.CloseBrace), ImportDeclarationSyntax id => !IsMissing(id.Semicolon), FunctionDeclarationSyntax fd => IsCompleteEntry(fd.Body), - ParameterSyntax p => IsCompleteEntry(p.Type), + NormalParameterSyntax p => IsCompleteEntry(p.Type), BlockFunctionBodySyntax bfb => !IsMissing(bfb.CloseBrace), InlineFunctionBodySyntax ifb => !IsMissing(ifb.Semicolon), LabelDeclarationSyntax ld => !IsMissing(ld.Colon), diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxHighlighter.cs b/src/Draco.Compiler/Api/Syntax/SyntaxHighlighter.cs index 6e50d7956..2e49eea72 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxHighlighter.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxHighlighter.cs @@ -140,7 +140,7 @@ or TokenKind.BracketOpen private static IEnumerable Identifier(SyntaxToken token, SemanticModel? semanticModel) { // Make a guess based on syntax - if (token.Parent is ParameterSyntax param && param.Name.Equals(token)) + if (token.Parent is NormalParameterSyntax param && param.Name.Equals(token)) { return Fragment(token, SyntaxColoring.ParameterName); } diff --git a/src/Draco.Compiler/Internal/Binding/BinderCache.cs b/src/Draco.Compiler/Internal/Binding/BinderCache.cs index fab218681..f0fc11a01 100644 --- a/src/Draco.Compiler/Internal/Binding/BinderCache.cs +++ b/src/Draco.Compiler/Internal/Binding/BinderCache.cs @@ -36,7 +36,8 @@ public Binder GetBinder(SyntaxNode syntax) CompilationUnitSyntax cu => this.BuildCompilationUnitBinder(cu), ScriptEntrySyntax entry => this.BuildScriptEntryBinder(entry), ModuleDeclarationSyntax mo => this.BuildModuleBinder(mo), - FunctionDeclarationSyntax decl => this.BuildFunctionDeclarationBinder(decl), + FunctionDeclarationSyntax funcDecl => this.BuildFunctionDeclarationBinder(funcDecl), + ClassDeclarationSyntax classDecl => this.BuildClassDeclarationBinder(classDecl), FunctionBodySyntax body => this.BuildFunctionBodyBinder(body), BlockExpressionSyntax block => this.BuildLocalBinder(block), WhileExpressionSyntax loop => this.BuildLoopBinder(loop), @@ -94,6 +95,19 @@ private FunctionBinder BuildFunctionDeclarationBinder(FunctionDeclarationSyntax return new FunctionBinder(binder, functionSymbol); } + private ClassBinder BuildClassDeclarationBinder(ClassDeclarationSyntax syntax) + { + Debug.Assert(syntax.Parent is not null); + var binder = this.GetBinder(syntax.Parent); + // Search for the function in the parents container + // For that we unwrap from the injected import layer(s) + var parent = UnwrapFromImportBinder(binder); + var classSymbol = parent.DeclaredSymbols + .OfType() + .First(member => member.DeclaringSyntax == syntax); // should we shove that in a helper ? + return new ClassBinder(binder, classSymbol); + } + private Binder BuildFunctionBodyBinder(FunctionBodySyntax syntax) { Debug.Assert(syntax.Parent is not null); diff --git a/src/Draco.Compiler/Internal/Binding/BinderFacts.cs b/src/Draco.Compiler/Internal/Binding/BinderFacts.cs index 4409d6c95..c8f9d9ba9 100644 --- a/src/Draco.Compiler/Internal/Binding/BinderFacts.cs +++ b/src/Draco.Compiler/Internal/Binding/BinderFacts.cs @@ -122,7 +122,8 @@ or ModuleDeclarationSyntax or FunctionBodySyntax or BlockExpressionSyntax or WhileExpressionSyntax - or ForExpressionSyntax; + or ForExpressionSyntax + or ClassDeclarationSyntax; /// /// Checks, if a given syntax node defines a symbol. @@ -132,8 +133,9 @@ or WhileExpressionSyntax public static bool DefinesSymbol(SyntaxNode node) => node is FunctionDeclarationSyntax or VariableDeclarationSyntax - or ParameterSyntax - or LabelDeclarationSyntax; + or NormalParameterSyntax + or LabelDeclarationSyntax + or ClassDeclarationSyntax; /// /// Checks, if a given syntax node references a symbol. diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs b/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs index ca3e415c5..ade60551c 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Expression.cs @@ -12,7 +12,9 @@ using Draco.Compiler.Internal.Solver.Tasks; using Draco.Compiler.Internal.Symbols; using Draco.Compiler.Internal.Symbols.Error; +using Draco.Compiler.Internal.Symbols.Syntax; using Draco.Compiler.Internal.Symbols.Synthetized; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Draco.Compiler.Internal.Binding; @@ -65,9 +67,10 @@ private async BindingTask BindExpressionToValueProducingExpress UnaryExpressionSyntax ury => this.BindUnaryExpression(ury, constraints, diagnostics), BinaryExpressionSyntax bin => this.BindBinaryExpression(bin, constraints, diagnostics), RelationalExpressionSyntax rel => this.BindRelationalExpression(rel, constraints, diagnostics), - MemberExpressionSyntax maccess => this.BindMemberExpression(maccess, constraints, diagnostics), + MemberExpressionSyntax mem => this.BindMemberExpression(mem, constraints, diagnostics), GenericExpressionSyntax gen => this.BindGenericExpression(gen, constraints, diagnostics), IndexExpressionSyntax index => this.BindIndexExpression(index, constraints, diagnostics), + ThisExpressionSyntax @this => this.BindThisExpression(@this, constraints, diagnostics), _ => throw new ArgumentOutOfRangeException(nameof(syntax)), }; @@ -710,10 +713,50 @@ private async BindingTask BindIndexExpression(IndexExpressionSy } else { + // Any other indexer return new BoundIndexGetExpression(syntax, receiver, indexer, await BindingTask.WhenAll(argsTask)); } } + private BindingTask BindThisExpression(ThisExpressionSyntax syntax, ConstraintSolver constraints, DiagnosticBag diagnostics) + { + // Check, if we are in a function + if (this.ContainingSymbol is not SyntaxFunctionSymbol function) + { + // No, report error + diagnostics.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalThis, + location: syntax.Location)); + return BindingTask.FromResult( + new BoundParameterExpression( + syntax, + new ErrorThisParameterSymbol(WellKnownTypes.ErrorType, new ErrorFunctionSymbol(0)))); + } + + // Check, if the function has a this argument + var thisArg = function.ThisParameter; + if (thisArg is null) + { + // No, report error + diagnostics.Add(Diagnostic.Create( + template: SymbolResolutionErrors.ThisReferencedInStaticMethod, + location: syntax.Location, + formatArgs: [this.ContainingSymbol!.Name])); + + // We can approximate the type of the this argument by checking the containing type + var type = function.ContainingSymbol as TypeSymbol + ?? WellKnownTypes.ErrorType; + return BindingTask.FromResult( + new BoundParameterExpression( + syntax, + new ErrorThisParameterSymbol(type, function))); + } + + // All ok + var boundThis = new BoundParameterExpression(syntax, thisArg); + return BindingTask.FromResult(boundThis); + } + private async BindingTask BindGenericExpression(GenericExpressionSyntax syntax, ConstraintSolver constraints, DiagnosticBag diagnostics) { var instantiatedTask = this.BindExpression(syntax.Instantiated, constraints, diagnostics); diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Type.cs b/src/Draco.Compiler/Internal/Binding/Binder_Type.cs index f1ae74195..80b4b3652 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Type.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Type.cs @@ -5,7 +5,6 @@ using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Diagnostics; using Draco.Compiler.Internal.Symbols; -using Draco.Compiler.Internal.Symbols.Error; namespace Draco.Compiler.Internal.Binding; @@ -60,7 +59,7 @@ internal TypeSymbol BindTypeToTypeSymbol(TypeSyntax syntax, DiagnosticBag diagno internal virtual Symbol BindType(TypeSyntax syntax, DiagnosticBag diagnostics) => syntax switch { // NOTE: The syntax error is already reported - UnexpectedTypeSyntax => new ErrorTypeSymbol(""), + UnexpectedTypeSyntax => WellKnownTypes.ErrorType, NameTypeSyntax name => this.BindNameType(name, diagnostics), MemberTypeSyntax member => this.BindMemberType(member, diagnostics), GenericTypeSyntax generic => this.BindGenericType(generic, diagnostics), diff --git a/src/Draco.Compiler/Internal/Binding/ClassBinder.cs b/src/Draco.Compiler/Internal/Binding/ClassBinder.cs new file mode 100644 index 000000000..cf83df1ed --- /dev/null +++ b/src/Draco.Compiler/Internal/Binding/ClassBinder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.Binding; + +/// +/// Binds an in-source defined class. +/// +internal sealed class ClassBinder(Binder parent, TypeSymbol symbol) : Binder(parent) +{ + public override TypeSymbol ContainingSymbol => this.symbol; + public override SyntaxNode? DeclaringSyntax => this.symbol.DeclaringSyntax; + + private readonly TypeSymbol symbol = symbol; + + public override IEnumerable DeclaredSymbols => this.symbol.Members + .Concat(this.symbol.GenericParameters); + + internal override void LookupLocal(LookupResult result, string name, ref LookupFlags flags, Predicate allowSymbol, SyntaxNode? currentReference) + { + foreach (var typeParam in this.symbol.GenericParameters) + { + if (typeParam.Name != name) continue; + if (!allowSymbol(typeParam)) continue; + result.Add(typeParam); + break; + } + + foreach (var member in this.symbol.Members) + { + if (member.Name != name) continue; + if (!allowSymbol(member)) continue; + result.Add(member); + // NOTE: We don't break here, because there are potentially multiple overloads + } + } +} diff --git a/src/Draco.Compiler/Internal/Binding/LocalBinder.cs b/src/Draco.Compiler/Internal/Binding/LocalBinder.cs index 25dd19861..61ccbdef0 100644 --- a/src/Draco.Compiler/Internal/Binding/LocalBinder.cs +++ b/src/Draco.Compiler/Internal/Binding/LocalBinder.cs @@ -168,7 +168,7 @@ private void Build() private Symbol? BuildSymbol(SyntaxNode syntax, int localCount) => syntax switch { FunctionDeclarationSyntax function => new SourceFunctionSymbol(this.ContainingSymbol, function), - ParameterSyntax parameter => new SourceParameterSymbol((FunctionSymbol)this.ContainingSymbol, parameter), + NormalParameterSyntax parameter => new SourceParameterSymbol((FunctionSymbol)this.ContainingSymbol, parameter), VariableDeclarationSyntax variable => new SourceLocalSymbol(this.ContainingSymbol, new TypeVariable(localCount), variable), LabelDeclarationSyntax label => new SourceLabelSymbol(this.ContainingSymbol, label), _ => null, diff --git a/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs b/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs index 491d07f92..25a0c57fc 100644 --- a/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs +++ b/src/Draco.Compiler/Internal/Binding/SymbolResolutionErrors.cs @@ -185,6 +185,24 @@ internal static class SymbolResolutionErrors format: "the {0} {1} is inaccessible due to its visibility", code: Code(19)); + /// + /// The this parameter is not first in the parameter list. + /// + public static readonly DiagnosticTemplate ThisParameterNotFirst = DiagnosticTemplate.Create( + title: "this parameter not first", + severity: DiagnosticSeverity.Error, + format: "the this parameter must be the first parameter", + code: Code(20)); + + /// + /// This was referenced outside of a type. + /// + public static readonly DiagnosticTemplate ThisReferencedInStaticMethod = DiagnosticTemplate.Create( + title: "this reference in static method", + severity: DiagnosticSeverity.Error, + format: "this reference can not be referenced in the static method {0}", + code: Code(21)); + /// /// The return expression is illegal in the current context. /// @@ -192,5 +210,14 @@ internal static class SymbolResolutionErrors title: "illegal return", severity: DiagnosticSeverity.Error, format: "illegal return expression outside of function definition", - code: Code(20)); + code: Code(22)); + + /// + /// The this expression is illegal in the current context. + /// + public static readonly DiagnosticTemplate IllegalThis = DiagnosticTemplate.Create( + title: "ilegal this", + severity: DiagnosticSeverity.Error, + format: "illegal this expression outside of a function definition", + code: Code(23)); } diff --git a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs index 34e3930c0..a4d0acb2b 100644 --- a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs +++ b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs @@ -72,7 +72,12 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) private EntityHandle GetHandle(Symbol symbol) => this.metadataCodegen.GetEntityHandle(symbol); // TODO: Parameters don't handle unit yet, it introduces some signature problems - private int GetParameterIndex(ParameterSymbol parameter) => this.procedure.GetParameterIndex(parameter); + private int GetParameterIndex(ParameterSymbol parameter) + { + if (parameter.IsThis) return 0; + return this.procedure.GetParameterIndex(parameter) + + (parameter.ContainingSymbol.IsStatic ? 0 : 1); + } private AllocatedLocal? GetAllocatedLocal(LocalSymbol local) { @@ -81,6 +86,7 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) } private int? GetLocalIndex(LocalSymbol local) => this.GetAllocatedLocal(local)?.Index; + private int? GetRegisterIndex(Register register) { if (SymbolEqualityComparer.Default.Equals(register.Type, WellKnownTypes.Unit)) return null; @@ -91,6 +97,7 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) allocatedRegister = this.allocatedLocals.Count + this.allocatedRegisters.Count; this.allocatedRegisters.Add(register, allocatedRegister); } + return allocatedRegister; } @@ -108,6 +115,7 @@ private LabelHandle GetLabel(IBasicBlock block) label = this.InstructionEncoder.DefineLabel(); this.labels.Add(block, label); } + return label; } @@ -128,11 +136,13 @@ private void EncodeBasicBlock(IBasicBlock basicBlock) private void EncodeInstruction(IInstruction instruction) { var operandEnumerator = instruction.Operands.GetEnumerator(); + IOperand NextOperand() { if (!operandEnumerator!.MoveNext()) throw new InvalidOperationException(); return operandEnumerator.Current; } + IEnumerable RemainingOperands() { while (operandEnumerator!.MoveNext()) yield return operandEnumerator.Current; @@ -221,15 +231,16 @@ IEnumerable RemainingOperands() this.LoadLocal(local); break; } - case FieldSymbol { IsStatic: true } global: + case FieldSymbol field: { - this.InstructionEncoder.OpCode(ILOpCode.Ldsfld); - this.EncodeToken(global); + this.InstructionEncoder.OpCode(field.IsStatic ? ILOpCode.Ldsfld : ILOpCode.Ldfld); + this.EncodeToken(field); break; } default: throw new InvalidOperationException(); } + // Just copy to the target local this.StoreRegister(load.Target); break; @@ -255,6 +266,7 @@ IEnumerable RemainingOperands() loadElement.Target.Type, loadElement.Indices.Count)); } + // Store result this.StoreRegister(loadElement.Target); break; @@ -277,14 +289,15 @@ IEnumerable RemainingOperands() this.EncodePush(NextOperand()); this.StoreLocal(local); break; - case FieldSymbol { IsStatic: true } global: + case FieldSymbol field: this.EncodePush(NextOperand()); - this.InstructionEncoder.OpCode(ILOpCode.Stsfld); - this.EncodeToken(global); + this.InstructionEncoder.OpCode(field.IsStatic ? ILOpCode.Stsfld : ILOpCode.Stfld); + this.EncodeToken(field); break; default: throw new InvalidOperationException(); } + break; } case StoreElementInstruction storeElement: @@ -311,6 +324,7 @@ IEnumerable RemainingOperands() targetStorageType, storeElement.Indices.Count)); } + break; } case StoreFieldInstruction storeField: @@ -351,6 +365,7 @@ IEnumerable RemainingOperands() default: throw new InvalidOperationException(); } + break; } case CallInstruction call: @@ -406,6 +421,7 @@ IEnumerable RemainingOperands() newArr.ElementType, newArr.Dimensions.Count)); } + // Store result this.StoreRegister(newArr.Target); break; @@ -512,6 +528,7 @@ private void EncodePush(IOperand operand) default: throw new NotImplementedException(); } + break; case DefaultValue d: { @@ -547,8 +564,10 @@ private void StoreRegister(Register register) // Need to pop this.InstructionEncoder.OpCode(ILOpCode.Pop); } + return; } + this.InstructionEncoder.StoreLocal(index.Value); } diff --git a/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs b/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs index 1c32c6d05..d7102d6c4 100644 --- a/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs +++ b/src/Draco.Compiler/Internal/Codegen/MetadataCodegen.cs @@ -15,7 +15,9 @@ using Draco.Compiler.Internal.Symbols.Metadata; using Draco.Compiler.Internal.Symbols.Script; using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Symbols.Synthetized; using Draco.Compiler.Internal.Symbols.Synthetized.Array; +using static System.Reflection.Metadata.BlobBuilder; namespace Draco.Compiler.Internal.Codegen; @@ -94,10 +96,9 @@ public static void Generate( private readonly IAssembly assembly; private readonly BlobBuilder ilBuilder = new(); - private readonly Dictionary moduleReferenceHandles = []; - private readonly Dictionary intrinsicReferenceHandles = []; private readonly AssemblyReferenceHandle systemRuntimeReference; private readonly TypeReferenceHandle systemObjectReference; + private readonly TypeReferenceHandle systemValueTypeReference; private MetadataCodegen(Compilation compilation, IAssembly assembly, CodegenFlags flags) { @@ -120,6 +121,11 @@ private MetadataCodegen(Compilation compilation, IAssembly assembly, CodegenFlag assembly: this.systemRuntimeReference, @namespace: "System", name: "Object"); + + this.systemValueTypeReference = this.GetOrAddTypeReference( + assembly: this.systemRuntimeReference, + @namespace: "System", + name: "ValueType"); } private void WriteModuleAndAssemblyDefinition() @@ -147,31 +153,8 @@ private void WriteModuleAndAssemblyDefinition() methodList: MetadataTokens.MethodDefinitionHandle(1)); } - public TypeReferenceHandle GetModuleReferenceHandle(IModule module) - { - if (!this.moduleReferenceHandles.TryGetValue(module, out var handle)) - { - var resolutionScope = module.Parent is null - // Root module, we take the module definition containing it - ? (EntityHandle)this.ModuleDefinitionHandle - // We take its parent module - : this.GetModuleReferenceHandle(module.Parent); - var name = string.IsNullOrEmpty(module.Name) - ? CompilerConstants.DefaultModuleName - : module.Name; - handle = this.GetOrAddTypeReference( - parent: resolutionScope, - @namespace: null, - name: name); - this.moduleReferenceHandles.Add(module, handle); - } - return handle; - } - public UserStringHandle GetStringLiteralHandle(string text) => this.MetadataBuilder.GetOrAddUserString(text); - public MemberReferenceHandle GetIntrinsicReferenceHandle(Symbol symbol) => this.intrinsicReferenceHandles[symbol]; - // TODO: This can be cached by symbol to avoid double reference instertion public EntityHandle GetEntityHandle(Symbol symbol) { @@ -209,6 +192,15 @@ public EntityHandle GetEntityHandle(Symbol symbol) return this.MetadataBuilder.AddTypeSpecification(blob); } + case SourceClassSymbol sourceClass: + { + // TODO: Possibly very incorrect + return this.GetOrAddTypeReference( + parent: this.GetEntityHandle(sourceClass.ContainingSymbol), + @namespace: null, + name: sourceClass.MetadataName); + } + case TypeSymbol typeSymbol: { var blob = this.EncodeBlob(e => @@ -242,17 +234,16 @@ Symbol GetContainingSymbol() { if (func.IsNested) { + // TODO: This can be fixed by moving this structural responsibility to the IR // We can't have nested functions represented in metadata directly, so we'll climb up the parent chain // To find the first non-function container - var current = func.ContainingSymbol; - while (current is FunctionSymbol func) - { - current = func.ContainingSymbol; - } - return current!; + return func.AncestorChain + .Skip(1) + .First(s => s is not FunctionSymbol); } if (func.ContainingSymbol is not TypeSymbol type) return func.ContainingSymbol!; + if (!type.IsArrayType) return type; // NOTE: This hack is present because of Arrays spit out stuff from their base types // to take priority @@ -272,9 +263,9 @@ Symbol GetContainingSymbol() // Probably not, let's shove them somewhere known once we can make up our minds // This is the case for synthetized ctor functions for example parent: func.ContainingSymbol is null - ? this.GetModuleReferenceHandle(this.assembly.RootModule) + ? (EntityHandle)this.ModuleDefinitionHandle : this.GetEntityHandle(GetContainingSymbol()), - name: func.NestedName, + name: func.MetadataName, signature: this.EncodeBlob(e => { // In generic instances we still need to reference the generic types @@ -295,23 +286,49 @@ Symbol GetContainingSymbol() case SourceModuleSymbol: case ScriptModuleSymbol: { - if (this.RedirectHandlesToCompileTimeRoot) - { - var root = this.Compilation.CompileTimeExecutor.RootModule; - return this.GetModuleReferenceHandle(root); - } - - var module = (ModuleSymbol)symbol; - var irModule = this.assembly.Lookup(module); - return this.GetModuleReferenceHandle(irModule); + var moduleSymbol = this.RedirectHandlesToCompileTimeRoot + ? this.Compilation.RootModule + : (ModuleSymbol)symbol; + var parent = moduleSymbol.AncestorChain + .Skip(1) + .FirstOrDefault(s => s is TypeSymbol or ModuleSymbol); + + var resolutionScope = parent is null + // Root module, we take the module definition containing it + ? (EntityHandle)this.ModuleDefinitionHandle + // We take its parent module + : this.GetEntityHandle(parent); + var name = string.IsNullOrEmpty(moduleSymbol.MetadataName) + ? CompilerConstants.DefaultModuleName + : moduleSymbol.MetadataName; + return this.GetOrAddTypeReference( + parent: resolutionScope, + @namespace: null, + name: name); } case FieldSymbol field: { + var parentHandle = this.GetEntityHandle(field.ContainingSymbol ?? throw new InvalidOperationException()); + // NOTE: Hack for field definitions for generics + if (field.ContainingSymbol is SourceClassSymbol { IsGenericDefinition: true } sourceClass) + { + var genericInstance = this.EncodeBlob(e => + { + var argsEncoder = e.TypeSpecificationSignature().GenericInstantiation( + genericType: parentHandle, + genericArgumentCount: sourceClass.GenericParameters.Length, + isValueType: sourceClass.IsValueType); + foreach (var param in sourceClass.GenericParameters) + { + this.EncodeSignatureType(argsEncoder.AddArgument(), param); + } + }); + parentHandle = this.MetadataBuilder.AddTypeSpecification(genericInstance); + } return this.AddMemberReference( - parent: this.GetEntityHandle(field.ContainingSymbol - ?? throw new InvalidOperationException()), - name: field.Name, + parent: parentHandle, + name: field.MetadataName, signature: this.EncodeBlob(e => { var encoder = e.Field(); @@ -393,7 +410,7 @@ private EntityHandle GetMultidimensionalArrayTypeHandle(TypeSymbol elementType, private AssemblyReferenceHandle AddAssemblyReference(MetadataAssemblySymbol module) => this.GetOrAddAssemblyReference( - name: module.Name, + name: module.MetadataName, version: module.Version); private static string? GetNamespaceForSymbol(Symbol? symbol) => symbol switch @@ -514,21 +531,28 @@ private void EncodeModule(IModule module, TypeDefinitionHandle? parent, ref int this.EncodeProcedure(module.GlobalInitializer, specialName: ".cctor"); ++procIndex; + // We encode every class + foreach (var @class in module.Classes.Values) + { + this.EncodeClass(@class, parent: createdModule, fieldIndex: ref fieldIndex, procIndex: ref procIndex); + } + // We encode every submodule foreach (var subModule in module.Submodules.Values) { - this.EncodeModule(subModule, createdModule, ref fieldIndex, ref procIndex); + this.EncodeModule(subModule, createdModule, fieldIndex: ref fieldIndex, procIndex: ref procIndex); } } private FieldDefinitionHandle EncodeField(FieldSymbol field) { - var visibility = GetFieldVisibility(field.Visibility); + var attributes = GetFieldVisibility(field.Visibility); + if (field.IsStatic) attributes |= FieldAttributes.Static; // Definition return this.AddFieldDefinition( - attributes: visibility | FieldAttributes.Static, - name: field.Name, + attributes: attributes, + name: field.MetadataName, signature: this.EncodeFieldSignature(field)); } @@ -558,10 +582,9 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe hasDynamicStackAllocation: false); // Determine attributes - var attributes = MethodAttributes.Static | MethodAttributes.HideBySig; - attributes |= specialName is null - ? visibility - : MethodAttributes.Private | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; + var attributes = MethodAttributes.HideBySig | visibility; + if (procedure.Symbol.IsStatic) attributes |= MethodAttributes.Static; + if (specialName != null) attributes |= MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; // Parameters var parameterList = this.NextParameterHandle; @@ -569,7 +592,7 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe { var paramHandle = this.AddParameterDefinition( attributes: ParameterAttributes.None, - name: param.Name, + name: param.MetadataName, index: procedure.GetParameterIndex(param)); // Add attributes @@ -580,7 +603,8 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe var definitionHandle = this.MetadataBuilder.AddMethodDefinition( attributes: attributes, implAttributes: MethodImplAttributes.IL, - name: this.GetOrAddString(specialName ?? procedure.Name), + // TODO: Maybe expose metadata name directly? + name: this.GetOrAddString(specialName ?? procedure.Symbol.MetadataName), signature: this.EncodeProcedureSignature(procedure), bodyOffset: methodBodyOffset, parameterList: parameterList); @@ -595,7 +619,7 @@ private MethodDefinitionHandle EncodeProcedure(IProcedure procedure, string? spe this.MetadataBuilder.AddGenericParameter( parent: definitionHandle, attributes: GenericParameterAttributes.None, - name: this.GetOrAddString(typeParam.Name), + name: this.GetOrAddString(typeParam.MetadataName), index: genericIndex++); } @@ -609,7 +633,7 @@ private PropertyDefinitionHandle EncodeProperty( TypeDefinitionHandle declaringType, PropertySymbol prop) => this.MetadataBuilder.AddProperty( attributes: PropertyAttributes.None, - name: this.GetOrAddString(prop.Name), + name: this.GetOrAddString(prop.MetadataName), signature: this.EncodeBlob(e => { e @@ -624,7 +648,7 @@ private BlobHandle EncodeFieldSignature(FieldSymbol field) => private BlobHandle EncodeProcedureSignature(IProcedure procedure) => this.EncodeBlob(e => { e - .MethodSignature(genericParameterCount: procedure.Generics.Count) + .MethodSignature(genericParameterCount: procedure.Generics.Count, isInstanceMethod: !procedure.IsStatic) .Parameters(procedure.Parameters.Count, out var retEncoder, out var paramsEncoder); this.EncodeReturnType(retEncoder, procedure.ReturnType); foreach (var param in procedure.Parameters) @@ -659,6 +683,91 @@ private BlobHandle EncodeAttributeSignature(AttributeInstance attribute) }); } + private TypeDefinitionHandle EncodeClass(IClass @class, TypeDefinitionHandle? parent, ref int fieldIndex, ref int procIndex) + { + var startFieldIndex = fieldIndex; + var startProcIndex = procIndex; + + var visibility = GetClassVisibility(@class.Symbol.Visibility, parent is not null); + var attributes = visibility + | TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed; + if (@class.Symbol.IsValueType) attributes |= TypeAttributes.SequentialLayout; // AutoLayout = 0. + + var definitionHandle = this.AddTypeDefinition( + attributes, + null, + // TODO: Maybe expose metadata name instead then instead of regular name? + @class.Symbol.MetadataName, + @class.Symbol.IsValueType ? this.systemValueTypeReference : this.systemObjectReference, + fieldList: MetadataTokens.FieldDefinitionHandle(startFieldIndex), + methodList: MetadataTokens.MethodDefinitionHandle(startProcIndex)); + + // Add generic type parameters + var genericIndex = 0; + foreach (var typeParam in @class.Generics) + { + this.MetadataBuilder.AddGenericParameter( + parent: definitionHandle, + attributes: GenericParameterAttributes.None, + name: this.GetOrAddString(typeParam.MetadataName), + index: genericIndex++); + } + + // Properties + // TODO: Copypasta + var firstProperty = null as PropertyDefinitionHandle?; + var propertyHandleMap = new Dictionary(); + foreach (var prop in @class.Properties) + { + var propHandle = this.EncodeProperty(definitionHandle, prop); + firstProperty ??= propHandle; + propertyHandleMap.Add(prop, propHandle); + } + if (firstProperty is not null) this.MetadataBuilder.AddPropertyMap(definitionHandle, firstProperty.Value); + + // Procedures + foreach (var proc in @class.Procedures.Values) + { + var specialName = proc.Symbol is DefaultConstructorSymbol + ? ".ctor" + : null; + var handle = this.EncodeProcedure(proc, specialName: specialName); + ++procIndex; + + // TODO: Copypasta + if (proc.Symbol is IPropertyAccessorSymbol propAccessor) + { + // This is an accessor + var isGetter = propAccessor.Property.Getter == propAccessor; + this.MetadataBuilder.AddMethodSemantics( + association: propertyHandleMap[propAccessor.Property], + semantics: isGetter ? MethodSemanticsAttributes.Getter : MethodSemanticsAttributes.Setter, + methodDefinition: handle); + } + } + + // Fields + foreach (var field in @class.Fields) + { + this.EncodeField(field); + ++fieldIndex; + } + + // If this is a valuetype without fields, we add .pack 0 and .size 1 + if (@class.Symbol.IsValueType && @class.Fields.Count == 0) + { + this.MetadataBuilder.AddTypeLayout( + type: definitionHandle, + packingSize: 0, + size: 1); + } + + // If this isn't top level module, we specify nested relationship + if (parent is not null) this.MetadataBuilder.AddNestedType(definitionHandle, parent.Value); + + return definitionHandle; + } + private IEnumerable ScalarConstantTypes => [ this.WellKnownTypes.SystemSByte, this.WellKnownTypes.SystemInt16, @@ -774,8 +883,9 @@ public void EncodeSignatureType(SignatureTypeEncoder encoder, TypeSymbol type) // Generic instantiation Debug.Assert(type.GenericDefinition is not null); + var typeRef = this.GetEntityHandle(type.GenericDefinition); var genericsEncoder = encoder.GenericInstantiation( - genericType: this.GetEntityHandle(type.GenericDefinition), + genericType: typeRef, genericArgumentCount: type.GenericArguments.Length, isValueType: type.IsValueType); foreach (var arg in type.GenericArguments) @@ -816,6 +926,17 @@ public void EncodeSignatureType(SignatureTypeEncoder encoder, TypeSymbol type) return; } + if (type is SourceClassSymbol sourceClass) + { + encoder.Type( + type: this.GetOrAddTypeReference( + parent: this.GetEntityHandle(sourceClass.ContainingSymbol), + @namespace: null, + name: sourceClass.MetadataName), + isValueType: sourceClass.IsValueType); + return; + } + // TODO throw new NotImplementedException(); } @@ -840,6 +961,14 @@ private void WritePe(Stream peStream) peBlob.WriteContentTo(peStream); } + private static TypeAttributes GetClassVisibility(Api.Semantics.Visibility visibility, bool isNested) => (visibility, isNested) switch + { + (Api.Semantics.Visibility.Public, true) => TypeAttributes.NestedPublic, + (Api.Semantics.Visibility.Public, false) => TypeAttributes.Public, + (_, true) => TypeAttributes.NestedAssembly, + (_, false) => TypeAttributes.NotPublic, + }; + private static FieldAttributes GetFieldVisibility(Api.Semantics.Visibility visibility) => visibility switch { Api.Semantics.Visibility.Public => FieldAttributes.Public, diff --git a/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs new file mode 100644 index 000000000..93f099509 --- /dev/null +++ b/src/Draco.Compiler/Internal/Declarations/ClassDeclaration.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.Declarations; + +/// +/// A class declaration. +/// +internal sealed class ClassDeclaration(ClassDeclarationSyntax syntax) : Declaration(syntax.Name.Text) +{ + /// + /// The syntax of the declaration. + /// + public ClassDeclarationSyntax Syntax { get; } = syntax; + + public override ImmutableArray Children => + InterlockedUtils.InitializeDefault(ref this.children, this.BuildChildren); + private ImmutableArray children; + + public override IEnumerable DeclaringSyntaxes => [this.Syntax]; + + private ImmutableArray BuildChildren() + { + if (this.Syntax.Body is not BlockClassBodySyntax block) return ImmutableArray.Empty; + + return block.Declarations.Select(this.BuildChild).OfType().ToImmutableArray(); + } + + // TODO: More entries to handle + private Declaration? BuildChild(SyntaxNode node) => node switch + { + // NOTE: We ignore import declarations in the declaration tree, unlike Roslyn + // We handle import declarations during constructing the binders + // Since we allow for imports in local scopes too, this is the most sensible choice + ImportDeclarationSyntax => null, + UnexpectedDeclarationSyntax => null, + _ => throw new ArgumentOutOfRangeException(nameof(node)), + }; +} diff --git a/src/Draco.Compiler/Internal/Declarations/FunctionDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/FunctionDeclaration.cs index 6ffc27b45..d1904330f 100644 --- a/src/Draco.Compiler/Internal/Declarations/FunctionDeclaration.cs +++ b/src/Draco.Compiler/Internal/Declarations/FunctionDeclaration.cs @@ -17,11 +17,5 @@ internal sealed class FunctionDeclaration(FunctionDeclarationSyntax syntax) public override ImmutableArray Children => []; - public override IEnumerable DeclaringSyntaxes - { - get - { - yield return this.Syntax; - } - } + public override IEnumerable DeclaringSyntaxes => [this.Syntax]; } diff --git a/src/Draco.Compiler/Internal/Declarations/GlobalDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/GlobalDeclaration.cs index 9fcfe7dfb..fef7bfdba 100644 --- a/src/Draco.Compiler/Internal/Declarations/GlobalDeclaration.cs +++ b/src/Draco.Compiler/Internal/Declarations/GlobalDeclaration.cs @@ -17,11 +17,5 @@ internal sealed class GlobalDeclaration(VariableDeclarationSyntax syntax) public override ImmutableArray Children => []; - public override IEnumerable DeclaringSyntaxes - { - get - { - yield return this.Syntax; - } - } + public override IEnumerable DeclaringSyntaxes => [this.Syntax]; } diff --git a/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs b/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs index 85a36bf07..c2420a858 100644 --- a/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs +++ b/src/Draco.Compiler/Internal/Declarations/SingleModuleDeclaration.cs @@ -27,13 +27,7 @@ internal sealed class SingleModuleDeclaration(string name, SplitPath path, Conta InterlockedUtils.InitializeDefault(ref this.children, this.BuildChildren); private ImmutableArray children; - public override IEnumerable DeclaringSyntaxes - { - get - { - yield return this.Syntax; - } - } + public override IEnumerable DeclaringSyntaxes => [this.Syntax]; private ImmutableArray BuildChildren() => this.Syntax.Declarations.Select(this.BuildChild).OfType().ToImmutableArray(); @@ -46,6 +40,7 @@ private ImmutableArray BuildChildren() => ImportDeclarationSyntax => null, VariableDeclarationSyntax var => new GlobalDeclaration(var), FunctionDeclarationSyntax func => new FunctionDeclaration(func), + ClassDeclarationSyntax @class => new ClassDeclaration(@class), ModuleDeclarationSyntax module => new SingleModuleDeclaration(module.Name.Text, this.Path.Append(module.Name.Text), module), UnexpectedDeclarationSyntax => null, _ => throw new ArgumentOutOfRangeException(nameof(node)), diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ClassCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ClassCodegen.cs new file mode 100644 index 000000000..bcc68f7b9 --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ClassCodegen.cs @@ -0,0 +1,65 @@ +using Draco.Compiler.Api; +using Draco.Compiler.Internal.BoundTree; +using Draco.Compiler.Internal.Lowering; +using Draco.Compiler.Internal.OptimizingIr.Model; +using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Symbols.Syntax; +using Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; + +namespace Draco.Compiler.Internal.OptimizingIr.Codegen; + +/// +/// Generates IR code for a class. +/// +internal sealed class ClassCodegen(ModuleCodegen moduleCodegen, Class @class) : SymbolVisitor +{ + private Compilation Compilation => moduleCodegen.Compilation; + private bool EmitSequencePoints => moduleCodegen.EmitSequencePoints; + + // Copypasta from ModuleCodegen + public override void VisitFunction(FunctionSymbol functionSymbol) + { + if (functionSymbol.Body is null) return; + + // Add procedure + var procedure = @class.DefineProcedure(functionSymbol); + + // Create the body + var body = this.RewriteBody(functionSymbol.Body); + // Yank out potential local functions and closures + var (bodyWithoutLocalFunctions, localFunctions) = ClosureRewriter.Rewrite(body); + // Compile it + var bodyCodegen = new LocalCodegen(this.Compilation, procedure); + bodyWithoutLocalFunctions.Accept(bodyCodegen); + + // Compile the local functions + foreach (var localFunc in localFunctions) this.VisitFunction(localFunc); + } + + public override void VisitField(FieldSymbol fieldSymbol) + { + if (fieldSymbol is not SyntaxFieldSymbol and not AutoPropertyBackingFieldSymbol) return; + + // TODO: Initializer value + @class.DefineField(fieldSymbol); + } + + public override void VisitProperty(PropertySymbol propertySymbol) + { + // TODO: Not flexible, won't work for non-auto props + if (propertySymbol is not SyntaxAutoPropertySymbol) return; + + @class.DefineProperty(propertySymbol); + + // TODO: Initializer value + } + + private BoundNode RewriteBody(BoundNode body) + { + // If needed, inject sequence points + if (body.Syntax is not null && this.EmitSequencePoints) body = SequencePointInjector.Inject(body); + // Desugar it + return body.Accept(new LocalRewriter(this.Compilation)); + } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ModuleCodegen.cs b/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ModuleCodegen.cs index 247d0ffee..86cbfd035 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ModuleCodegen.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Codegen/ModuleCodegen.cs @@ -68,6 +68,15 @@ public override void VisitField(FieldSymbol fieldSymbol) } } + public override void VisitType(TypeSymbol typeSymbol) + { + if (typeSymbol is not SourceClassSymbol sourceClass) return; + + var type = this.module.DefineClass(typeSymbol); + var typeCodegen = new ClassCodegen(this, type); + sourceClass.Accept(typeCodegen); + } + public override void VisitProperty(PropertySymbol propertySymbol) { // TODO: Not flexible, won't work for non-auto props diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Assembly.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Assembly.cs index b32136f51..10875a7fa 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/Assembly.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Assembly.cs @@ -7,29 +7,12 @@ namespace Draco.Compiler.Internal.OptimizingIr.Model; /// /// A mutable implementation. /// -internal sealed class Assembly : IAssembly +internal sealed class Assembly(ModuleSymbol module) : IAssembly { - private Procedure? entryPoint; - private readonly Module rootModule; + private readonly Module rootModule = new(module); public string Name { get; set; } = "output"; - public Procedure? EntryPoint - { - get => this.entryPoint; - set - { - if (value is null) - { - this.entryPoint = null; - return; - } - if (!ReferenceEquals(this, value.Assembly)) - { - throw new System.InvalidOperationException("entry point must be part of the assembly"); - } - this.entryPoint = value; - } - } + public Procedure? EntryPoint { get; set; } IProcedure? IAssembly.EntryPoint => this.EntryPoint; public Module RootModule => this.rootModule; @@ -37,11 +20,6 @@ public Procedure? EntryPoint public ImmutableArray GetAllProcedures() => this.rootModule.GetProcedures(); - public Assembly(ModuleSymbol module) - { - this.rootModule = new Module(module, this, null); - } - public override string ToString() { var result = new StringBuilder(); diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs new file mode 100644 index 000000000..5b29ff8ec --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Class.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.OptimizingIr.Model; + +internal sealed class Class(TypeSymbol symbol) : IClass +{ + public TypeSymbol Symbol { get; } = symbol; + public string Name => this.Symbol.Name; + + public IReadOnlyList Generics => this.Symbol.GenericParameters; + public IReadOnlyDictionary Procedures => this.procedures; + + public IReadOnlySet Fields => this.fields; + public IReadOnlySet Properties => this.properties; + + private readonly HashSet fields = []; + private readonly HashSet properties = []; + private readonly Dictionary procedures = []; + + public void DefineField(FieldSymbol fieldSymbol) => this.fields.Add(fieldSymbol); + public void DefineProperty(PropertySymbol propertySymbol) => this.properties.Add(propertySymbol); + + public Procedure DefineProcedure(FunctionSymbol functionSymbol) + { + if (!this.procedures.TryGetValue(functionSymbol, out var result)) + { + result = new Procedure(functionSymbol); + this.procedures.Add(functionSymbol, result); + } + return (Procedure)result; + } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs new file mode 100644 index 000000000..9eefc08f5 --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IClass.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.OptimizingIr.Model; + +/// +/// Interface of a class definition. +/// +internal interface IClass +{ + /// + /// The symbol of this class. + /// + public TypeSymbol Symbol { get; } + + /// + /// The name of this class. + /// + public string Name { get; } + + /// + /// The generic parameters on this class. + /// + public IReadOnlyList Generics { get; } + + // TODO: Fields and props should be order-dependent for classes and modules too + /// + /// The fields on this class. + /// + public IReadOnlySet Fields { get; } + + /// + /// The properties within this class. + /// + public IReadOnlySet Properties { get; } + + /// + /// The procedures on this class. + /// + public IReadOnlyDictionary Procedures { get; } +} diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs index 41b28030f..1fbb29f15 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IModule.cs @@ -18,16 +18,6 @@ internal interface IModule /// public string Name { get; } - /// - /// The assembly this module is defined in. - /// - public IAssembly Assembly { get; } - - /// - /// The parent module of this module. - /// - public IModule? Parent { get; } - /// /// The submodules of this module. /// @@ -48,6 +38,11 @@ internal interface IModule /// public IReadOnlyDictionary Procedures { get; } + /// + /// The compiled classes within this module. + /// + public IReadOnlyDictionary Classes { get; } + /// /// The procedure performing global initialization. /// diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs index 1b94b1692..dc28f9114 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/IProcedure.cs @@ -14,19 +14,14 @@ internal interface IProcedure public FunctionSymbol Symbol { get; } /// - /// The name of this procedure. + /// Whether this procedure is static or not. /// - public string Name { get; } + public bool IsStatic { get; } /// - /// The module this procedure is defined in. - /// - public IModule DeclaringModule { get; } - - /// - /// The assembly this procedure is defined in. + /// The name of this procedure. /// - public IAssembly Assembly { get; } + public string Name { get; } /// /// The entry basic block of this procedure. diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/ModelExtensions.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/ModelExtensions.cs index 872ab3a75..220b736b1 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/ModelExtensions.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/ModelExtensions.cs @@ -9,23 +9,6 @@ namespace Draco.Compiler.Internal.OptimizingIr.Model; /// internal static class ModelExtensions { - /// - /// Looks up the module in an assembly based on the module symbol. - /// - /// The assembly to lookup in. - /// The module to look up. - /// The looked up module within . - public static IModule Lookup(this IAssembly assembly, ModuleSymbol module) - { - if (ReferenceEquals(assembly.RootModule.Symbol, module)) return assembly.RootModule; - if (module.ContainingSymbol is not ModuleSymbol containingModule) - { - throw new KeyNotFoundException("the module could not be resolved based on the symbol"); - } - var parentModule = Lookup(assembly, containingModule); - return parentModule.Submodules[module]; - } - /// /// Retrieves all functions that are statically referenced by a given procedure. /// diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs index 3ec1a2a4e..3a46ef789 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Module.cs @@ -18,6 +18,9 @@ internal sealed class Module : IModule public IDictionary Submodules => this.submodules; IReadOnlyDictionary IModule.Submodules => this.submodules; + public IDictionary Classes => this.classes; + IReadOnlyDictionary IModule.Classes => this.classes; + public IReadOnlySet Fields => this.fields; public IReadOnlySet Properties => this.properties; @@ -27,26 +30,19 @@ internal sealed class Module : IModule public IReadOnlyDictionary Procedures => this.procedures; IReadOnlyDictionary IModule.Procedures => this.procedures; - public Assembly Assembly { get; } - IAssembly IModule.Assembly => this.Assembly; - - public Module? Parent { get; } - IModule? IModule.Parent => this.Parent; - private readonly HashSet fields = []; private readonly HashSet properties = []; private readonly Dictionary procedures = []; private readonly Dictionary submodules = []; + private readonly Dictionary classes = []; - public Module(ModuleSymbol symbol, Assembly assembly, Module? Parent) + public Module(ModuleSymbol symbol) { this.Symbol = symbol; this.GlobalInitializer = this.DefineProcedure(new IntrinsicFunctionSymbol( name: "", paramTypes: [], returnType: WellKnownTypes.Unit)); - this.Assembly = assembly; - this.Parent = Parent; } public ImmutableArray GetProcedures() @@ -67,7 +63,7 @@ public Procedure DefineProcedure(FunctionSymbol functionSymbol) { if (!this.procedures.TryGetValue(functionSymbol, out var result)) { - result = new Procedure(this, functionSymbol); + result = new Procedure(functionSymbol); this.procedures.Add(functionSymbol, result); } return (Procedure)result; @@ -77,12 +73,22 @@ public Module DefineModule(ModuleSymbol moduleSymbol) { if (!this.submodules.TryGetValue(moduleSymbol, out var result)) { - result = new Module(moduleSymbol, this.Assembly, this); + result = new Module(moduleSymbol); this.submodules.Add(moduleSymbol, result); } return (Module)result; } + public Class DefineClass(TypeSymbol typeSymbol) + { + if (!this.classes.TryGetValue(typeSymbol, out var result)) + { + result = new Class(typeSymbol); + this.classes.Add(typeSymbol, result); + } + return (Class)result; + } + public override string ToString() { var result = new StringBuilder(); diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs index 39a4d2b2d..6366b0d89 100644 --- a/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/Procedure.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Text; using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Symbols.Source; using Draco.Compiler.Internal.Symbols.Synthetized; namespace Draco.Compiler.Internal.OptimizingIr.Model; @@ -12,12 +14,7 @@ namespace Draco.Compiler.Internal.OptimizingIr.Model; internal sealed class Procedure : IProcedure { public FunctionSymbol Symbol { get; } - public string Name => this.Symbol.NestedName; - public TypeSymbol? Type => this.Symbol.Type; - public Module DeclaringModule { get; } - IModule IProcedure.DeclaringModule => this.DeclaringModule; - public Assembly Assembly => this.DeclaringModule.Assembly; - IAssembly IProcedure.Assembly => this.Assembly; + public string Name => this.Symbol.Name; public BasicBlock Entry { get; } IBasicBlock IProcedure.Entry => this.Entry; public IReadOnlyList Attributes => this.Symbol.Attributes; @@ -31,21 +28,23 @@ internal sealed class Procedure : IProcedure public IReadOnlyList Locals => this.locals; public IReadOnlyList Registers => this.registers; + public bool IsStatic => this.Symbol.IsStatic; + private readonly Dictionary basicBlocks = []; private readonly List locals = []; private readonly List registers = []; - public Procedure(Module declaringModule, FunctionSymbol symbol) + public Procedure(FunctionSymbol symbol) { - this.DeclaringModule = declaringModule; this.Symbol = symbol; this.Entry = this.DefineBasicBlock(new SynthetizedLabelSymbol("begin")); } public int GetParameterIndex(ParameterSymbol symbol) { + if (symbol.IsThis) throw new ArgumentOutOfRangeException(nameof(symbol), "this parameter is treated special"); var idx = this.Symbol.Parameters.IndexOf(symbol); - if (idx == -1) throw new System.ArgumentOutOfRangeException(nameof(symbol)); + if (idx == -1) throw new ArgumentOutOfRangeException(nameof(symbol)); return idx; } diff --git a/src/Draco.Compiler/Internal/Solver/OverloadResolution/CallCandidate.cs b/src/Draco.Compiler/Internal/Solver/OverloadResolution/CallCandidate.cs index c920f719f..6bb0e17e6 100644 --- a/src/Draco.Compiler/Internal/Solver/OverloadResolution/CallCandidate.cs +++ b/src/Draco.Compiler/Internal/Solver/OverloadResolution/CallCandidate.cs @@ -13,8 +13,8 @@ internal static class CallCandidate public static CallCandidate Create(FunctionSymbol function) => CallCandidate.Create(function); - public static CallCandidate Create(FunctionTypeSymbol functionType) => - CallCandidate.Create(functionType); + public static CallCandidate Create(FunctionTypeSymbol functionType) => + CallCandidate.Create(functionType); } /// @@ -27,7 +27,7 @@ public static CallCandidate Create(FunctionSymbol function) => new(function.Parameters, function.IsVariadic, function); // TODO: Can a function type be variadic? This is probably something we should specify... - public static CallCandidate Create(FunctionTypeSymbol functionType) => + public static CallCandidate Create(FunctionTypeSymbol functionType) => new(functionType.Parameters, false, default); /// diff --git a/src/Draco.Compiler/Internal/Symbols/AttributeInstance.cs b/src/Draco.Compiler/Internal/Symbols/AttributeInstance.cs index 954db4ef8..4c6b5227f 100644 --- a/src/Draco.Compiler/Internal/Symbols/AttributeInstance.cs +++ b/src/Draco.Compiler/Internal/Symbols/AttributeInstance.cs @@ -106,7 +106,7 @@ private bool TypesMatch(System.Type reflType, TypeSymbol internalType) var wellKnownTypes = this.Constructor.DeclaringCompilation?.WellKnownTypes; if (wellKnownTypes is null) throw new System.NotImplementedException(); - var translatedReflType = wellKnownTypes.TranslatePrmitive(reflType); + var translatedReflType = wellKnownTypes.TranslatePrimitive(reflType); if (translatedReflType is null) throw new System.NotImplementedException(); return SymbolEqualityComparer.Default.Equals(translatedReflType, internalType); diff --git a/src/Draco.Compiler/Internal/Symbols/Error/ErrorThisParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Error/ErrorThisParameterSymbol.cs new file mode 100644 index 000000000..437738938 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Error/ErrorThisParameterSymbol.cs @@ -0,0 +1,15 @@ +namespace Draco.Compiler.Internal.Symbols.Error; + +/// +/// A 'this' parameter symbol where it's invalid. +/// +internal sealed class ErrorThisParameterSymbol( + TypeSymbol type, + FunctionSymbol containingSymbol) : ParameterSymbol +{ + public override TypeSymbol Type { get; } = type; + public override FunctionSymbol ContainingSymbol { get; } = containingSymbol; + + public override bool IsError => true; + public override bool IsThis => true; +} diff --git a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs index 4839979ac..d572ad244 100644 --- a/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/FunctionSymbol.cs @@ -110,6 +110,14 @@ public delegate IOperand CodegenDelegate( public override bool IsSpecialName => this.IsConstructor; + // TODO: Apart from exposing the metadata name here, we need to take care of nested names for local methods for example + // The name should include the parent functions too, like func foo() { func bar() { ... } } + // the inner method should be called foo.bar in metadata + + // NOTE: It seems like the backtick is only for types + // TODO: In that case, maybe move this logic out from Symbol and make MetadataName abstract or default to this instead? + public override string MetadataName => this.Name; + // NOTE: We override for covariant return type public override FunctionSymbol? GenericDefinition => null; public override IEnumerable Members => this.Parameters; @@ -123,6 +131,7 @@ public delegate IOperand CodegenDelegate( /// /// The bound body of this function, if it has one. /// This is the case for in-source and certain synthesized functions. + /// When null, the function will not be emitted. /// public virtual BoundStatement? Body => null; @@ -132,15 +141,6 @@ public delegate IOperand CodegenDelegate( /// public virtual CodegenDelegate? Codegen => null; - /// - /// Retrieves the nested name of this function, which prepends the containing function names. - /// - public string NestedName => this.ContainingSymbol switch - { - FunctionSymbol f => $"{f.NestedName}.{this.Name}", - _ => this.Name, - }; - public override string ToString() { var result = new StringBuilder(); @@ -178,7 +178,7 @@ public override FunctionSymbol GenericInstantiate(Symbol? containingSymbol, Immu public override FunctionSymbol GenericInstantiate(Symbol? containingSymbol, GenericContext context) => new FunctionInstanceSymbol(containingSymbol, this, context); - public override Api.Semantics.IFunctionSymbol ToApiSymbol() => new Api.Semantics.FunctionSymbol(this); + public override IFunctionSymbol ToApiSymbol() => new Api.Semantics.FunctionSymbol(this); public override void Accept(SymbolVisitor visitor) => visitor.VisitFunction(this); public override TResult Accept(SymbolVisitor visitor) => visitor.VisitFunction(this); diff --git a/src/Draco.Compiler/Internal/Symbols/IMemberSymbol.cs b/src/Draco.Compiler/Internal/Symbols/IMemberSymbol.cs index 9dede0337..d02ce1ea5 100644 --- a/src/Draco.Compiler/Internal/Symbols/IMemberSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/IMemberSymbol.cs @@ -6,7 +6,7 @@ namespace Draco.Compiler.Internal.Symbols; internal interface IMemberSymbol { /// - /// Specifying if given symbol is static. + /// Specifying if the given symbol is static. /// public bool IsStatic { get; } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/AssemblyNameComparer.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/AssemblyNameComparer.cs index fc0d00be7..ac8164d8f 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/AssemblyNameComparer.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/AssemblyNameComparer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using System.Reflection; namespace Draco.Compiler.Internal.Symbols.Metadata; diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs index c16dccbcf..805f7e2b8 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs @@ -57,7 +57,7 @@ private ImmutableArray BuildMembers() var symbol = MetadataSymbol.ToSymbol(this, typeDef); result.Add(symbol); // Add additional symbols - result.AddRange(MetadataSymbol.GetAdditionalSymbols(symbol)); + result.AddRange(symbol.GetAdditionalSymbols()); } // Done diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs index 64420ea26..066379ad9 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs @@ -67,7 +67,7 @@ private ImmutableArray BuildMembers() var symbol = MetadataSymbol.ToSymbol(this, typeDef); result.Add(symbol); // Add additional symbols - result.AddRange(MetadataSymbol.GetAdditionalSymbols(symbol)); + result.AddRange(symbol.GetAdditionalSymbols()); } // Methods diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs index e03a2ba13..9826b3e7d 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; using System.Reflection.Metadata; -using Draco.Compiler.Internal.Symbols.Synthetized; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -39,20 +37,6 @@ public static Symbol ToSymbol(Symbol containingSymbol, TypeDefinition typeDefini } } - // TODO: This isn't dependent on metadata types anymore, we could move it out - /// - /// Retrieves additional symbols for the given . - /// - /// The symbol to get additional symbols for. - /// The additional symbols for the given . - public static IEnumerable GetAdditionalSymbols(Symbol symbol) - { - if (symbol is not TypeSymbol typeSymbol) return []; - if (typeSymbol.IsAbstract) return []; - // For other types we provide constructor functions - return typeSymbol.Constructors.Select(ctor => new ConstructorFunctionSymbol(ctor)); - } - /// /// Decodes the given constant. /// diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs index 30d6a593f..022b03bf3 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -157,7 +156,7 @@ private ImmutableArray BuildMembers() var symbol = MetadataSymbol.ToSymbol(this, typeDef); result.Add(symbol); // Add additional symbols - result.AddRange(MetadataSymbol.GetAdditionalSymbols(symbol)); + result.AddRange(symbol.GetAdditionalSymbols()); } // Methods diff --git a/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs index 95f4846ab..a238c7279 100644 --- a/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/ParameterSymbol.cs @@ -17,7 +17,13 @@ internal abstract partial class ParameterSymbol : LocalSymbol /// public virtual bool IsVariadic => false; + /// + /// True, if this is a 'this' (receiver) parameter. + /// + public virtual bool IsThis => false; + public override bool IsMutable => false; + // NOTE: Override for covariant return type public override ParameterSymbol? GenericDefinition => null; public override SymbolKind Kind => SymbolKind.Parameter; diff --git a/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs index 2a5380c82..612e7a283 100644 --- a/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs @@ -84,30 +84,26 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) // Non-declaration statements are compiled into an initializer method if (statement is not DeclarationStatementSyntax decl) continue; - // Build the symbol(s) - foreach (var member in this.BuildMember(decl.Declaration)) - { - var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); - result.Add(member); - - // We check for illegal shadowing - if (earlierMember is null) continue; - - // Overloading is legal - if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; - - // If the illegal member is special name, it was cascaded from a declaration with multiple symbols, - // like an autoprop, skip it - if (member.IsSpecialName) continue; - - // Illegal - var syntax = member.DeclaringSyntax; - Debug.Assert(syntax is not null); - binderProvider.DiagnosticBag.Add(Diagnostic.Create( - template: SymbolResolutionErrors.IllegalShadowing, - location: syntax.Location, - formatArgs: member.Name)); - } + var member = this.BuildMember(decl.Declaration); + if (member is null) continue; + + var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); + result.Add(member); + result.AddRange(member.GetAdditionalSymbols()); + + // We check for illegal shadowing + if (earlierMember is null) continue; + + // Overloading is legal + if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; + + // Illegal + var syntax = member.DeclaringSyntax; + Debug.Assert(syntax is not null); + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalShadowing, + location: syntax.Location, + formatArgs: member.Name)); } // We add a function to evaluate the script @@ -137,28 +133,17 @@ private ScriptBinding BindScriptBindings(IBinderProvider binderProvider) return binder.BindScript(this, binderProvider.DiagnosticBag); } - private IEnumerable BuildMember(DeclarationSyntax decl) => decl switch + private Symbol? BuildMember(DeclarationSyntax decl) => decl switch { ImportDeclarationSyntax - or UnexpectedDeclarationSyntax => [], - FunctionDeclarationSyntax f => [this.BuildFunction(f)], - VariableDeclarationSyntax v when v.FieldModifier is not null => [this.BuildField(v)], - VariableDeclarationSyntax v when v.FieldModifier is null => this.BuildAutoProperty(v), - ModuleDeclarationSyntax m => [this.BuildModule(m)], + or UnexpectedDeclarationSyntax => null, + FunctionDeclarationSyntax f => new ScriptFunctionSymbol(this, f), + VariableDeclarationSyntax v when v.FieldModifier is not null => new ScriptFieldSymbol(this, v), + VariableDeclarationSyntax v when v.FieldModifier is null => new ScriptAutoPropertySymbol(this, v), + ModuleDeclarationSyntax m => this.BuildModule(m), _ => throw new ArgumentOutOfRangeException(nameof(decl)), }; - private ScriptFunctionSymbol BuildFunction(FunctionDeclarationSyntax syntax) => new(this, syntax); - private ScriptFieldSymbol BuildField(VariableDeclarationSyntax syntax) => new(this, syntax); - private IEnumerable BuildAutoProperty(VariableDeclarationSyntax syntax) - { - var property = new ScriptAutoPropertySymbol(this, syntax); - yield return property; - if (property.Getter is not null) yield return property.Getter; - if (property.Setter is not null) yield return property.Setter; - yield return property.BackingField; - } - private SourceModuleSymbol BuildModule(ModuleDeclarationSyntax syntax) { // We need to wrap it into a merged module declaration diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs new file mode 100644 index 000000000..e54b0d94f --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceClassSymbol.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Draco.Compiler.Api.Diagnostics; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; +using Draco.Compiler.Internal.Declarations; +using Draco.Compiler.Internal.Symbols.Synthetized; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// A generic class defined in-source. +/// +internal sealed class SourceClassSymbol( + Symbol containingSymbol, + ClassDeclarationSyntax syntax) : TypeSymbol, ISourceSymbol +{ + public override Symbol ContainingSymbol { get; } = containingSymbol; + public override ClassDeclarationSyntax DeclaringSyntax { get; } = syntax; + public override string Name => this.DeclaringSyntax.Name.Text; + + public override ImmutableArray GenericParameters => + this.BindGenericParametersIfNeeded(this.DeclaringCompilation!); + private ImmutableArray genericParameters; + + public override IEnumerable DefinedMembers => + this.BindMembersIfNeeded(this.DeclaringCompilation!); + private ImmutableArray definedMembers; + + public SourceClassSymbol(Symbol containingSymbol, ClassDeclaration declaration) + : this(containingSymbol, declaration.Syntax) + { + } + + public void Bind(IBinderProvider binderProvider) + { + this.BindGenericParametersIfNeeded(binderProvider); + this.BindMembersIfNeeded(binderProvider); + } + + private ImmutableArray BindGenericParametersIfNeeded(IBinderProvider binderProvider) => + InterlockedUtils.InitializeDefault(ref this.genericParameters, () => this.BindGenericParameters(binderProvider)); + + private ImmutableArray BindGenericParameters(IBinderProvider binderProvider) + { + if (this.DeclaringSyntax.Generics is null) return []; + + var genericParamSyntaxes = this.DeclaringSyntax.Generics.Parameters.Values.ToList(); + var genericParams = ImmutableArray.CreateBuilder(genericParamSyntaxes.Count); + + foreach (var genericParamSyntax in genericParamSyntaxes) + { + var paramName = genericParamSyntax.Name.Text; + var usedBefore = genericParams.Any(p => p.Name == paramName); + if (usedBefore) + { + // NOTE: We only report later duplicates, no need to report the first instance + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalShadowing, + location: genericParamSyntax.Location, + formatArgs: paramName)); + } + + var genericParam = new SourceTypeParameterSymbol(this, genericParamSyntax); + genericParams.Add(genericParam); + } + return genericParams.ToImmutable(); + } + + private ImmutableArray BindMembersIfNeeded(IBinderProvider binderProvider) => + InterlockedUtils.InitializeDefault(ref this.definedMembers, () => this.BindMembers(binderProvider)); + + // TODO: This shadowing check logic is duplicated a few places + // Along with member construction, maybe factor it out + private ImmutableArray BindMembers(IBinderProvider binderProvider) + { + // If there is no body, we only have a default constructor + if (this.DeclaringSyntax.Body is EmptyClassBodySyntax) return [new DefaultConstructorSymbol(this)]; + + if (this.DeclaringSyntax.Body is BlockClassBodySyntax blockBody) + { + var result = ImmutableArray.CreateBuilder(); + // NOTE: For now we always add a default constructor + result.Add(new DefaultConstructorSymbol(this)); + foreach (var member in blockBody.Declarations.Select(this.BuildMember)) + { + if (member is null) continue; + + var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); + result.Add(member); + result.AddRange(member.GetAdditionalSymbols()); + + // We check for illegal shadowing + if (earlierMember is null) continue; + + // Overloading is legal + if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; + + // Illegal + var syntax = member.DeclaringSyntax; + Debug.Assert(syntax is not null); + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.IllegalShadowing, + location: syntax.Location, + formatArgs: member.Name)); + } + return result.ToImmutable(); + } + + return []; + } + + private Symbol? BuildMember(DeclarationSyntax syntax) => syntax switch + { + FunctionDeclarationSyntax functionSyntax => new SourceFunctionSymbol(this, functionSyntax), + VariableDeclarationSyntax varSyntax when varSyntax.FieldModifier is null => new SourceAutoPropertySymbol(this, varSyntax), + VariableDeclarationSyntax varSyntax when varSyntax.FieldModifier is not null => new SourceFieldSymbol(this, varSyntax), + UnexpectedDeclarationSyntax => null, + _ => throw new NotImplementedException(), + }; + + public override string ToString() => $"{this.Name}{this.GenericsToString()}"; +} diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs index 030d0cf05..5c3b9bf32 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs @@ -31,6 +31,7 @@ public override void Bind(IBinderProvider binderProvider) { this.BindAttributesIfNeeded(binderProvider); this.BindGenericParametersIfNeeded(binderProvider); + this.BindThisParameterIfNeeded(binderProvider); this.BindParametersIfNeeded(binderProvider); // Force binding of parameters, as the type is lazy too foreach (var param in this.Parameters.Cast()) param.Bind(binderProvider); diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs index f272aaeb7..a35e2264a 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs @@ -6,6 +6,7 @@ using System.Threading; using Draco.Compiler.Api; using Draco.Compiler.Api.Diagnostics; +using Draco.Compiler.Api.Semantics; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Binding; using Draco.Compiler.Internal.Declarations; @@ -72,22 +73,18 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) var result = ImmutableArray.CreateBuilder(); // A declaration can yield multiple members, like an auto-property a getter and setter - foreach (var member in this.declaration.Children.SelectMany(this.BuildMember)) + foreach (var member in this.declaration.Children.Select(this.BuildMember)) { var earlierMember = result.FirstOrDefault(s => s.Name == member.Name); result.Add(member); + result.AddRange(member.GetAdditionalSymbols()); - // We chech for illegal shadowing + // We check for illegal shadowing if (earlierMember is null) continue; // Overloading is legal if (member is FunctionSymbol && earlierMember is FunctionSymbol) continue; - // NOTE: An illegal shadowing can be caused by a property, which introduces additional synthetized symbols - // like the backing field - // We check for these synthetized symbols and if they are special-named, we ignore them - if (member.IsSpecialName) continue; - // Illegal var syntax = member.DeclaringSyntax; Debug.Assert(syntax is not null); @@ -100,30 +97,15 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) return result.ToImmutable(); } - private IEnumerable BuildMember(Declaration declaration) => declaration switch + private Symbol BuildMember(Declaration declaration) => declaration switch { - FunctionDeclaration f => [this.BuildFunction(f)], - MergedModuleDeclaration m => [this.BuildModule(m)], - GlobalDeclaration g when g.Syntax.FieldModifier is not null => [this.BuildGlobalField(g)], - GlobalDeclaration g when g.Syntax.FieldModifier is null => this.BuildGlobalProperty(g), + FunctionDeclaration f => new SourceFunctionSymbol(this, f), + MergedModuleDeclaration m => new SourceModuleSymbol(this.DeclaringCompilation, this, m), + GlobalDeclaration g when g.Syntax.FieldModifier is not null => new SourceFieldSymbol(this, g), + GlobalDeclaration g when g.Syntax.FieldModifier is null => new SourceAutoPropertySymbol(this, g), + ClassDeclaration c => new SourceClassSymbol(this, c), _ => throw new ArgumentOutOfRangeException(nameof(declaration)), }; - private SourceFunctionSymbol BuildFunction(FunctionDeclaration declaration) => new(this, declaration); - private SourceFieldSymbol BuildGlobalField(GlobalDeclaration declaration) => new(this, declaration); - - private IEnumerable BuildGlobalProperty(GlobalDeclaration declaration) - { - // Auto-property, need to add getter, setter and backing field - var prop = new SourceAutoPropertySymbol(this, declaration); - yield return prop; - if (prop.Getter is not null) yield return prop.Getter; - if (prop.Setter is not null) yield return prop.Setter; - yield return prop.BackingField; - } - - private SourceModuleSymbol BuildModule(MergedModuleDeclaration declaration) => new(this.DeclaringCompilation, this, declaration); - - private SymbolDocumentation BuildDocumentation() => - MarkdownDocumentationExtractor.Extract(this); + private SymbolDocumentation BuildDocumentation() => MarkdownDocumentationExtractor.Extract(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceParameterSymbol.cs index ca507aa63..f56897587 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceParameterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceParameterSymbol.cs @@ -12,7 +12,7 @@ namespace Draco.Compiler.Internal.Symbols.Source; /// internal sealed class SourceParameterSymbol( FunctionSymbol containingSymbol, - ParameterSyntax syntax) : ParameterSymbol, ISourceSymbol + NormalParameterSyntax syntax) : ParameterSymbol, ISourceSymbol { public override ImmutableArray Attributes => this.BindAttributesIfNeeded(this.DeclaringCompilation!); private ImmutableArray attributes; @@ -24,7 +24,7 @@ internal sealed class SourceParameterSymbol( public override bool IsVariadic => this.DeclaringSyntax.Variadic is not null; public override string Name => this.DeclaringSyntax.Name.Text; - public override ParameterSyntax DeclaringSyntax { get; } = syntax; + public override NormalParameterSyntax DeclaringSyntax { get; } = syntax; public void Bind(IBinderProvider binderProvider) { diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceThisParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceThisParameterSymbol.cs new file mode 100644 index 000000000..ed678bfa1 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceThisParameterSymbol.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; + +namespace Draco.Compiler.Internal.Symbols.Source; + +/// +/// The 'this' parameter of a function defined in-source. +/// +internal sealed class SourceThisParameterSymbol : ParameterSymbol, ISourceSymbol +{ + public override string Name => "this"; + public override FunctionSymbol ContainingSymbol { get; } + public override bool IsVariadic => false; + public override bool IsThis => true; + public override ThisParameterSyntax DeclaringSyntax { get; } + + public override ImmutableArray Attributes => []; + + public override TypeSymbol Type => LazyInitializer.EnsureInitialized(ref this.type, this.BuildType); + private TypeSymbol? type; + + public SourceThisParameterSymbol(FunctionSymbol containingSymbol, ThisParameterSyntax syntax) + { + var containingType = containingSymbol.ContainingSymbol?.AncestorChain + .OfType() + .FirstOrDefault() + ?? throw new ArgumentException("the containing symbol of a source this parameter must be a function within a type"); + + this.ContainingSymbol = containingSymbol; + this.DeclaringSyntax = syntax; + } + + public void Bind(IBinderProvider binderProvider) { } + + private TypeSymbol BuildType() + { + var containingType = this.ContainingSymbol.AncestorChain + .OfType() + .First(); + + if (!containingType.IsGenericDefinition) return containingType; + + var genericArgs = containingType.GenericParameters.Cast().ToImmutableArray(); + return containingType.GenericInstantiate(containingType.ContainingSymbol, genericArgs); + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/Symbol.cs b/src/Draco.Compiler/Internal/Symbols/Symbol.cs index 8a30904e1..80af47243 100644 --- a/src/Draco.Compiler/Internal/Symbols/Symbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Symbol.cs @@ -8,6 +8,9 @@ using Draco.Compiler.Internal.Documentation; using Draco.Compiler.Internal.Symbols.Generic; using Draco.Compiler.Internal.Symbols.Metadata; +using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Symbols.Syntax; +using Draco.Compiler.Internal.Symbols.Synthetized; using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Symbols; @@ -85,7 +88,9 @@ public IEnumerable AncestorChain /// /// The metadata name of this symbol. /// - public virtual string MetadataName => this.Name; + public virtual string MetadataName => this.IsGenericDefinition + ? $"{this.Name}`{this.GenericParameters.Length}" + : this.Name; /// /// The name of this symbol. @@ -157,7 +162,7 @@ public virtual string MetadataFullName /// /// The visibility of this symbol. /// - public virtual Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Internal; + public virtual Visibility Visibility => Visibility.Internal; /// /// The syntax declaring this symbol. @@ -322,11 +327,18 @@ private protected string GenericsToString() return string.Empty; } - private protected static Api.Semantics.Visibility GetVisibilityFromTokenKind(TokenKind? kind) => kind switch + private protected static Visibility GetVisibilityFromTokenKind(TokenKind? kind) => kind switch { - null => Api.Semantics.Visibility.Private, - TokenKind.KeywordInternal => Api.Semantics.Visibility.Internal, - TokenKind.KeywordPublic => Api.Semantics.Visibility.Public, + null => Visibility.Private, + TokenKind.KeywordInternal => Visibility.Internal, + TokenKind.KeywordPublic => Visibility.Public, _ => throw new InvalidOperationException($"illegal visibility modifier token {kind}"), }; + + /// + /// Retrieves additional symbols for this symbol that should live in the same scope as this symbol itself. + /// This returns the constructor functions for types for example. + /// + /// The additional symbols for this symbol. + protected internal virtual IEnumerable GetAdditionalSymbols() => []; } diff --git a/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs b/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs index bebe1b314..7382a3ade 100644 --- a/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs +++ b/src/Draco.Compiler/Internal/Symbols/SymbolVisitor.cs @@ -15,6 +15,7 @@ public virtual void VisitFunction(FunctionSymbol functionSymbol) public virtual void VisitType(TypeSymbol typeSymbol) { + foreach (var genericParam in typeSymbol.GenericParameters) genericParam.Accept(this); foreach (var member in typeSymbol.Members) member.Accept(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs index 830deefc3..d940920e7 100644 --- a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs @@ -8,6 +8,7 @@ using Draco.Compiler.Internal.Symbols.Source; using Draco.Compiler.Internal.Utilities; using Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; +using System.Collections.Generic; namespace Draco.Compiler.Internal.Symbols.Syntax; @@ -63,4 +64,12 @@ private SymbolDocumentation BuildDocumentation() => private FunctionSymbol BuildGetter() => new AutoPropertyGetterSymbol(this.ContainingSymbol, this); private FunctionSymbol? BuildSetter() => new AutoPropertySetterSymbol(this.ContainingSymbol, this); private FieldSymbol BuildBackingField() => new AutoPropertyBackingFieldSymbol(this.ContainingSymbol, this); + + protected internal override sealed IEnumerable GetAdditionalSymbols() + { + // For auto-properties we provide the backing field and the accessors in the same scope + if (this.Getter is not null) yield return this.Getter; + if (this.Setter is not null) yield return this.Setter; + yield return this.BackingField; + } } diff --git a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxFunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxFunctionSymbol.cs index d748c8fc0..d63a029be 100644 --- a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxFunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxFunctionSymbol.cs @@ -22,6 +22,7 @@ internal abstract class SyntaxFunctionSymbol( public override Symbol ContainingSymbol => containingSymbol; public override FunctionDeclarationSyntax DeclaringSyntax => syntax; public override string Name => this.DeclaringSyntax.Name.Text; + public override bool IsStatic => this.ThisParameter is null; public override Api.Semantics.Visibility Visibility => GetVisibilityFromTokenKind(this.DeclaringSyntax.VisibilityModifier?.Kind); @@ -35,6 +36,12 @@ internal abstract class SyntaxFunctionSymbol( public override ImmutableArray Parameters => this.BindParametersIfNeeded(this.DeclaringCompilation!); private ImmutableArray parameters; + /// + /// An optional this parameter, if the function is an instance method. + /// + public ParameterSymbol? ThisParameter => this.BindThisParameterIfNeeded(this.DeclaringCompilation!); + private ParameterSymbol? thisParameter; + public override TypeSymbol ReturnType => this.BindReturnTypeIfNeeded(this.DeclaringCompilation!); private TypeSymbol? returnType; @@ -91,6 +98,16 @@ private ImmutableArray BindGenericParameters(IBinderProvide return genericParams.ToImmutable(); } + protected ParameterSymbol? BindThisParameterIfNeeded(IBinderProvider binderProvider) => + InterlockedUtils.InitializeMaybeNull(ref this.thisParameter, () => this.BindThisParameter(binderProvider)); + + private ParameterSymbol? BindThisParameter(IBinderProvider binderProvider) + { + if (this.DeclaringSyntax.ParameterList.Values.FirstOrDefault() is not ThisParameterSyntax thisSyntax) return null; + + return new SourceThisParameterSymbol(this, thisSyntax); + } + protected ImmutableArray BindParametersIfNeeded(IBinderProvider binderProvider) => InterlockedUtils.InitializeDefault(ref this.parameters, () => this.BindParameters(binderProvider)); @@ -99,9 +116,25 @@ private ImmutableArray BindParameters(IBinderProvider binderPro var parameterSyntaxes = this.DeclaringSyntax.ParameterList.Values.ToList(); var parameters = ImmutableArray.CreateBuilder(); - for (var i = 0; i < parameterSyntaxes.Count; ++i) + // NOTE: If the first parameter is 'this', we skip it + var start = parameterSyntaxes.FirstOrDefault() is ThisParameterSyntax ? 1 : 0; + for (var i = start; i < parameterSyntaxes.Count; ++i) { - var parameterSyntax = parameterSyntaxes[i]; + var syntax = parameterSyntaxes[i]; + if (syntax is ThisParameterSyntax thisSyntax) + { + // NOTE: Do we want to construct an error here, or regular symbol is fine? + // Error, 'this' at an illegal place + var thisSymbol = new SourceThisParameterSymbol(this, thisSyntax); + parameters.Add(thisSymbol); + binderProvider.DiagnosticBag.Add(Diagnostic.Create( + template: SymbolResolutionErrors.ThisParameterNotFirst, + location: thisSyntax.Location)); + continue; + } + + // We assume it's a regular parameter + var parameterSyntax = (NormalParameterSyntax)syntax; var parameterName = parameterSyntax.Name.Text; var usedBefore = parameters.Any(p => p.Name == parameterName); @@ -110,7 +143,7 @@ private ImmutableArray BindParameters(IBinderProvider binderPro // NOTE: We only report later duplicates, no need to report the first instance binderProvider.DiagnosticBag.Add(Diagnostic.Create( template: SymbolResolutionErrors.IllegalShadowing, - location: parameterSyntax.Location, + location: syntax.Location, formatArgs: parameterName)); } @@ -118,7 +151,7 @@ private ImmutableArray BindParameters(IBinderProvider binderPro { binderProvider.DiagnosticBag.Add(Diagnostic.Create( template: SymbolResolutionErrors.VariadicParameterNotLast, - location: parameterSyntax.Location, + location: syntax.Location, formatArgs: parameterName)); } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs index 9d7562c57..2fdf0bc10 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs @@ -1,6 +1,7 @@ using Draco.Compiler.Internal.BoundTree; using Draco.Compiler.Internal.Symbols.Source; using Draco.Compiler.Internal.Symbols.Syntax; +using Draco.Compiler.Internal.Utilities; using System; using System.Collections.Immutable; using System.Threading; @@ -28,6 +29,12 @@ internal sealed class AutoPropertyGetterSymbol( public override BoundStatement Body => LazyInitializer.EnsureInitialized(ref this.body, this.BuildBody); private BoundStatement? body; + /// + /// An optional this parameter, if the getter is an instance method. + /// + public ParameterSymbol? ThisParameter => InterlockedUtils.InitializeMaybeNull(ref this.thisParameter, this.BuildThisParameter); + private ParameterSymbol? thisParameter; + PropertySymbol IPropertyAccessorSymbol.Property => this.Property; public SyntaxAutoPropertySymbol Property { get; } = property; @@ -36,7 +43,14 @@ private BoundStatement BuildBody() => ExpressionStatement(BlockExpression( statements: [ExpressionStatement(ReturnExpression(FieldExpression( receiver: this.IsStatic ? null - : throw new NotImplementedException("TODO: for classes"), + : ParameterExpression(this.ThisParameter!), field: this.Property.BackingField)))], value: BoundUnitExpression.Default)); + + private ParameterSymbol? BuildThisParameter() + { + if (this.IsStatic) return null; + + return new SynthetizedThisParameterSymbol(this); + } } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs index 763a18fc7..5f46de132 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs @@ -4,6 +4,7 @@ using Draco.Compiler.Internal.Utilities; using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Threading; using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; @@ -31,6 +32,12 @@ internal sealed class AutoPropertySetterSymbol( public override BoundStatement Body => LazyInitializer.EnsureInitialized(ref this.body, this.BuildBody); private BoundStatement? body; + /// + /// An optional this parameter, if the getter is an instance method. + /// + public ParameterSymbol? ThisParameter => InterlockedUtils.InitializeMaybeNull(ref this.thisParameter, this.BuildThisParameter); + private ParameterSymbol? thisParameter; + PropertySymbol IPropertyAccessorSymbol.Property => this.Property; public SyntaxAutoPropertySymbol Property { get; } = property; @@ -45,9 +52,16 @@ private BoundStatement BuildBody() => ExpressionStatement(BlockExpression( left: FieldLvalue( receiver: this.IsStatic ? null - : throw new NotImplementedException("TODO: classes"), + : ParameterExpression(this.ThisParameter!), field: this.Property.BackingField), right: ParameterExpression(this.Parameters[^1]))), ExpressionStatement(ReturnExpression(BoundUnitExpression.Default))], value: BoundUnitExpression.Default)); + + private ParameterSymbol? BuildThisParameter() + { + if (this.IsStatic) return null; + + return new SynthetizedThisParameterSymbol(this); + } } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/ConstructorFunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/ConstructorFunctionSymbol.cs index d3e28aff1..a1a8cb08b 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/ConstructorFunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/ConstructorFunctionSymbol.cs @@ -19,6 +19,7 @@ internal sealed class ConstructorFunctionSymbol(FunctionSymbol ctorDefinition) : ctorDefinition.Visibility < this.ReturnType.Visibility ? ctorDefinition.Visibility : this.ReturnType.Visibility; public override SymbolDocumentation Documentation => ctorDefinition.Documentation; internal override string RawDocumentation => ctorDefinition.RawDocumentation; + public override Symbol? ContainingSymbol => ctorDefinition.ContainingSymbol; public override ImmutableArray GenericParameters => InterlockedUtils.InitializeDefault(ref this.genericParameters, this.BuildGenericParameters); diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs new file mode 100644 index 000000000..ebedf1e49 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultConstructorSymbol.cs @@ -0,0 +1,25 @@ +using System.Collections.Immutable; +using Draco.Compiler.Internal.BoundTree; +using static Draco.Compiler.Internal.BoundTree.BoundTreeFactory; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// The default constructor for types without a user-defined constructor. +/// +internal sealed class DefaultConstructorSymbol(TypeSymbol containingSymbol) : FunctionSymbol +{ + public override ImmutableArray Parameters => []; + + public override TypeSymbol ReturnType { get; } = WellKnownTypes.Unit; + + public override bool IsConstructor => true; + public override bool IsStatic => false; + + public override string Name => ".ctor"; + + public override TypeSymbol ContainingSymbol { get; } = containingSymbol; + + public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Public; + public override BoundStatement Body { get; } = ExpressionStatement(ReturnExpression(BoundUnitExpression.Default)); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs index 85145ea63..dbf78607c 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedAliasSymbol.cs @@ -5,7 +5,8 @@ namespace Draco.Compiler.Internal.Symbols.Synthetized; /// internal sealed class SynthetizedAliasSymbol(string name, Symbol substitution) : AliasSymbol { - public override string Name { get; } = name; + public override string MetadataName => this.Substitution.MetadataName; + public override string Name => name; public override Symbol Substitution { get; } = substitution; public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Public; } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs new file mode 100644 index 000000000..88a1537c4 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/SynthetizedThisParameterSymbol.cs @@ -0,0 +1,30 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// A compiler-generated this parameter. +/// +internal sealed class SynthetizedThisParameterSymbol(FunctionSymbol containingSymbol) : ParameterSymbol +{ + public override FunctionSymbol ContainingSymbol => containingSymbol; + public override bool IsThis => true; + + public override TypeSymbol Type => LazyInitializer.EnsureInitialized(ref this.type, this.BuildType); + private TypeSymbol? type; + + private TypeSymbol BuildType() + { + var containingType = this.ContainingSymbol.AncestorChain + .OfType() + .First(); + + if (!containingType.IsGenericDefinition) return containingType; + + var genericArgs = containingType.GenericParameters.Cast().ToImmutableArray(); + return containingType.GenericInstantiate(containingType.ContainingSymbol, genericArgs); + } +} diff --git a/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs index f8396bcd8..b06687ab5 100644 --- a/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs @@ -17,7 +17,7 @@ public override TypeSymbol GenericInstantiate(Symbol? containingSymbol, GenericC ? type : this; - public override Api.Semantics.ITypeSymbol ToApiSymbol() => new Api.Semantics.TypeParameterSymbol(this); + public override ITypeSymbol ToApiSymbol() => new Api.Semantics.TypeParameterSymbol(this); public override string ToString() => this.Name; diff --git a/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs b/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs index ad99e1ecc..b23fdd510 100644 --- a/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/TypeSymbol.cs @@ -4,6 +4,7 @@ using System.Linq; using Draco.Compiler.Api.Semantics; using Draco.Compiler.Internal.Symbols.Generic; +using Draco.Compiler.Internal.Symbols.Synthetized; using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Symbols; @@ -207,10 +208,17 @@ public override TypeSymbol GenericInstantiate(Symbol? containingSymbol, GenericC return new TypeInstanceSymbol(containingSymbol, this, context); } - public override Api.Semantics.ITypeSymbol ToApiSymbol() => new Api.Semantics.TypeSymbol(this); + public override ITypeSymbol ToApiSymbol() => new Api.Semantics.TypeSymbol(this); public override void Accept(SymbolVisitor visitor) => visitor.VisitType(this); public override TResult Accept(SymbolVisitor visitor) => visitor.VisitType(this); public override abstract string ToString(); + + protected internal override sealed IEnumerable GetAdditionalSymbols() + { + if (this.IsAbstract) yield break; + // For non-abstract types we provide constructor functions + foreach (var ctor in this.Constructors) yield return new ConstructorFunctionSymbol(ctor); + } } diff --git a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs index 2d8b57c76..8c18c6c11 100644 --- a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs +++ b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs @@ -204,7 +204,7 @@ public IEnumerable GetEnumEqualityMembers(TypeSymbol type) /// /// The reflected type to translate. /// The translated type symbol, or if it's not a translatable primitive type. - public TypeSymbol? TranslatePrmitive(Type type) + public TypeSymbol? TranslatePrimitive(Type type) { if (type == typeof(byte)) return this.SystemByte; if (type == typeof(ushort)) return this.SystemUInt16; diff --git a/src/Draco.Compiler/Internal/Syntax/Lexer.cs b/src/Draco.Compiler/Internal/Syntax/Lexer.cs index 10ab4000c..4dd2cb388 100644 --- a/src/Draco.Compiler/Internal/Syntax/Lexer.cs +++ b/src/Draco.Compiler/Internal/Syntax/Lexer.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Linq; using System.Text; using Draco.Compiler.Api.Diagnostics; using Draco.Compiler.Api.Syntax; @@ -161,29 +160,26 @@ public SyntaxToken Lex() /// /// Lexes tokens that can be found in regular code. /// - /// The lexed . - private Unit LexNormal() + private void LexNormal() { - Unit TakeBasic(TokenKind tokenKind, int length) + void TakeBasic(TokenKind tokenKind, int length) { this.Advance(length); this.tokenBuilder.SetKind(tokenKind); - return default; } - Unit TakeWithText(TokenKind tokenKind, int length) + void TakeWithText(TokenKind tokenKind, int length) { this.tokenBuilder .SetKind(tokenKind) .SetText(this.AdvanceWithText(length)); - return default; } // First check for end of source here if (this.SourceReader.IsEnd) { this.tokenBuilder.SetKind(TokenKind.EndOfInput); - return default; + return; } var mode = this.CurrentMode; @@ -192,60 +188,136 @@ Unit TakeWithText(TokenKind tokenKind, int length) // Punctuation switch (ch) { - case '(': return TakeBasic(TokenKind.ParenOpen, 1); - case ')': return TakeBasic(TokenKind.ParenClose, 1); + case '(': + TakeBasic(TokenKind.ParenOpen, 1); + return; + case ')': + TakeBasic(TokenKind.ParenClose, 1); + return; case '{': if (mode.IsInterpolation) this.PushMode(mode.Kind, 0); - return TakeBasic(TokenKind.CurlyOpen, 1); + TakeBasic(TokenKind.CurlyOpen, 1); + return; case '}': if (mode.IsInterpolation) { this.PopMode(); // If we are not in interpolation anymore, this is an interpolation end token - if (!this.CurrentMode.IsInterpolation) return TakeBasic(TokenKind.InterpolationEnd, 1); + if (!this.CurrentMode.IsInterpolation) + { + TakeBasic(TokenKind.InterpolationEnd, 1); + return; + } } - return TakeBasic(TokenKind.CurlyClose, 1); - case '[': return TakeBasic(TokenKind.BracketOpen, 1); - case ']': return TakeBasic(TokenKind.BracketClose, 1); - + TakeBasic(TokenKind.CurlyClose, 1); + return; + case '[': + TakeBasic(TokenKind.BracketOpen, 1); + return; + case ']': + TakeBasic(TokenKind.BracketClose, 1); + return; case '.': - if (this.Peek(1) == '.' && this.Peek(2) == '.') return TakeBasic(TokenKind.Ellipsis, 3); - return TakeBasic(TokenKind.Dot, 1); - case ',': return TakeBasic(TokenKind.Comma, 1); - case ':': return TakeBasic(TokenKind.Colon, 1); - case ';': return TakeBasic(TokenKind.Semicolon, 1); + if (this.Peek(1) == '.' && this.Peek(2) == '.') + { + TakeBasic(TokenKind.Ellipsis, 3); + return; + } + TakeBasic(TokenKind.Dot, 1); + return; + case ',': + TakeBasic(TokenKind.Comma, 1); + return; + case ':': + TakeBasic(TokenKind.Colon, 1); + return; + case ';': + TakeBasic(TokenKind.Semicolon, 1); + return; case '+': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.PlusAssign, 2); - return TakeBasic(TokenKind.Plus, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.PlusAssign, 2); + return; + } + TakeBasic(TokenKind.Plus, 1); + return; case '-': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.MinusAssign, 2); - return TakeBasic(TokenKind.Minus, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.MinusAssign, 2); + return; + } + TakeBasic(TokenKind.Minus, 1); + return; case '*': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.StarAssign, 2); - return TakeBasic(TokenKind.Star, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.StarAssign, 2); + return; + } + TakeBasic(TokenKind.Star, 1); + return; case '/': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.SlashAssign, 2); - return TakeBasic(TokenKind.Slash, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.SlashAssign, 2); + return; + } + TakeBasic(TokenKind.Slash, 1); + return; case '<': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.LessEqual, 2); - return TakeBasic(TokenKind.LessThan, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.LessEqual, 2); + return; + } + TakeBasic(TokenKind.LessThan, 1); + return; case '>': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.GreaterEqual, 2); - return TakeBasic(TokenKind.GreaterThan, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.GreaterEqual, 2); + return; + } + TakeBasic(TokenKind.GreaterThan, 1); + return; case '=': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.Equal, 2); - return TakeBasic(TokenKind.Assign, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.Equal, 2); + return; + } + TakeBasic(TokenKind.Assign, 1); + return; case '!': - if (this.Peek(1) == '=') return TakeBasic(TokenKind.NotEqual, 2); - return TakeBasic(TokenKind.CNot, 1); - case '%': return TakeBasic(TokenKind.CMod, 1); + if (this.Peek(1) == '=') + { + TakeBasic(TokenKind.NotEqual, 2); + return; + } + TakeBasic(TokenKind.CNot, 1); + return; + case '%': + TakeBasic(TokenKind.CMod, 1); + return; case '|': - if (this.Peek(1) == '|') return TakeBasic(TokenKind.COr, 2); + if (this.Peek(1) == '|') + { + TakeBasic(TokenKind.COr, 2); + return; + } break; case '&': - if (this.Peek(1) == '&') return TakeBasic(TokenKind.CAnd, 2); + if (this.Peek(1) == '&') + { + TakeBasic(TokenKind.CAnd, 2); + return; + } break; - case '@': return TakeBasic(TokenKind.AtSign, 1); + case '@': + TakeBasic(TokenKind.AtSign, 1); + return; } // Numeric literals @@ -265,7 +337,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.LiteralInteger) .SetText($"0{radix}{view}") .SetValue(value); - return default; + return; } var offset = 1; var isFloat = false; @@ -293,7 +365,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) this.tokenBuilder .SetKind(TokenKind.LiteralFloat) .SetText(this.Advance(offset).Span.ToString()); - return default; + return; } while (IsDigit(this.Peek(offset))) ++offset; } @@ -307,7 +379,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.LiteralFloat) .SetText(floatView.ToString()) .SetValue(floatValue); - return default; + return; } // Regular integer @@ -316,7 +388,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.LiteralInteger) .SetText(decimalView) .SetValue(decimalValue); - return default; + return; } // Identifier-like tokens @@ -329,11 +401,13 @@ Unit TakeWithText(TokenKind tokenKind, int length) var tokenKind = ident.Span switch { "and" => TokenKind.KeywordAnd, + "class" => TokenKind.KeywordClass, "else" => TokenKind.KeywordElse, "false" => TokenKind.KeywordFalse, "field" => TokenKind.KeywordField, "for" => TokenKind.KeywordFor, "func" => TokenKind.KeywordFunc, + "global" => TokenKind.KeywordGlobal, "goto" => TokenKind.KeywordGoto, "if" => TokenKind.KeywordIf, "import" => TokenKind.KeywordImport, @@ -346,8 +420,10 @@ Unit TakeWithText(TokenKind tokenKind, int length) "public" => TokenKind.KeywordPublic, "rem" => TokenKind.KeywordRem, "return" => TokenKind.KeywordReturn, + "this" => TokenKind.KeywordThis, "true" => TokenKind.KeywordTrue, "val" => TokenKind.KeywordVal, + "value" => TokenKind.KeywordValue, "var" => TokenKind.KeywordVar, "while" => TokenKind.KeywordWhile, _ => TokenKind.Identifier, @@ -363,7 +439,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.Identifier) .SetText(identStr) .SetValue(identStr); - return default; + return; } else { @@ -371,7 +447,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) this.tokenBuilder.SetKind(tokenKind); if (tokenKind == TokenKind.KeywordTrue) this.tokenBuilder.SetValue(true); if (tokenKind == TokenKind.KeywordFalse) this.tokenBuilder.SetValue(false); - return default; + return; } } @@ -391,7 +467,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.LiteralCharacter) .SetText(errText) .SetValue(new Rune(' ')); - return default; + return; } var resultChar = default(Rune); if (ch2 == '\\') @@ -449,7 +525,7 @@ Unit TakeWithText(TokenKind tokenKind, int length) .SetKind(TokenKind.LiteralCharacter) .SetText(text) .SetValue(resultChar); - return default; + return; } // String literal starts @@ -463,23 +539,26 @@ Unit TakeWithText(TokenKind tokenKind, int length) { // Mutli-line string opening quotes this.PushMode(ModeKind.MultiLineString, extendedDelims); - return TakeWithText(TokenKind.MultiLineStringStart, offset + 3); + TakeWithText(TokenKind.MultiLineStringStart, offset + 3); + return; } // Single-line string opening quote this.PushMode(ModeKind.LineString, extendedDelims); - return TakeWithText(TokenKind.LineStringStart, offset + 1); + TakeWithText(TokenKind.LineStringStart, offset + 1); + return; } } // Unknown - return TakeWithText(TokenKind.Unknown, 1); + TakeWithText(TokenKind.Unknown, 1); + return; } /// /// Lexes a token that can be part of a string. /// /// The lexed string . - private Unit LexString() + private void LexString() { // Get the largest continuous sequence without linebreaks or interpolation var mode = this.CurrentMode; @@ -490,7 +569,7 @@ private Unit LexString() if (this.SourceReader.IsEnd) { this.tokenBuilder.SetKind(TokenKind.EndOfInput); - return default; + return; } // NOTE: We are checking end of input differently here, because SourceReader.IsEnd is based on its @@ -503,7 +582,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } // Check for closing quotes @@ -553,7 +632,7 @@ private Unit LexString() this.tokenBuilder .SetKind(tokenKind) .SetText(this.AdvanceWithText(endLength)); - return default; + return; } else { @@ -563,7 +642,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } } @@ -587,7 +666,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } // Interpolation @@ -603,7 +682,7 @@ private Unit LexString() this.tokenBuilder .SetKind(TokenKind.InterpolationStart) .SetText(this.AdvanceWithText(mode.ExtendedDelims + 2)); - return default; + return; } else { @@ -613,7 +692,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } } @@ -634,7 +713,7 @@ private Unit LexString() .SetKind(TokenKind.StringNewline) .SetText(this.AdvanceWithText(mode.ExtendedDelims + 1 + whiteCharOffset + length)) .SetValue(string.Empty); - return default; + return; } else { @@ -643,7 +722,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } } } @@ -656,7 +735,7 @@ private Unit LexString() .SetKind(TokenKind.EscapeSequence) .SetText(this.AdvanceWithText(offset)) .SetValue(escaped); - return default; + return; } not_escape_sequence: @@ -676,7 +755,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } else { @@ -705,7 +784,7 @@ private Unit LexString() .SetKind(TokenKind.MultiLineStringEnd) .SetText(this.AdvanceWithText(3 + mode.ExtendedDelims)); this.ParseTrailingTriviaList(); - return default; + return; } not_string_end2: // Just a regular newline, more content to follow @@ -714,7 +793,7 @@ private Unit LexString() .SetKind(TokenKind.StringNewline) .SetText(stringNewlineText) .SetValue(stringNewlineText); - return default; + return; } else { @@ -723,7 +802,7 @@ private Unit LexString() .SetKind(TokenKind.StringContent) .SetText(this.AdvanceWithText(offset)) .SetValue(this.valueBuilder.ToString()); - return default; + return; } } } diff --git a/src/Draco.Compiler/Internal/Syntax/Parser.cs b/src/Draco.Compiler/Internal/Syntax/Parser.cs index 3acf1610e..f2cf3189d 100644 --- a/src/Draco.Compiler/Internal/Syntax/Parser.cs +++ b/src/Draco.Compiler/Internal/Syntax/Parser.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -39,10 +38,15 @@ internal sealed class Parser( private enum DeclarationContext { /// - /// Global, like in a compilation unit, module, class, ... + /// Global, like in a compilation unit or module. /// Global, + /// + /// Inside a class declaration body. + /// + Class, + /// /// Local to a function body/expression/code-block. /// @@ -191,12 +195,15 @@ private ExpressionParserDelegate BinaryRight(params TokenKind[] operators) => le /// private static readonly TokenKind[] declarationStarters = [ + TokenKind.KeywordClass, TokenKind.KeywordImport, TokenKind.KeywordField, TokenKind.KeywordFunc, + TokenKind.KeywordGlobal, TokenKind.KeywordModule, TokenKind.KeywordVar, TokenKind.KeywordVal, + TokenKind.KeywordValue, ]; /// @@ -236,6 +243,7 @@ private ExpressionParserDelegate BinaryRight(params TokenKind[] operators) => le TokenKind.Plus, TokenKind.Minus, TokenKind.Star, + TokenKind.KeywordThis, ]; /// @@ -379,15 +387,19 @@ private DeclarationSyntax ParseDeclaration(DeclarationContext context) case TokenKind.KeywordImport: return this.ParseImportDeclaration(attributes, visibility); + case TokenKind.KeywordValue: + case TokenKind.KeywordClass: + return this.ParseClassDeclaration(attributes, visibility); + case TokenKind.KeywordFunc: return this.ParseFunctionDeclaration(attributes, visibility, context); case TokenKind.KeywordModule: return this.ParseModuleDeclaration(attributes, visibility, context); - case TokenKind.KeywordVar: case TokenKind.KeywordVal: case TokenKind.KeywordField: + case TokenKind.KeywordGlobal: return this.ParseVariableDeclaration(attributes, visibility, context); case TokenKind.Identifier when this.PeekKind(1) == TokenKind.Colon: @@ -528,11 +540,81 @@ private ImportPathSyntax ParseImportPath() return result; } + /// + /// Parses a class declaration. + /// + /// Optional attributes on the class. + /// Optional visibility modifier token. + /// The parsed . + private ClassDeclarationSyntax ParseClassDeclaration(SyntaxList? attributes, SyntaxToken? visibility) + { + this.Matches(TokenKind.KeywordValue, out var valueModifier); + + // Class keyword and name of the class + var classKeyword = this.Expect(TokenKind.KeywordClass); + var name = this.Expect(TokenKind.Identifier); + + // Optional generics + var generics = null as GenericParameterListSyntax; + if (this.PeekKind() == TokenKind.LessThan) generics = this.ParseGenericParameterList(); + + var body = this.ParseClassBody(); + + return new ClassDeclarationSyntax( + attributes, + visibility, + valueModifier, + classKeyword, + name, + generics, + body); + } + + /// + /// Parses the body of a class. + /// + /// The parsed . + private ClassBodySyntax ParseClassBody() + { + if (this.Matches(TokenKind.Semicolon, out var semicolon)) + { + return new EmptyClassBodySyntax(semicolon); + } + else if (this.Matches(TokenKind.CurlyOpen, out var openBrace)) + { + var decls = SyntaxList.CreateBuilder(); + while (true) + { + // Break on the end of the block + if (this.PeekKind() is TokenKind.EndOfInput or TokenKind.CurlyClose) break; + + // Parse a declaration + var decl = this.ParseDeclaration(DeclarationContext.Class); + decls.Add(decl); + } + var closeBrace = this.Expect(TokenKind.CurlyClose); + return new BlockClassBodySyntax(openBrace, decls.ToSyntaxList(), closeBrace); + } + else + { + var input = this.Synchronize(t => t.Kind switch + { + TokenKind.Semicolon or TokenKind.CurlyClose => false, + _ when this.IsDeclarationStarter(DeclarationContext.Class) => false, + _ => true, + }); + var info = DiagnosticInfo.Create(SyntaxErrors.UnexpectedInput, formatArgs: "class body"); + var node = new UnexpectedClassBodySyntax(input); + this.AddDiagnostic(node, info); + return node; + } + } + /// /// Parses a . /// - /// The attributes on the import. - /// The visibility modifier on the import. + /// The attributes on the variable. + /// The visibility modifier on the variable. /// The current declaration context. /// The parsed . private VariableDeclarationSyntax ParseVariableDeclaration( @@ -551,9 +633,16 @@ private VariableDeclarationSyntax ParseVariableDeclaration( this.AddDiagnostic(visibility, info); } + // Global modifier + this.Matches(TokenKind.KeywordGlobal, out var globalModifier); + if (context != DeclarationContext.Class && globalModifier is not null) + { + var info = DiagnosticInfo.Create(SyntaxErrors.UnexpectedGlobalModifier); + this.AddDiagnostic(globalModifier, info); + } + // Field modifier - var fieldModifier = null as SyntaxToken; - this.Matches(TokenKind.KeywordField, out fieldModifier); + this.Matches(TokenKind.KeywordField, out var fieldModifier); if (context == DeclarationContext.Local && fieldModifier is not null) { var info = DiagnosticInfo.Create(SyntaxErrors.UnexpectedFieldModifier); @@ -579,7 +668,16 @@ private VariableDeclarationSyntax ParseVariableDeclaration( // Eat semicolon at the end of declaration var semicolon = this.Expect(TokenKind.Semicolon); - return new VariableDeclarationSyntax(attributes, visibility, fieldModifier, keyword, identifier, type, assignment, semicolon); + return new VariableDeclarationSyntax( + attributes, + visibility, + globalModifier, + fieldModifier, + keyword, + identifier, + type, + assignment, + semicolon); } /// @@ -620,7 +718,7 @@ private FunctionDeclarationSyntax ParseFunctionDeclaration( TypeSpecifierSyntax? returnType = null; if (this.PeekKind() == TokenKind.Colon) returnType = this.ParseTypeSpecifier(); - var body = this.ParseFunctionBody(); + var body = this.ParseFunctionBody(context); return new FunctionDeclarationSyntax( attributes, @@ -739,15 +837,24 @@ private DeclarationSyntax ParseLabelDeclaration( /// /// Parses a function parameter. /// - /// The parsed . + /// The parsed . private ParameterSyntax ParseParameter() { var attributes = this.ParseAttributeList(); + if (this.Matches(TokenKind.KeywordThis, out var thisKeyWord)) + { + if (attributes is not null) + { + var diag = DiagnosticInfo.Create(SyntaxErrors.UnexpectedAttributeList, formatArgs: "this parameter"); + this.AddDiagnostic(attributes, diag); + } + return new ThisParameterSyntax(thisKeyWord); + } this.Matches(TokenKind.Ellipsis, out var variadic); var name = this.Expect(TokenKind.Identifier); var colon = this.Expect(TokenKind.Colon); var type = this.ParseType(); - return new(attributes, variadic, name, colon, type); + return new NormalParameterSyntax(attributes, variadic, name, colon, type); } /// @@ -785,8 +892,9 @@ private GenericParameterSyntax ParseGenericParameter() /// /// Parses a function body. /// + /// The current context we are in. /// The parsed . - private FunctionBodySyntax ParseFunctionBody() + private FunctionBodySyntax ParseFunctionBody(DeclarationContext ctx) { if (this.Matches(TokenKind.Assign, out var assign)) { @@ -809,7 +917,7 @@ private FunctionBodySyntax ParseFunctionBody() var input = this.Synchronize(t => t.Kind switch { TokenKind.Semicolon or TokenKind.CurlyClose => false, - _ when this.IsDeclarationStarter(DeclarationContext.Global) => false, + _ when this.IsDeclarationStarter(ctx) => false, _ => true, }); var info = DiagnosticInfo.Create(SyntaxErrors.UnexpectedInput, formatArgs: "function body"); @@ -1243,6 +1351,11 @@ private ExpressionSyntax ParseAtomExpression(int level) var value = this.Advance(); return new LiteralExpressionSyntax(value); } + case TokenKind.KeywordThis: + { + var keyword = this.Advance(); + return new ThisExpressionSyntax(keyword); + } case TokenKind.LineStringStart: return this.ParseLineString(); case TokenKind.MultiLineStringStart: diff --git a/src/Draco.Compiler/Internal/Syntax/Syntax.xml b/src/Draco.Compiler/Internal/Syntax/Syntax.xml index b06a758fc..b153f6662 100644 --- a/src/Draco.Compiler/Internal/Syntax/Syntax.xml +++ b/src/Draco.Compiler/Internal/Syntax/Syntax.xml @@ -21,11 +21,13 @@ + + @@ -38,8 +40,10 @@ + + @@ -332,6 +336,123 @@ + + + A class declaration. + + + + + The attributes of this class. + + + + + + The visibility modifier keyword possibly starting the declaration. + + + + + + + + + A modifier for making a class a value-type. + + + + + + + + The 'class' keyword starting the delcaration. + + + + + + + + The name of the declared class. + + + + + + + + The list of generic parameters, in case the class introduces generics. + + + + + + The body of the class. + + + + + + + A class declaration body. + + + + + + A semicolon-terminated empty class body. + + + + + The semicolon terminating the class body. + + + + + + + + + A block class body with multiple declarations within braces. + + + + + The opening brace token. + + + + + + + + All declaration syntaxes within the class. + + + + + + The closing brace token. + + + + + + + + + Unexpected input in class body context. + + + + + The unexpected syntax nodes. + + + + A function declaration. @@ -402,7 +523,26 @@ - + + + A single parameter in a function parameter list. + + + + + + The this parameter at the first place in a parameter list. + + + + + The 'this' keyword. + + + + + + A single parameter in a function parameter list. @@ -412,6 +552,7 @@ The attributes of this parameter. + The ellipsis token, in case it's variadic. @@ -564,6 +705,14 @@ + + + The optional global modifier that marks this variable as a global (static). + + + + + The field modifier keyword to specify the variable to be manifested as a field. @@ -1248,6 +1397,20 @@ + + + The expression referencing the receiver of a member function. + + + + + The 'this' keyword. + + + + + + An expression between parenthesis. diff --git a/src/Draco.Compiler/Internal/Syntax/SyntaxErrors.cs b/src/Draco.Compiler/Internal/Syntax/SyntaxErrors.cs index 3b629ee02..ce9d604f2 100644 --- a/src/Draco.Compiler/Internal/Syntax/SyntaxErrors.cs +++ b/src/Draco.Compiler/Internal/Syntax/SyntaxErrors.cs @@ -191,4 +191,13 @@ internal static class SyntaxErrors severity: DiagnosticSeverity.Error, format: "unexpected field modifier in a local context", code: Code(20)); + + /// + /// There is a global modifier in a global or local context. + /// + public static readonly DiagnosticTemplate UnexpectedGlobalModifier = DiagnosticTemplate.Create( + title: "unexpected global modifier", + severity: DiagnosticSeverity.Error, + format: "unexpected global modifier outside of class declaration", + code: Code(21)); } diff --git a/src/Draco.Compiler/Internal/Utilities/Unit.cs b/src/Draco.Compiler/Internal/Utilities/Unit.cs deleted file mode 100644 index e2e8e94b0..000000000 --- a/src/Draco.Compiler/Internal/Utilities/Unit.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Draco.Compiler.Internal.Utilities; - -/// -/// Utility type for returning nothing as an expression. -/// Usable instead of 'void'. -/// -[ExcludeFromCodeCoverage] -internal readonly record struct Unit; diff --git a/src/Draco.DebugAdapter/DracoDebugAdapter.cs b/src/Draco.DebugAdapter/DracoDebugAdapter.cs index fa850877f..22eab28cb 100644 --- a/src/Draco.DebugAdapter/DracoDebugAdapter.cs +++ b/src/Draco.DebugAdapter/DracoDebugAdapter.cs @@ -219,7 +219,7 @@ public Task SetBreakpointsAsync(SetBreakpointsArguments return Task.FromResult(new SetBreakpointsResponse() { Breakpoints = args.Breakpoints? - .Select(bp => new Dap.Model.Breakpoint() + .Select(bp => new Breakpoint() { Verified = false, Id = this.translator.AllocateId(bp), @@ -235,11 +235,11 @@ public Task SetBreakpointsAsync(SetBreakpointsArguments }); } - private List SetBreakpointsImpl(SetBreakpointsArguments args) + private List SetBreakpointsImpl(SetBreakpointsArguments args) { if (this.debugger is null) throw new InvalidOperationException("cannot set up breakpoints without a running debugger"); - var result = new List(); + var result = new List(); var source = this.debugger.MainModule.SourceFiles .FirstOrDefault(s => PathEqualityComparer.Instance.Equals(s.Uri.AbsolutePath, args.Source.Path)); if (args.Breakpoints is not null && source is not null) diff --git a/src/Draco.Debugger/RuntimeValues/ObjectValue.cs b/src/Draco.Debugger/RuntimeValues/ObjectValue.cs index 49b96d1ed..220fee40f 100644 --- a/src/Draco.Debugger/RuntimeValues/ObjectValue.cs +++ b/src/Draco.Debugger/RuntimeValues/ObjectValue.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using ClrDebug; -using Draco.Debugger; namespace Draco.Debugger.RuntimeValues; diff --git a/src/Draco.Fuzzing.Tui/Addons/ExportLcovAddon.cs b/src/Draco.Fuzzing.Tui/Addons/ExportLcovAddon.cs index cf6f11688..48f9f93d7 100644 --- a/src/Draco.Fuzzing.Tui/Addons/ExportLcovAddon.cs +++ b/src/Draco.Fuzzing.Tui/Addons/ExportLcovAddon.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Linq; using Draco.Coverage; diff --git a/src/Draco.LanguageServer/Capabilities/Rename.cs b/src/Draco.LanguageServer/Capabilities/Rename.cs index 33d492796..c756325cf 100644 --- a/src/Draco.LanguageServer/Capabilities/Rename.cs +++ b/src/Draco.LanguageServer/Capabilities/Rename.cs @@ -85,7 +85,7 @@ private static IEnumerable FindAllAppearances( private static Lsp.Model.TextEdit RenameNode(SyntaxNode original, string name) => original switch { - ParameterSyntax p => RenameToken(p.Name, name), + NormalParameterSyntax p => RenameToken(p.Name, name), GenericParameterSyntax g => RenameToken(g.Name, name), FunctionDeclarationSyntax f => RenameToken(f.Name, name), VariableDeclarationSyntax v => RenameToken(v.Name, name), diff --git a/src/Draco.SourceGeneration/Lsp/Translator.cs b/src/Draco.SourceGeneration/Lsp/Translator.cs index e536d63d5..699e5c4f1 100644 --- a/src/Draco.SourceGeneration/Lsp/Translator.cs +++ b/src/Draco.SourceGeneration/Lsp/Translator.cs @@ -24,7 +24,7 @@ internal sealed class Translator(Ts.MetaModel sourceModel) /// /// The name of the type. /// The reflected type. - public void AddBuiltinType(string name, System.Type type) => + public void AddBuiltinType(string name, Type type) => this.AddBuiltinType(name, type.FullName); /// diff --git a/src/Draco.SourceGeneration/TemplateUtils.cs b/src/Draco.SourceGeneration/TemplateUtils.cs index 1cd10a21d..ce798a54a 100644 --- a/src/Draco.SourceGeneration/TemplateUtils.cs +++ b/src/Draco.SourceGeneration/TemplateUtils.cs @@ -28,7 +28,8 @@ internal static class TemplateUtils "operator", "object", "bool", - "string" + "string", + "this", ]; ///