From 9ff6158bcf0c512314783f03d5c4bce91ebc0d82 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Sun, 27 Oct 2024 14:57:01 +0100 Subject: [PATCH] Added script module support --- .../Internal/Binding/Binder_Symbol.cs | 57 +++++++++++++++- .../Script/ScriptAutoPropertySymbol.cs | 32 +++++++++ .../Symbols/Script/ScriptModuleSymbol.cs | 65 +++++++++++-------- .../Source/SourceAutoPropertySymbol.cs | 18 ----- .../Syntax/SyntaxAutoPropertySymbol.cs | 20 ++++++ .../AutoProperty/AutoPropertyGetterSymbol.cs | 5 +- .../AutoProperty/AutoPropertySetterSymbol.cs | 5 +- 7 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Symbols/Script/ScriptAutoPropertySymbol.cs diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Symbol.cs b/src/Draco.Compiler/Internal/Binding/Binder_Symbol.cs index ca655b1ef..4512d006f 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Symbol.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Symbol.cs @@ -157,14 +157,26 @@ public virtual ScriptBinding BindScript(ScriptModuleSymbol module, DiagnosticBag // Imports are skipped if (decl is ImportDeclarationSyntax) continue; // Globals mean an assignment into the eval function - if (decl is VariableDeclarationSyntax varDecl) + if (decl is VariableDeclarationSyntax { FieldModifier: not null } varDecl) { // Retrieve the symbol var symbol = module.Members .OfType() .First(g => g.DeclaringSyntax == varDecl); - BindGlobal(symbol); + BindGlobalField(symbol); + + continue; + } + // TODO: Copy-pasta from above + if (decl is VariableDeclarationSyntax { FieldModifier: null } varDecl2) + { + // Retrieve the symbol + var symbol = module.Members + .OfType() + .First(g => g.DeclaringSyntax == varDecl2); + + BindGlobalProperty(symbol); continue; } @@ -221,7 +233,7 @@ public virtual ScriptBinding BindScript(ScriptModuleSymbol module, DiagnosticBag value: BoundUnitExpression.Default)), EvalType: evalType); - void BindGlobal(ScriptFieldSymbol symbol) + void BindGlobalField(ScriptFieldSymbol symbol) { var typeSyntax = symbol.DeclaringSyntax.Type; var valueSyntax = symbol.DeclaringSyntax.Value; @@ -259,6 +271,45 @@ void BindGlobal(ScriptFieldSymbol symbol) }); } + // TODO: Copypasta + void BindGlobalProperty(ScriptAutoPropertySymbol symbol) + { + var typeSyntax = symbol.DeclaringSyntax.Type; + var valueSyntax = symbol.DeclaringSyntax.Value; + + var type = typeSyntax is null ? null : this.BindTypeToTypeSymbol(typeSyntax.Type, diagnostics); + var valueTask = valueSyntax is null ? null : this.BindExpression(valueSyntax.Value, solver, diagnostics); + + // Infer declared type + var declaredType = type ?? solver.AllocateTypeVariable(); + + // Unify with the type declared on the symbol + ConstraintSolver.UnifyAsserted(declaredType, symbol.Type); + + // Add assignability constraint, if needed + if (valueTask is not null) + { + solver.Assignable( + declaredType, + valueTask.GetResultType(valueSyntax!.Value, solver, diagnostics), + valueSyntax.Value); + } + + fillerTasks.Add(() => + { + var assignedValue = valueTask?.Result; + if (assignedValue is not null) + { + // Add the assignment to the eval function + evalFuncStatements.Add(ExpressionStatement(AssignmentExpression( + left: FieldLvalue(receiver: null, field: symbol.BackingField), + right: assignedValue))); + } + + globalBindings.Add(symbol.DeclaringSyntax, new(declaredType, assignedValue)); + }); + } + void BindFunction(ScriptFunctionSymbol symbol) { var binder = this.GetBinder(symbol.DeclaringSyntax); diff --git a/src/Draco.Compiler/Internal/Symbols/Script/ScriptAutoPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Script/ScriptAutoPropertySymbol.cs new file mode 100644 index 000000000..1bd04f5f2 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Script/ScriptAutoPropertySymbol.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Binding; +using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Symbols.Syntax; +using Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.Symbols.Script; + +/// +/// A property defined inside a script. +/// +/// Properties are special in script context, as global properties (global variables) can be inferred from other statements, +/// and their initialization intermixes with the rest of the script. +/// +internal sealed class ScriptAutoPropertySymbol( + ScriptModuleSymbol containingSymbol, + VariableDeclarationSyntax syntax) : SyntaxAutoPropertySymbol(containingSymbol, syntax), ISourceSymbol +{ + public override TypeSymbol Type => this.type ??= new TypeVariable(1); + private TypeSymbol? type; + + public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Public; + + public override void Bind(IBinderProvider binderProvider) => containingSymbol.Bind(binderProvider); +} diff --git a/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs index 5863de2c8..2a5380c82 100644 --- a/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Script/ScriptModuleSymbol.cs @@ -84,26 +84,30 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) // Non-declaration statements are compiled into an initializer method if (statement is not DeclarationStatementSyntax decl) continue; - // Build the declaration - var member = this.BuildMember(decl.Declaration); - if (member is null) continue; - - 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; - - // 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)); + // 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)); + } } // We add a function to evaluate the script @@ -133,18 +137,27 @@ private ScriptBinding BindScriptBindings(IBinderProvider binderProvider) return binder.BindScript(this, binderProvider.DiagnosticBag); } - private Symbol? BuildMember(DeclarationSyntax decl) => decl switch + private IEnumerable BuildMember(DeclarationSyntax decl) => decl switch { ImportDeclarationSyntax - or UnexpectedDeclarationSyntax => null, - FunctionDeclarationSyntax f => this.BuildFunction(f), - VariableDeclarationSyntax v => this.BuildGlobal(v), - ModuleDeclarationSyntax m => this.BuildModule(m), + 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)], _ => throw new ArgumentOutOfRangeException(nameof(decl)), }; private ScriptFunctionSymbol BuildFunction(FunctionDeclarationSyntax syntax) => new(this, syntax); - private ScriptFieldSymbol BuildGlobal(VariableDeclarationSyntax 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) { diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs index 2ef575eb5..4e489d3f8 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceAutoPropertySymbol.cs @@ -21,20 +21,6 @@ internal sealed class SourceAutoPropertySymbol( public override TypeSymbol Type => this.BindTypeAndValueIfNeeded(this.DeclaringCompilation!).Type; public BoundExpression? Value => this.BindTypeAndValueIfNeeded(this.DeclaringCompilation!).Value; - public override FunctionSymbol Getter => LazyInitializer.EnsureInitialized(ref this.getter, this.BuildGetter); - private FunctionSymbol? getter; - - public override FunctionSymbol? Setter => this.DeclaringSyntax.Keyword.Kind == TokenKind.KeywordVal - ? null - : InterlockedUtils.InitializeMaybeNull(ref this.setter, this.BuildSetter); - private FunctionSymbol? setter; - - /// - /// The backing field of this auto-prop. - /// - public FieldSymbol BackingField => LazyInitializer.EnsureInitialized(ref this.backingField, this.BuildBackingField); - private FieldSymbol? backingField; - // IMPORTANT: flag is type, needs to be written last // NOTE: We check the TYPE here, as value is nullable private bool NeedsBuild => Volatile.Read(ref this.type) is null; @@ -80,8 +66,4 @@ private GlobalBinding BindTypeAndValue(IBinderProvider binderProvider) var binder = binderProvider.GetBinder(this.DeclaringSyntax); return binder.BindGlobal(this, binderProvider.DiagnosticBag); } - - private FunctionSymbol BuildGetter() => new AutoPropertyGetterSymbol(this.ContainingSymbol, this); - private FunctionSymbol? BuildSetter() => new AutoPropertySetterSymbol(this.ContainingSymbol, this); - private FieldSymbol BuildBackingField() => new AutoPropertyBackingFieldSymbol(this.ContainingSymbol, this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs index 256a18408..830deefc3 100644 --- a/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Syntax/SyntaxAutoPropertySymbol.cs @@ -6,6 +6,8 @@ using System.Threading; using System; using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Utilities; +using Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; namespace Draco.Compiler.Internal.Symbols.Syntax; @@ -31,6 +33,20 @@ internal abstract class SyntaxAutoPropertySymbol : PropertySymbol, ISourceSymbol internal override string RawDocumentation => this.DeclaringSyntax.Documentation; + public override FunctionSymbol Getter => LazyInitializer.EnsureInitialized(ref this.getter, this.BuildGetter); + private FunctionSymbol? getter; + + public override FunctionSymbol? Setter => this.DeclaringSyntax.Keyword.Kind == TokenKind.KeywordVal + ? null + : InterlockedUtils.InitializeMaybeNull(ref this.setter, this.BuildSetter); + private FunctionSymbol? setter; + + /// + /// The backing field of this auto-prop. + /// + public FieldSymbol BackingField => LazyInitializer.EnsureInitialized(ref this.backingField, this.BuildBackingField); + private FieldSymbol? backingField; + protected SyntaxAutoPropertySymbol(Symbol containingSymbol, VariableDeclarationSyntax syntax) { if (syntax.FieldModifier is not null) throw new ArgumentException("a property must not have the field modifier", nameof(syntax)); @@ -43,4 +59,8 @@ protected SyntaxAutoPropertySymbol(Symbol containingSymbol, VariableDeclarationS private SymbolDocumentation BuildDocumentation() => MarkdownDocumentationExtractor.Extract(this); + + private FunctionSymbol BuildGetter() => new AutoPropertyGetterSymbol(this.ContainingSymbol, this); + private FunctionSymbol? BuildSetter() => new AutoPropertySetterSymbol(this.ContainingSymbol, this); + private FieldSymbol BuildBackingField() => new AutoPropertyBackingFieldSymbol(this.ContainingSymbol, this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs index a78df4280..9d7562c57 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertyGetterSymbol.cs @@ -1,5 +1,6 @@ using Draco.Compiler.Internal.BoundTree; using Draco.Compiler.Internal.Symbols.Source; +using Draco.Compiler.Internal.Symbols.Syntax; using System; using System.Collections.Immutable; using System.Threading; @@ -12,7 +13,7 @@ namespace Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; /// internal sealed class AutoPropertyGetterSymbol( Symbol containingSymbol, - SourceAutoPropertySymbol property) : FunctionSymbol, IPropertyAccessorSymbol + SyntaxAutoPropertySymbol property) : FunctionSymbol, IPropertyAccessorSymbol { public override Symbol ContainingSymbol { get; } = containingSymbol; @@ -28,7 +29,7 @@ internal sealed class AutoPropertyGetterSymbol( private BoundStatement? body; PropertySymbol IPropertyAccessorSymbol.Property => this.Property; - public SourceAutoPropertySymbol Property { get; } = property; + public SyntaxAutoPropertySymbol Property { get; } = property; private BoundStatement BuildBody() => ExpressionStatement(BlockExpression( locals: [], diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs index a7274ef38..763a18fc7 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/AutoProperty/AutoPropertySetterSymbol.cs @@ -1,5 +1,6 @@ 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; @@ -13,7 +14,7 @@ namespace Draco.Compiler.Internal.Symbols.Synthetized.AutoProperty; /// internal sealed class AutoPropertySetterSymbol( Symbol containingSymbol, - SourceAutoPropertySymbol property) : FunctionSymbol, IPropertyAccessorSymbol + SyntaxAutoPropertySymbol property) : FunctionSymbol, IPropertyAccessorSymbol { public override Symbol ContainingSymbol { get; } = containingSymbol; @@ -31,7 +32,7 @@ internal sealed class AutoPropertySetterSymbol( private BoundStatement? body; PropertySymbol IPropertyAccessorSymbol.Property => this.Property; - public SourceAutoPropertySymbol Property { get; } = property; + public SyntaxAutoPropertySymbol Property { get; } = property; private ImmutableArray BuildParameters() => [new SynthetizedParameterSymbol(this, "value", this.Property.Type)];