diff --git a/DMCompiler/Compiler/DM/DMAST.cs b/DMCompiler/Compiler/DM/DMAST.cs index 9ddaadd626..59f0f5c246 100644 --- a/DMCompiler/Compiler/DM/DMAST.cs +++ b/DMCompiler/Compiler/DM/DMAST.cs @@ -2693,34 +2693,42 @@ public override void Visit(DMASTVisitor visitor) { public sealed class DMASTDereference : DMASTExpression { - public enum OperationKind { - Invalid, - - Field, // x.y - FieldSafe, // x?.y - FieldSearch, // x:y - FieldSafeSearch, // x?:y - - Index, // x[y] - IndexSafe, // x?[y] - - Call, // x.y() - CallSafe, // x?.y() - CallSearch, // x:y() - CallSafeSearch, // x?:y() - } - - public struct Operation { - public OperationKind Kind; - - // Field*, Call* - public DMASTIdentifier Identifier; - - // Index* - public DMASTExpression Index; - - // Call* - public DMASTCallParameter[] Parameters; + public abstract class Operation { + /// + /// The location of the operation. + /// + public required Location Location; + /// + /// Whether we should short circuit if the expression we are accessing is null. + /// + public required bool Safe; // x?.y, x?.y() etc + } + + public abstract class NamedOperation : Operation { + /// + /// Name of the identifier. + /// + public required string Identifier; + /// + /// Whether we should check if the variable exists or not. + /// + public required bool NoSearch; // x:y, x:y() + } + + public sealed class FieldOperation : NamedOperation; + + public sealed class IndexOperation : Operation { + /// + /// The index expression that we use to index this expression (constant or otherwise). + /// + public required DMASTExpression Index; // x[y], x?[y] + } + + public sealed class CallOperation : NamedOperation { + /// + /// The parameters that we call this proc with. + /// + public required DMASTCallParameter[] Parameters; // x.y(), } public DMASTExpression Expression; diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index d9c2554a89..912b03f329 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; using DMCompiler.Compiler.DMPreprocessor; using OpenDreamShared.Compiler; using OpenDreamShared.Dream; +using System; +using System.Collections.Generic; +using System.Linq; using String = System.String; namespace DMCompiler.Compiler.DM { @@ -2215,15 +2215,13 @@ private void BracketWhitespace() { Token token = Current(); // Check for a valid deref operation token - { - if (!Check(DereferenceTypes)) { - Whitespace(); + if (!Check(DereferenceTypes)) { + Whitespace(); - token = Current(); + token = Current(); - if (!Check(WhitespacedDereferenceTypes)) { - break; - } + if (!Check(WhitespacedDereferenceTypes)) { + break; } } @@ -2254,46 +2252,49 @@ private void BracketWhitespace() { break; } - DMASTDereference.Operation operation = new() { - Kind = DMASTDereference.OperationKind.Invalid, - }; + DMASTDereference.Operation operation; switch (token.Type) { case TokenType.DM_Period: case TokenType.DM_QuestionPeriod: case TokenType.DM_Colon: case TokenType.DM_QuestionColon: { - DMASTIdentifier identifier = Identifier(); + var identifier = Identifier(); - operation.Kind = token.Type switch { - TokenType.DM_Period => DMASTDereference.OperationKind.Field, - TokenType.DM_QuestionPeriod => DMASTDereference.OperationKind.FieldSafe, - TokenType.DM_Colon => DMASTDereference.OperationKind.FieldSearch, - TokenType.DM_QuestionColon => DMASTDereference.OperationKind.FieldSafeSearch, - _ => throw new InvalidOperationException(), - }; - - operation.Identifier = identifier; + if (identifier == null) { + DMCompiler.Emit(WarningCode.BadToken, token.Location, "Identifier expected"); + return new DMASTConstantNull(token.Location); } + + operation = new DMASTDereference.FieldOperation { + Location = identifier.Location, + Safe = token.Type is TokenType.DM_QuestionPeriod or TokenType.DM_QuestionColon, + Identifier = identifier.Identifier, + NoSearch = token.Type is TokenType.DM_Colon or TokenType.DM_QuestionColon + }; break; + } case TokenType.DM_LeftBracket: case TokenType.DM_QuestionLeftBracket: { - ternaryBHasPriority = true; - - Whitespace(); - DMASTExpression index = Expression(); - ConsumeRightBracket(); + ternaryBHasPriority = true; - operation.Kind = token.Type switch { - TokenType.DM_LeftBracket => DMASTDereference.OperationKind.Index, - TokenType.DM_QuestionLeftBracket => DMASTDereference.OperationKind.IndexSafe, - _ => throw new InvalidOperationException(), - }; + Whitespace(); + var index = Expression(); + ConsumeRightBracket(); - operation.Index = index; + if (index == null) { + DMCompiler.Emit(WarningCode.BadToken, token.Location, "Expression expected"); + return new DMASTConstantNull(token.Location); } + + operation = new DMASTDereference.IndexOperation { + Index = index, + Location = index.Location, + Safe = token.Type is TokenType.DM_QuestionLeftBracket + }; break; + } default: throw new InvalidOperationException("unhandled dereference token"); @@ -2303,39 +2304,26 @@ private void BracketWhitespace() { if (allowCalls) { Whitespace(); - DMASTCallParameter[] parameters = ProcCall(); + var parameters = ProcCall(); if (parameters != null) { ternaryBHasPriority = true; - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - operation.Kind = DMASTDereference.OperationKind.Call; - operation.Parameters = parameters; - break; - - case DMASTDereference.OperationKind.FieldSafe: - operation.Kind = DMASTDereference.OperationKind.CallSafe; - operation.Parameters = parameters; - break; - - case DMASTDereference.OperationKind.FieldSearch: - operation.Kind = DMASTDereference.OperationKind.CallSearch; - operation.Parameters = parameters; - break; - - case DMASTDereference.OperationKind.FieldSafeSearch: - operation.Kind = DMASTDereference.OperationKind.CallSafeSearch; - operation.Parameters = parameters; + switch (operation) { + case DMASTDereference.FieldOperation fieldOperation: + operation = new DMASTDereference.CallOperation { + Parameters = parameters, + Location = fieldOperation.Location, + Safe = fieldOperation.Safe, + Identifier = fieldOperation.Identifier, + NoSearch = fieldOperation.NoSearch + }; break; - case DMASTDereference.OperationKind.Index: - case DMASTDereference.OperationKind.IndexSafe: - Error("attempt to call an invalid l-value"); - return null; + case DMASTDereference.IndexOperation: + DMCompiler.Emit(WarningCode.BadToken, token.Location, "Attempt to call an invalid l-value"); + return new DMASTConstantNull(token.Location); - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSafe: default: throw new InvalidOperationException("unhandled dereference operation kind"); } @@ -2345,7 +2333,7 @@ private void BracketWhitespace() { operations.Add(operation); } - if (operations.Any()) { + if (operations.Count != 0) { Whitespace(); return new DMASTDereference(expression.Location, expression, operations.ToArray()); } @@ -2355,7 +2343,7 @@ private void BracketWhitespace() { return expression; } - private DMASTExpression ParseProcCall(DMASTExpression expression) { + private DMASTExpression? ParseProcCall(DMASTExpression? expression) { if (expression is not (DMASTCallable or DMASTIdentifier or DMASTGlobalIdentifier)) return expression; Whitespace(); diff --git a/DMCompiler/DM/DMExpression.cs b/DMCompiler/DM/DMExpression.cs index 78cffa0001..31055cb394 100644 --- a/DMCompiler/DM/DMExpression.cs +++ b/DMCompiler/DM/DMExpression.cs @@ -64,6 +64,12 @@ public virtual string GetNameof(DMObject dmObject, DMProc proc) { throw new CompileAbortException(Location, "nameof: requires a var, proc reference, or type path"); } + /// + /// Determines whether the expression returns an ambiguous path. + /// + /// Dereferencing these expressions will always skip validation via the "expr:y" operation. + public virtual bool PathIsFuzzy => false; + public virtual DreamPath? Path => null; public virtual DreamPath? NestedPath => Path; diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs index 6a29020177..2853445b41 100644 --- a/DMCompiler/DM/Expressions/Binary.cs +++ b/DMCompiler/DM/Expressions/Binary.cs @@ -214,6 +214,8 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { // x & y sealed class BinaryAnd : BinaryOp { + public override bool PathIsFuzzy => true; + public BinaryAnd(Location location, DMExpression lhs, DMExpression rhs) : base(location, lhs, rhs) { } diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index ab46a0fc31..1a6c68108a 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -48,6 +48,8 @@ sealed class New : DMExpression { private readonly DMExpression _expr; private readonly ArgumentList _arguments; + public override bool PathIsFuzzy => Path == null; + public New(Location location, DMExpression expr, ArgumentList arguments) : base(location) { _expr = expr; _arguments = arguments; @@ -345,6 +347,8 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class IsNull : DMExpression { private readonly DMExpression _value; + public override bool PathIsFuzzy => true; + public IsNull(Location location, DMExpression value) : base(location) { _value = value; } @@ -359,6 +363,8 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class Length : DMExpression { private readonly DMExpression _value; + public override bool PathIsFuzzy => true; + public Length(Location location, DMExpression value) : base(location) { _value = value; } @@ -374,6 +380,8 @@ internal sealed class GetStep : DMExpression { private readonly DMExpression _ref; private readonly DMExpression _dir; + public override bool PathIsFuzzy => true; + public GetStep(Location location, DMExpression refValue, DMExpression dir) : base(location) { _ref = refValue; _dir = dir; @@ -391,6 +399,8 @@ internal sealed class GetDir : DMExpression { private readonly DMExpression _loc1; private readonly DMExpression _loc2; + public override bool PathIsFuzzy => true; + public GetDir(Location location, DMExpression loc1, DMExpression loc2) : base(location) { _loc1 = loc1; _loc2 = loc2; @@ -408,6 +418,8 @@ sealed class List : DMExpression { private readonly (DMExpression? Key, DMExpression Value)[] _values; private readonly bool _isAssociative; + public override bool PathIsFuzzy => true; + public List(Location location, (DMExpression? Key, DMExpression Value)[] values) : base(location) { _values = values; diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs index 1a5c1233e5..f3ccf66788 100644 --- a/DMCompiler/DM/Expressions/Dereference.cs +++ b/DMCompiler/DM/Expressions/Dereference.cs @@ -1,31 +1,49 @@ +using DMCompiler.Bytecode; using OpenDreamShared.Compiler; -using DMCompiler.Compiler.DM; using OpenDreamShared.Dream; using System; -using DMCompiler.Bytecode; +using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace DMCompiler.DM.Expressions { // x.y.z // x[y][z] // x.f().y.g()[2] // etc. - class Dereference : LValue { - public struct Operation { - public DMASTDereference.OperationKind Kind; - - // Field*, Call* - public string Identifier; + internal class Dereference : LValue { + public abstract class Operation { + /// + /// Whether this operation will short circuit if the dereference equals null. (equal to x?.y) + /// + public required bool Safe { get; init; } + + /// + /// The path of the l-value being dereferenced. + /// + public DreamPath? Path { get; init; } + } - // Field* - public int? GlobalId; + public abstract class NamedOperation : Operation { + /// + /// The name of the identifier. + /// + public required string Identifier { get; init; } + } - // Index* - public DMExpression Index; + public sealed class FieldOperation : NamedOperation; - // Call* - public ArgumentList Parameters; + public sealed class IndexOperation : Operation { + /// + /// The index expression. (eg. x[expr]) + /// + public required DMExpression Index { get; init; } + } - public DreamPath? Path; + public sealed class CallOperation : NamedOperation { + /// + /// The argument list inside the call operation's parentheses. (eg. x(args, ...)) + /// + public required ArgumentList Parameters { get; init; } } private readonly DMExpression _expression; @@ -33,6 +51,7 @@ public struct Operation { public override DreamPath? Path { get; } public override DreamPath? NestedPath { get; } + public override bool PathIsFuzzy => Path == null; public Dereference(Location location, DreamPath? path, DMExpression expression, Operation[] operations) : base(location, null) { @@ -60,49 +79,34 @@ private void ShortCircuitHandler(DMProc proc, string endLabel, ShortCircuitMode } } - private void EmitOperation(DMObject dmObject, DMProc proc, ref Operation operation, string endLabel, ShortCircuitMode shortCircuitMode) { - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - proc.DereferenceField(operation.Identifier); - break; - - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - ShortCircuitHandler(proc, endLabel, shortCircuitMode); - proc.DereferenceField(operation.Identifier); - break; - - case DMASTDereference.OperationKind.Index: - operation.Index.EmitPushValue(dmObject, proc); - proc.DereferenceIndex(); + private void EmitOperation(DMObject dmObject, DMProc proc, Operation operation, string endLabel, ShortCircuitMode shortCircuitMode) { + switch (operation) { + case FieldOperation fieldOperation: + if (fieldOperation.Safe) { + ShortCircuitHandler(proc, endLabel, shortCircuitMode); + } + proc.DereferenceField(fieldOperation.Identifier); break; - case DMASTDereference.OperationKind.IndexSafe: - ShortCircuitHandler(proc, endLabel, shortCircuitMode); - operation.Index.EmitPushValue(dmObject, proc); + case IndexOperation indexOperation: + if (indexOperation.Safe) { + ShortCircuitHandler(proc, endLabel, shortCircuitMode); + } + indexOperation.Index.EmitPushValue(dmObject, proc); proc.DereferenceIndex(); break; - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSearch: { - var (argumentsType, argumentStackSize) = operation.Parameters.EmitArguments(dmObject, proc); - proc.DereferenceCall(operation.Identifier, argumentsType, argumentStackSize); - break; - } - - case DMASTDereference.OperationKind.CallSafe: - case DMASTDereference.OperationKind.CallSafeSearch: { - ShortCircuitHandler(proc, endLabel, shortCircuitMode); - var (argumentsType, argumentStackSize) = operation.Parameters.EmitArguments(dmObject, proc); - proc.DereferenceCall(operation.Identifier, argumentsType, argumentStackSize); + case CallOperation callOperation: + if (callOperation.Safe) { + ShortCircuitHandler(proc, endLabel, shortCircuitMode); + } + var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(dmObject, proc); + proc.DereferenceCall(callOperation.Identifier, argumentsType, argumentStackSize); break; - } - case DMASTDereference.OperationKind.Invalid: default: - throw new NotImplementedException(); - }; + throw new InvalidOperationException("Unimplemented dereference operation"); + } } public override void EmitPushValue(DMObject dmObject, DMProc proc) { @@ -110,78 +114,49 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { _expression.EmitPushValue(dmObject, proc); - foreach (ref var operation in _operations.AsSpan()) { - EmitOperation(dmObject, proc, ref operation, endLabel, ShortCircuitMode.KeepNull); + foreach (var operation in _operations) { + EmitOperation(dmObject, proc, operation, endLabel, ShortCircuitMode.KeepNull); } proc.AddLabel(endLabel); } public override bool CanReferenceShortCircuit() { - foreach (var operation in _operations) { - switch (operation.Kind) { - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - case DMASTDereference.OperationKind.IndexSafe: - case DMASTDereference.OperationKind.CallSafe: - case DMASTDereference.OperationKind.CallSafeSearch: - return true; - - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - case DMASTDereference.OperationKind.Index: - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSearch: - break; - - case DMASTDereference.OperationKind.Invalid: - default: - throw new NotImplementedException(); - } - } - - return base.CanReferenceShortCircuit(); + return _operations.Any(operation => operation.Safe); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode) { + public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { _expression.EmitPushValue(dmObject, proc); // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, ref _operations[i], endLabel, shortCircuitMode); + EmitOperation(dmObject, proc, _operations[i], endLabel, shortCircuitMode); } - ref var operation = ref _operations[^1]; + var operation = _operations[^1]; - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - return DMReference.CreateField(operation.Identifier); - - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - ShortCircuitHandler(proc, endLabel, shortCircuitMode); - return DMReference.CreateField(operation.Identifier); - - case DMASTDereference.OperationKind.Index: - operation.Index.EmitPushValue(dmObject, proc); - return DMReference.ListIndex; + switch (operation) { + case FieldOperation fieldOperation: + if (fieldOperation.Safe) { + ShortCircuitHandler(proc, endLabel, shortCircuitMode); + } + return DMReference.CreateField(fieldOperation.Identifier); - case DMASTDereference.OperationKind.IndexSafe: - ShortCircuitHandler(proc, endLabel, shortCircuitMode); - operation.Index.EmitPushValue(dmObject, proc); + case IndexOperation indexOperation: + if (indexOperation.Safe) { + ShortCircuitHandler(proc, endLabel, shortCircuitMode); + } + indexOperation.Index.EmitPushValue(dmObject, proc); return DMReference.ListIndex; - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSearch: - case DMASTDereference.OperationKind.CallSafe: - case DMASTDereference.OperationKind.CallSafeSearch: - throw new CompileErrorException(Location, $"attempt to reference proc call result"); + case CallOperation: + DMCompiler.Emit(WarningCode.BadExpression, Location, + "Expected field or index as reference, got proc call result"); + return default; - case DMASTDereference.OperationKind.Invalid: default: - throw new NotImplementedException(); - }; + throw new InvalidOperationException("Unimplemented dereference operation"); + } } public override void EmitPushInitial(DMObject dmObject, DMProc proc) { @@ -191,46 +166,36 @@ public override void EmitPushInitial(DMObject dmObject, DMProc proc) { // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, ref _operations[i], endLabel, ShortCircuitMode.KeepNull); + EmitOperation(dmObject, proc, _operations[i], endLabel, ShortCircuitMode.KeepNull); } - ref var operation = ref _operations[^1]; + var operation = _operations[^1]; - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - proc.PushString(operation.Identifier); - proc.Initial(); - break; - - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - proc.JumpIfNullNoPop(endLabel); - proc.PushString(operation.Identifier); + switch (operation) { + case FieldOperation fieldOperation: + if (fieldOperation.Safe) { + proc.JumpIfNullNoPop(endLabel); + } + proc.PushString(fieldOperation.Identifier); proc.Initial(); break; - case DMASTDereference.OperationKind.Index: - operation.Index.EmitPushValue(dmObject, proc); + case IndexOperation indexOperation: + if (indexOperation.Safe) { + proc.JumpIfNullNoPop(endLabel); + } + indexOperation.Index.EmitPushValue(dmObject, proc); proc.Initial(); break; - case DMASTDereference.OperationKind.IndexSafe: - proc.JumpIfNullNoPop(endLabel); - operation.Index.EmitPushValue(dmObject, proc); - proc.Initial(); + case CallOperation: + DMCompiler.Emit(WarningCode.BadExpression, Location, + "Expected field or index for initial(), got proc call result"); break; - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSearch: - case DMASTDereference.OperationKind.CallSafe: - case DMASTDereference.OperationKind.CallSafeSearch: - throw new CompileErrorException(Location, $"attempt to get `initial` of a proc call"); - - case DMASTDereference.OperationKind.Invalid: default: - throw new NotImplementedException(); - }; + throw new InvalidOperationException("Unimplemented dereference operation"); + } proc.AddLabel(endLabel); } @@ -242,79 +207,56 @@ public void EmitPushIsSaved(DMObject dmObject, DMProc proc) { // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, ref _operations[i], endLabel, ShortCircuitMode.KeepNull); + EmitOperation(dmObject, proc, _operations[i], endLabel, ShortCircuitMode.KeepNull); } - ref var operation = ref _operations[^1]; + var operation = _operations[^1]; - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - proc.PushString(operation.Identifier); + switch (operation) { + case FieldOperation fieldOperation: + if (fieldOperation.Safe) { + proc.JumpIfNullNoPop(endLabel); + } + proc.PushString(fieldOperation.Identifier); proc.IsSaved(); break; - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - proc.JumpIfNullNoPop(endLabel); - proc.PushString(operation.Identifier); + case IndexOperation indexOperation: + if (indexOperation.Safe) { + proc.JumpIfNullNoPop(endLabel); + } + indexOperation.Index.EmitPushValue(dmObject, proc); proc.IsSaved(); break; - case DMASTDereference.OperationKind.Index: - operation.Index.EmitPushValue(dmObject, proc); - proc.IsSaved(); + case CallOperation: + DMCompiler.Emit(WarningCode.BadExpression, Location, + "Expected field or index for issaved(), got proc call result"); break; - case DMASTDereference.OperationKind.IndexSafe: - proc.JumpIfNullNoPop(endLabel); - operation.Index.EmitPushValue(dmObject, proc); - proc.IsSaved(); - break; - - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSearch: - case DMASTDereference.OperationKind.CallSafe: - case DMASTDereference.OperationKind.CallSafeSearch: - throw new CompileErrorException(Location, $"attempt to get `issaved` of a proc call"); - - case DMASTDereference.OperationKind.Invalid: default: - throw new NotImplementedException(); - }; + throw new InvalidOperationException("Unimplemented dereference operation"); + } proc.AddLabel(endLabel); } - public override bool TryAsConstant(out Constant constant) { - DreamPath? prevPath = null; + public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + var prevPath = _operations.Length == 1 ? _expression.Path : _operations[^2].Path; - if (_operations.Length == 1) { - prevPath = _expression.Path; - } else { - prevPath = _operations[^2].Path; - } + var operation = _operations[^1]; - ref var operation = ref _operations[^1]; - - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSearch: - case DMASTDereference.OperationKind.FieldSafe: - case DMASTDereference.OperationKind.FieldSafeSearch: - if (prevPath is not null) { - var obj = DMObjectTree.GetDMObject(prevPath.GetValueOrDefault()); - var variable = obj.GetVariable(operation.Identifier); - if (variable != null) { - if (variable.IsConst) - return variable.Value.TryAsConstant(out constant); - if ((variable.ValType & DMValueType.CompiletimeReadonly) == DMValueType.CompiletimeReadonly) { - variable.Value.TryAsConstant(out constant); - return true; // MUST be true. - } - } + if (operation is FieldOperation fieldOperation && prevPath is not null) { + var obj = DMObjectTree.GetDMObject(prevPath.Value); + var variable = obj!.GetVariable(fieldOperation.Identifier); + if (variable != null) { + if (variable.IsConst) + return variable.Value.TryAsConstant(out constant); + if (variable.ValType.HasFlag(DMValueType.CompiletimeReadonly)) { + variable.Value.TryAsConstant(out constant!); + return true; // MUST be true. } - break; + } } constant = null; diff --git a/DMCompiler/DM/Expressions/Procs.cs b/DMCompiler/DM/Expressions/Procs.cs index c12827a2fd..ca16cf3798 100644 --- a/DMCompiler/DM/Expressions/Procs.cs +++ b/DMCompiler/DM/Expressions/Procs.cs @@ -102,6 +102,8 @@ sealed class ProcCall : DMExpression { private readonly DMExpression _target; private readonly ArgumentList _arguments; + public override bool PathIsFuzzy => Path == null; + public ProcCall(Location location, DMExpression target, ArgumentList arguments) : base(location) { _target = target; _arguments = arguments; diff --git a/DMCompiler/DM/Expressions/Ternary.cs b/DMCompiler/DM/Expressions/Ternary.cs index 73e1e9fbce..2ec880c57d 100644 --- a/DMCompiler/DM/Expressions/Ternary.cs +++ b/DMCompiler/DM/Expressions/Ternary.cs @@ -6,6 +6,8 @@ namespace DMCompiler.DM.Expressions { sealed class Ternary : DMExpression { private readonly DMExpression _a, _b, _c; + public override bool PathIsFuzzy => true; + public Ternary(Location location, DMExpression a, DMExpression b, DMExpression c) : base(location) { _a = a; _b = b; diff --git a/DMCompiler/DM/Visitors/DMASTSimplifier.cs b/DMCompiler/DM/Visitors/DMASTSimplifier.cs index 77c8b7b371..1956f95884 100644 --- a/DMCompiler/DM/Visitors/DMASTSimplifier.cs +++ b/DMCompiler/DM/Visitors/DMASTSimplifier.cs @@ -1,4 +1,5 @@ using DMCompiler.Compiler.DM; +using DMCompiler.DM.Expressions; using System; namespace DMCompiler.DM.Visitors { @@ -556,19 +557,19 @@ private void SimplifyExpression(ref DMASTExpression expression) { } } - DMASTDereference deref = expression as DMASTDereference; - if (deref != null) { + if (expression is DMASTDereference deref) { SimplifyExpression(ref deref.Expression); - foreach (ref var operation in deref.Operations.AsSpan()) { - if (operation.Index != null) { - SimplifyExpression(ref deref.Expression); - } - - if (operation.Parameters != null) { - foreach (DMASTCallParameter parameter in operation.Parameters) { - SimplifyExpression(ref parameter.Value); - } + foreach (var operation in deref.Operations) { + switch (operation) { + case DMASTDereference.IndexOperation indexOperation: + SimplifyExpression(ref indexOperation.Index); + break; + case DMASTDereference.CallOperation callOperation: + foreach (var param in callOperation.Parameters) { + SimplifyExpression(ref param.Value); + } + break; } } } diff --git a/DMCompiler/DM/Visitors/DMExpressionBuilder.cs b/DMCompiler/DM/Visitors/DMExpressionBuilder.cs index 2f21a22532..1e0e9e7da7 100644 --- a/DMCompiler/DM/Visitors/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Visitors/DMExpressionBuilder.cs @@ -1,9 +1,9 @@ -using System; +using DMCompiler.Compiler.DM; using DMCompiler.DM.Expressions; using OpenDreamShared.Compiler; -using DMCompiler.Compiler.DM; using OpenDreamShared.Dream; using Robust.Shared.Utility; +using System; namespace DMCompiler.DM.Visitors; @@ -480,72 +480,50 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm var operations = new Dereference.Operation[deref.Operations.Length]; int astOperationOffset = 0; - static bool IsFuzzy(DMExpression expr) { - switch (expr) { - case Dereference when expr.Path == null: - case ProcCall when expr.Path == null: - case New when expr.Path == null: - case List: - case Ternary: - case BinaryAnd: - case IsNull: - case Length: - case GetStep: - case GetDir: - return true; - default: return false; - } - } - // Path of the previous operation that was iterated over (starting as the base expression) DreamPath? prevPath = expr.Path; - bool pathIsFuzzy = IsFuzzy(expr); + var pathIsFuzzy = expr.PathIsFuzzy; // Special behaviour for `global.x`, `global.vars`, and `global.f()` if (expr is Global) { - ref DMASTDereference.Operation firstOperation = ref astOperations[0]; - - if (firstOperation is { Kind: DMASTDereference.OperationKind.Field, Identifier.Identifier: "vars" }) { - // `global.vars` - expr = new GlobalVars(expr.Location); - - var newOperationCount = operations.Length - 1; - if (newOperationCount == 0) { - return expr; - } - - operations = new Dereference.Operation[newOperationCount]; - astOperationOffset = 1; + DMASTDereference.Operation firstOperation = astOperations[0]; + if (firstOperation is DMASTDereference.NamedOperation namedOperation) { prevPath = null; pathIsFuzzy = true; - } else if (firstOperation.Kind == DMASTDereference.OperationKind.Field) { - // `global.x` - var globalId = dmObject.GetGlobalVariableId(firstOperation.Identifier.Identifier); - if (globalId == null) { - throw new UnknownIdentifierException(deref.Location, $"global.{firstOperation.Identifier.Identifier}"); - } - - var property = DMObjectTree.Globals[globalId.Value]; - expr = new GlobalField(expr.Location, property.Type, globalId.Value); - - var newOperationCount = operations.Length - 1; - if (newOperationCount == 0) { - return expr; - } + switch (namedOperation) { + // global.f() + case DMASTDereference.CallOperation callOperation: + ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, + callOperation.Parameters); + + var globalProc = new GlobalProc(expr.Location, callOperation.Identifier); + expr = new ProcCall(expr.Location, globalProc, argumentList); + break; + + case DMASTDereference.FieldOperation: + // global.vars + if (namedOperation is { Identifier: "vars" }) { + expr = new GlobalVars(expr.Location); + break; + } - operations = new Dereference.Operation[newOperationCount]; - astOperationOffset = 1; + // global.variable + var globalId = dmObject.GetGlobalVariableId(namedOperation.Identifier); + if (globalId == null) { + throw new UnknownIdentifierException(deref.Location, $"global.{namedOperation.Identifier}"); + } - prevPath = property.Type; - pathIsFuzzy = false; - } else if (firstOperation.Kind == DMASTDereference.OperationKind.Call) { - // `global.f()` - ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, firstOperation.Parameters); + var property = DMObjectTree.Globals[globalId.Value]; + expr = new GlobalField(expr.Location, property.Type, globalId.Value); - var globalProc = new GlobalProc(expr.Location, firstOperation.Identifier.Identifier); - expr = new ProcCall(expr.Location, globalProc, argumentList); + prevPath = property.Type; + pathIsFuzzy = false; + break; + default: + throw new ArgumentOutOfRangeException($"Missing implementation for {namedOperation}"); + } var newOperationCount = operations.Length - 1; if (newOperationCount == 0) { @@ -554,156 +532,138 @@ static bool IsFuzzy(DMExpression expr) { operations = new Dereference.Operation[newOperationCount]; astOperationOffset = 1; - - prevPath = null; - pathIsFuzzy = true; } else { - throw new CompileErrorException(deref.Location, $"Invalid dereference operation performed on `global`"); + DMCompiler.Emit(WarningCode.BadExpression, firstOperation.Location, + "Invalid dereference operation performed on global"); + expr = new Null(firstOperation.Location); } } for (int i = 0; i < operations.Length; i++) { - ref DMASTDereference.Operation astOperation = ref astOperations[i + astOperationOffset]; - ref Dereference.Operation operation = ref operations[i]; - - operation.Kind = astOperation.Kind; - - // If the last operation evaluated as an ambiguous type, we force the next operation to be a search - if (pathIsFuzzy) { - operation.Kind = operation.Kind switch { - DMASTDereference.OperationKind.Invalid => throw new InvalidOperationException(), - - DMASTDereference.OperationKind.Field => DMASTDereference.OperationKind.FieldSearch, - DMASTDereference.OperationKind.FieldSafe => DMASTDereference.OperationKind.FieldSafeSearch, - DMASTDereference.OperationKind.FieldSearch => DMASTDereference.OperationKind.FieldSearch, - DMASTDereference.OperationKind.FieldSafeSearch => DMASTDereference.OperationKind.FieldSafeSearch, - DMASTDereference.OperationKind.Call => DMASTDereference.OperationKind.CallSearch, - DMASTDereference.OperationKind.CallSafe => DMASTDereference.OperationKind.CallSafeSearch, - DMASTDereference.OperationKind.CallSearch => DMASTDereference.OperationKind.CallSearch, - DMASTDereference.OperationKind.CallSafeSearch => DMASTDereference.OperationKind.CallSafeSearch, - - // Indexes are always fuzzy anyway! - DMASTDereference.OperationKind.Index => DMASTDereference.OperationKind.Index, - DMASTDereference.OperationKind.IndexSafe => DMASTDereference.OperationKind.IndexSafe, - - _ => throw new InvalidOperationException(), - }; - } - switch (operation.Kind) { - case DMASTDereference.OperationKind.Field: - case DMASTDereference.OperationKind.FieldSafe: { - string field = astOperation.Identifier.Identifier; + DMASTDereference.Operation astOperation = astOperations[i + astOperationOffset]; + Dereference.Operation operation; - if (prevPath == null) { - throw new UnknownIdentifierException(deref.Location, field); - } + switch (astOperation) { + case DMASTDereference.FieldOperation fieldOperation: { + var field = fieldOperation.Identifier; - DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); - if (fromObject == null) { - throw new CompileErrorException(deref.Location, $"Type {prevPath.Value} does not exist"); - } + DMVariable? property = null; + + // If the last operation evaluated as an ambiguous type, we force the next operation to be a search + if (!fieldOperation.NoSearch && !pathIsFuzzy) { + if (prevPath == null) { + throw new UnknownIdentifierException(deref.Location, field); + } - DMVariable? property = fromObject.GetVariable(field); - if (property != null) { - operation.Identifier = field; - operation.GlobalId = null; - operation.Path = property.Type; - if (operation.Kind == DMASTDereference.OperationKind.Field && - fromObject.IsSubtypeOf(DreamPath.Client)) { - DMCompiler.Emit(WarningCode.UnsafeClientAccess, deref.Location,"Unsafe \"client\" access. Use the \"?.\" operator instead"); + DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); + if (fromObject == null) { + DMCompiler.Emit(WarningCode.ItemDoesntExist, fieldOperation.Location, + $"Type {prevPath.Value} does not exist"); + return new Null(deref.Location); } - } else { - var globalId = fromObject.GetGlobalVariableId(field); - if (globalId != null) { - property = DMObjectTree.Globals[globalId.Value]; - expr = new GlobalField(expr.Location, property.Type, globalId.Value); + property = fromObject.GetVariable(field); + if (!fieldOperation.Safe && fromObject.IsSubtypeOf(DreamPath.Client)) { + DMCompiler.Emit(WarningCode.UnsafeClientAccess, deref.Location, + "Unsafe \"client\" access. Use the \"?.\" operator instead"); + } + + if (property == null && fromObject.GetGlobalVariableId(field) is { } globalId) { + property = DMObjectTree.Globals[globalId]; + + expr = new GlobalField(expr.Location, property.Type, globalId); var newOperationCount = operations.Length - i - 1; if (newOperationCount == 0) { return expr; } + if (property == null) { + throw new UnknownIdentifierException(deref.Location, field); + } + + if ((property.ValType & DMValueType.Unimplemented) == DMValueType.Unimplemented) { + DMCompiler.UnimplementedWarning(deref.Location, + $"{prevPath}.{field} is not implemented and will have unexpected behavior"); + } + operations = new Dereference.Operation[newOperationCount]; astOperationOffset = i + 1; i = -1; + prevPath = property.Type; + pathIsFuzzy = prevPath == null; + continue; } - } - if (property == null) { - throw new UnknownIdentifierException(deref.Location, field); + if (property == null) { + throw new UnknownIdentifierException(deref.Location, field); + } } - if ((property.ValType & DMValueType.Unimplemented) == DMValueType.Unimplemented) { - DMCompiler.UnimplementedWarning(deref.Location, $"{prevPath}.{field} is not implemented and will have unexpected behavior"); - } + operation = new Dereference.FieldOperation { + Safe = fieldOperation.Safe, + Identifier = fieldOperation.Identifier, + Path = property?.Type + }; - prevPath = property.Type; - pathIsFuzzy = false; - } - break; - - case DMASTDereference.OperationKind.FieldSearch: - case DMASTDereference.OperationKind.FieldSafeSearch: - // TODO: im pretty sure types should be inferred if a field with their name only exists in a single place, sounds cursed though - operation.Identifier = astOperation.Identifier.Identifier; - operation.GlobalId = null; - operation.Path = null; - prevPath = null; - pathIsFuzzy = true; + prevPath = property?.Type; + pathIsFuzzy = property == null; break; + } - case DMASTDereference.OperationKind.Index: - case DMASTDereference.OperationKind.IndexSafe: - // Passing the path here is cursed, but one of the tests seems to suggest we want that? - operation.Index = DMExpression.Create(dmObject, proc, astOperation.Index, prevPath); - operation.Path = prevPath; + case DMASTDereference.IndexOperation indexOperation: + operation = new Dereference.IndexOperation { + // Passing the path here is cursed, but one of the tests seems to suggest we want that? + Index = DMExpression.Create(dmObject, proc, indexOperation.Index, prevPath), + Safe = indexOperation.Safe, + Path = prevPath + }; prevPath = null; pathIsFuzzy = true; break; - case DMASTDereference.OperationKind.Call: - case DMASTDereference.OperationKind.CallSafe: { - string field = astOperation.Identifier.Identifier; - ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, astOperation.Parameters); + case DMASTDereference.CallOperation callOperation: { + var field = callOperation.Identifier; + ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, callOperation.Parameters); - if (prevPath == null) { - throw new UnknownIdentifierException(deref.Location, field); - } + if (!callOperation.NoSearch && !pathIsFuzzy) { + if (prevPath == null) { + throw new UnknownIdentifierException(deref.Location, field); + } - DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); - if (fromObject == null) { - throw new CompileErrorException(deref.Location, $"Type {prevPath.Value} does not exist"); - } + DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); + if (fromObject == null) { + DMCompiler.Emit(WarningCode.ItemDoesntExist, callOperation.Location, + $"Type {prevPath.Value} does not exist"); + return new Null(deref.Location); + } - if (!fromObject.HasProc(field)) { - throw new CompileErrorException(deref.Location, $"Type {prevPath.Value} does not have a proc named \"{field}\""); + if (!fromObject.HasProc(field)) { + DMCompiler.Emit(WarningCode.ItemDoesntExist, callOperation.Location, + $"Type {prevPath.Value} does not have a proc named \"{field}\""); + return new Null(deref.Location); + } } - operation.Identifier = astOperation.Identifier.Identifier; - operation.Parameters = argumentList; - operation.Path = null; + operation = new Dereference.CallOperation { + Parameters = argumentList, + Safe = callOperation.Safe, + Identifier = field, + Path = null + }; prevPath = null; pathIsFuzzy = true; break; } - case DMASTDereference.OperationKind.CallSearch: - case DMASTDereference.OperationKind.CallSafeSearch: - operation.Identifier = astOperation.Identifier.Identifier; - operation.Parameters = new ArgumentList(deref.Expression.Location, dmObject, proc, astOperation.Parameters); - operation.Path = null; - prevPath = null; - pathIsFuzzy = true; - break; - default: throw new InvalidOperationException("unhandled deref operation kind"); } + + operations[i] = operation; } // The final value in prevPath is our expression's path! - return new Dereference(deref.Location, prevPath, expr, operations); }