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);
}