diff --git a/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm b/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm
index 46c609064c..c1de60bd32 100644
--- a/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm
+++ b/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm
@@ -4,3 +4,4 @@
/proc/RunTest()
return
+#pragma ImplicitNullType notice
diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
index a2f210bdfc..845b38e10e 100644
--- a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
+++ b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
@@ -70,7 +70,7 @@ public sealed class DMASTObjectVarDefinition(
Location location,
DreamPath path,
DMASTExpression value,
- DMComplexValueType valType) : DMASTStatement(location) {
+ DMComplexValueType? valType) : DMASTStatement(location) {
/// The path of the object that we are a property of.
public DreamPath ObjectPath => _varDecl.ObjectPath;
@@ -88,7 +88,7 @@ public sealed class DMASTObjectVarDefinition(
public bool IsFinal => _varDecl.IsFinal;
public bool IsTmp => _varDecl.IsTmp;
- public readonly DMComplexValueType ValType = valType;
+ public readonly DMComplexValueType? ValType = valType;
}
public sealed class DMASTMultipleObjectVarDefinitions(Location location, DMASTObjectVarDefinition[] varDefinitions)
diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs
index 37067a9929..20c2835b4e 100644
--- a/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs
+++ b/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs
@@ -29,13 +29,14 @@ public sealed class DMASTProcStatementExpression(Location location, DMASTExpress
public DMASTExpression Expression = expression;
}
-public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType valType)
+public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType? valType)
: DMASTProcStatement(location) {
public DMASTExpression? Value = value;
public DreamPath? Type => _varDecl.IsList ? DreamPath.List : _varDecl.TypePath;
+ public DMComplexValueType? ExplicitValType => valType;
- public DMComplexValueType ValType => valType;
+ public DMComplexValueType? ValType => ExplicitValType ?? DMValueType.Anything;
public string Name => _varDecl.VarName;
public bool IsGlobal => _varDecl.IsStatic;
diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs
index 0996f41bf0..637bcbb76b 100644
--- a/DMCompiler/Compiler/DM/DMParser.cs
+++ b/DMCompiler/Compiler/DM/DMParser.cs
@@ -324,7 +324,7 @@ public DMASTFile File() {
value = new DMASTConstantNull(loc);
}
- var valType = AsComplexTypes() ?? DMValueType.Anything;
+ var valType = AsComplexTypes();
var varDef = new DMASTObjectVarDefinition(loc, varPath, value, valType);
if (varDef.IsStatic && varDef.Name is "usr" or "src" or "args" or "world" or "global" or "callee" or "caller")
@@ -936,7 +936,11 @@ public DMASTFile File() {
RequireExpression(ref value);
}
- var valType = AsComplexTypes() ?? DMValueType.Anything;
+ var valType = AsComplexTypes();
+ // the != DMValueType.Null check is a hacky workaround for Anything|Null being equal to just Null
+ if (valType is null && value?.GetUnwrapped() is DMASTInput input && input.Types != DMValueType.Null) {
+ valType = input.Types;
+ }
varDeclarations.Add(new DMASTProcStatementVarDeclaration(loc, varPath, value, valType));
if (allowMultiple && Check(TokenType.DM_Comma)) {
@@ -2782,7 +2786,7 @@ private void BracketWhitespace() {
do {
Whitespace();
- type |= SingleAsType(out _);
+ type |= SingleAsType(out _, out _);
Whitespace();
} while (Check(TokenType.DM_Bar));
@@ -2802,29 +2806,49 @@ private void BracketWhitespace() {
if (parenthetical && Check(TokenType.DM_RightParenthesis)) // as ()
return DMValueType.Anything; // TODO: BYOND doesn't allow this for proc return types
+ var outType = UnionComplexTypes();
+
+ if (parenthetical) {
+ ConsumeRightParenthesis();
+ }
+
+ return outType;
+ }
+
+ private DMComplexValueType? UnionComplexTypes() {
+
DMValueType type = DMValueType.Anything;
DreamPath? path = null;
+ DMListValueTypes? outListTypes = null;
do {
Whitespace();
- type |= SingleAsType(out var pathType, allowPath: true);
+ type |= SingleAsType(out var pathType, out var listTypes, allowPath: true);
Whitespace();
- if (pathType != null) {
- if (path == null)
+ if (pathType is not null) {
+ if (path is null)
path = pathType;
- else
- Compiler.Emit(WarningCode.BadToken, CurrentLoc,
- $"Only one type path can be used, ignoring {pathType}");
+ else {
+ var newPath = path.Value.GetLastCommonAncestor(compiler, pathType.Value);
+ if (newPath != path) {
+ Compiler.Emit(WarningCode.LostTypeInfo, CurrentLoc,
+ $"Only one type path can be used, using last common ancestor {newPath}");
+ path = newPath;
+ }
+ }
}
+ if (listTypes is not null) {
+ if (outListTypes is null)
+ outListTypes = listTypes;
+ else {
+ outListTypes = DMListValueTypes.MergeListValueTypes(compiler, outListTypes, listTypes);
+ }
+ }
} while (Check(TokenType.DM_Bar));
- if (parenthetical) {
- ConsumeRightParenthesis();
- }
-
- return new(type, path);
+ return new(type, path, outListTypes);
}
private bool AsTypesStart(out bool parenthetical) {
@@ -2838,10 +2862,21 @@ private bool AsTypesStart(out bool parenthetical) {
return false;
}
- private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
+ private DMValueType SingleAsType(out DreamPath? path, out DMListValueTypes? listTypes, bool allowPath = false) {
Token typeToken = Current();
- if (!Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) {
+ listTypes = null;
+
+ var inPath = false;
+ if (typeToken.Type is TokenType.DM_Identifier && typeToken.Text == "path") {
+ Advance();
+ if(Check(TokenType.DM_LeftParenthesis)) {
+ inPath = true;
+ Whitespace();
+ }
+ }
+
+ if (inPath || !Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) {
// Proc return types
path = Path()?.Path;
if (allowPath) {
@@ -2849,11 +2884,31 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type or path");
}
- return DMValueType.Path;
+ if (inPath) {
+ Whitespace();
+ ConsumeRightParenthesis();
+ } else if (path == DreamPath.List) {
+ // check for list types
+ if (Check(TokenType.DM_LeftParenthesis)) {
+ DMComplexValueType? nestedKeyType = UnionComplexTypes();
+ if (nestedKeyType is null)
+ Compiler.Emit(WarningCode.BadToken, CurrentLoc, "Expected value type or path");
+ DMComplexValueType? nestedValType = null;
+ if (Check(TokenType.DM_Comma)) { // Value
+ nestedValType = UnionComplexTypes();
+ }
+
+ ConsumeRightParenthesis();
+ listTypes = new(nestedKeyType!.Value, nestedValType);
+ return DMValueType.Instance;
+ }
+ }
+
+ return inPath ? DMValueType.Path : DMValueType.Instance;
}
Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type");
- return 0;
+ return DMValueType.Anything;
}
path = null;
@@ -2872,7 +2927,6 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
case "command_text": return DMValueType.CommandText;
case "sound": return DMValueType.Sound;
case "icon": return DMValueType.Icon;
- case "path": return DMValueType.Path;
case "opendream_unimplemented": return DMValueType.Unimplemented;
case "opendream_unsupported": return DMValueType.Unsupported;
case "opendream_compiletimereadonly": return DMValueType.CompiletimeReadonly;
diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs
index 943ccf87cc..9da2781d0f 100644
--- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs
+++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs
@@ -46,6 +46,11 @@ public void Emit(DMASTExpression expression, DreamPath? inferredPath = null) {
expr.EmitPushValue(ctx);
}
+ public void Emit(DMASTExpression expression, out DMExpression returnedExpr, DreamPath? inferredPath = null) {
+ returnedExpr = Create(expression, inferredPath);
+ returnedExpr.EmitPushValue(ctx);
+ }
+
public bool TryConstant(DMASTExpression expression, out Constant? constant) {
var expr = Create(expression);
return expr.TryAsConstant(Compiler, out constant);
@@ -64,7 +69,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
case DMASTStringFormat stringFormat: result = BuildStringFormat(stringFormat, inferredPath); break;
case DMASTIdentifier identifier: result = BuildIdentifier(identifier, inferredPath); break;
case DMASTScopeIdentifier globalIdentifier: result = BuildScopeIdentifier(globalIdentifier, inferredPath); break;
- case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.ReturnTypes); break;
+ case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.RawReturnTypes); break;
case DMASTCallableSuper: result = new ProcSuper(expression.Location, ctx.Type.GetProcReturnTypes(ctx.Proc.Name)); break;
case DMASTCallableProcIdentifier procIdentifier: result = BuildCallableProcIdentifier(procIdentifier, ctx.Type); break;
case DMASTProcCall procCall: result = BuildProcCall(procCall, inferredPath); break;
@@ -272,10 +277,10 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
if (b.ValType.TypePath != null && c.ValType.TypePath != null && b.ValType.TypePath != c.ValType.TypePath) {
Compiler.Emit(WarningCode.LostTypeInfo, ternary.Location,
- $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using {b.ValType.TypePath}.");
+ $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using their common ancestor, {b.ValType.TypePath.Value.GetLastCommonAncestor(Compiler, c.ValType.TypePath.Value)}.");
}
- result = new Ternary(ternary.Location, a, b, c);
+ result = new Ternary(Compiler, ternary.Location, a, b, c);
break;
case DMASTNewPath newPath:
if (BuildExpression(newPath.Path, inferredPath) is not IConstantPath path) {
@@ -284,7 +289,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
break;
}
- result = new NewPath(Compiler, newPath.Location, path,
+ result = new NewPath(newPath.Location, path,
BuildArgumentList(newPath.Location, newPath.Parameters, inferredPath));
break;
case DMASTNewExpr newExpr:
@@ -305,7 +310,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
break;
}
- result = new NewPath(Compiler, newInferred.Location, inferredType,
+ result = new NewPath(newInferred.Location, inferredType,
BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath));
break;
case DMASTPreIncrement preIncrement:
@@ -554,7 +559,7 @@ private DMExpression BuildIdentifier(DMASTIdentifier identifier, DreamPath? infe
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
var localVar = ctx.Proc?.GetLocalVariable(name);
if (localVar is not null) {
- return new Local(identifier.Location, localVar);
+ return new Local(identifier.Location, localVar, localVar.ExplicitValueType);
}
}
@@ -622,7 +627,7 @@ private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier,
return UnknownReference(location, $"No global proc named \"{bIdentifier}\" exists");
var arguments = BuildArgumentList(location, scopeIdentifier.CallArguments, inferredPath);
- return new ProcCall(location, new GlobalProc(location, globalProc), arguments, DMValueType.Anything);
+ return new ProcCall(Compiler, location, new GlobalProc(location, globalProc), arguments, DMValueType.Anything);
}
// ::vars, special case
@@ -715,7 +720,7 @@ private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier,
if (variable == null)
return UnknownIdentifier(location, bIdentifier);
- return new ScopeReference(ObjectTree, location, expression, bIdentifier, variable);
+ return new ScopeReference(Compiler, location, expression, bIdentifier, variable);
}
}
@@ -729,7 +734,7 @@ private DMExpression BuildCallableProcIdentifier(DMASTCallableProcIdentifier pro
}
if (dmObject.HasProc(procIdentifier.Identifier)) {
- return new Proc(procIdentifier.Location, procIdentifier.Identifier);
+ return new Proc(procIdentifier.Location, procIdentifier.Identifier, dmObject);
}
if (ObjectTree.TryGetGlobalProc(procIdentifier.Identifier, out var globalProc)) {
@@ -766,10 +771,10 @@ private DMExpression BuildProcCall(DMASTProcCall procCall, DreamPath? inferredPa
if (target is Proc targetProc) { // GlobalProc handles returnType itself
var returnType = targetProc.GetReturnType(ctx.Type);
- return new ProcCall(procCall.Location, target, args, returnType);
+ return new ProcCall(Compiler, procCall.Location, target, args, returnType);
}
- return new ProcCall(procCall.Location, target, args, DMValueType.Anything);
+ return new ProcCall(Compiler, procCall.Location, target, args, DMValueType.Anything);
}
private ArgumentList BuildArgumentList(Location location, DMASTCallParameter[]? arguments, DreamPath? inferredPath = null) {
@@ -899,7 +904,7 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters, inferredPath);
var globalProcExpr = new GlobalProc(expr.Location, globalProc);
- expr = new ProcCall(expr.Location, globalProcExpr, argumentList, DMValueType.Anything);
+ expr = new ProcCall(Compiler, expr.Location, globalProcExpr, argumentList, DMValueType.Anything);
break;
case DMASTDereference.FieldOperation:
@@ -1045,6 +1050,7 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
case DMASTDereference.CallOperation callOperation: {
var field = callOperation.Identifier;
var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters, inferredPath);
+ DreamPath? nextPath = null;
if (!callOperation.NoSearch && !pathIsFuzzy) {
if (prevPath == null) {
@@ -1056,6 +1062,14 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
if (!fromObject.HasProc(field))
return UnknownIdentifier(callOperation.Location, field);
+ var returnTypes = fromObject.GetProcReturnTypes(field, argumentList) ?? DMValueType.Anything;
+ nextPath = returnTypes.HasPath ? returnTypes.TypePath : returnTypes.AsPath();
+ if (!returnTypes.HasPath & nextPath.HasValue) {
+ var thePath = nextPath!.Value;
+ thePath.Type = DreamPath.PathType.UpwardSearch;
+ nextPath = thePath;
+ }
+
var procId = fromObject.GetProcs(field)![^1];
ObjectTree.AllProcs[procId].EmitUsageWarnings(callOperation.Location);
}
@@ -1064,10 +1078,11 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
Parameters = argumentList,
Safe = callOperation.Safe,
Identifier = field,
- Path = null
+ Path = prevPath
};
- prevPath = null;
- pathIsFuzzy = true;
+ prevPath = nextPath;
+ if(prevPath is null)
+ pathIsFuzzy = true;
break;
}
@@ -1089,7 +1104,7 @@ private DMExpression BuildLocate(DMASTLocate locate, DreamPath? inferredPath) {
if (inferredPath == null)
return BadExpression(WarningCode.BadExpression, locate.Location, "inferred locate requires a type");
- return new LocateInferred(locate.Location, inferredPath.Value, container);
+ return new LocateInferred(Compiler, locate.Location, inferredPath.Value, container);
}
var pathExpr = BuildExpression(locate.Expression, inferredPath);
@@ -1136,7 +1151,7 @@ private DMExpression BuildList(DMASTList list, DreamPath? inferredPath) {
if (list.IsAList) {
return new AList(list.Location, values!);
} else {
- return new List(list.Location, values);
+ return new List(Compiler, list.Location, values);
}
}
@@ -1243,7 +1258,7 @@ private DMExpression BuildPick(DMASTPick pick, DreamPath? inferredPath) {
pickValues[i] = new Pick.PickValue(weight, value);
}
- return new Pick(pick.Location, pickValues);
+ return new Pick(Compiler, pick.Location, pickValues);
}
private DMExpression BuildLog(DMASTLog log, DreamPath? inferredPath) {
diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs
index e93d90a0d8..1e1761d0c5 100644
--- a/DMCompiler/DM/Builders/DMProcBuilder.cs
+++ b/DMCompiler/DM/Builders/DMProcBuilder.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using DMCompiler.Bytecode;
using DMCompiler.Compiler;
using DMCompiler.Compiler.DM;
@@ -9,6 +10,11 @@ namespace DMCompiler.DM.Builders;
internal sealed class DMProcBuilder(DMCompiler compiler, DMObject dmObject, DMProc proc) {
private readonly DMExpressionBuilder _exprBuilder = new(new(compiler, dmObject, proc));
+ ///
+ /// This tracks the current return type.
+ ///
+ private DMComplexValueType _currentReturnType = DMValueType.Null;
+
private ExpressionContext ExprContext => new(compiler, dmObject, proc);
public void ProcessProcDefinition(DMASTProcDefinition procDefinition) {
@@ -36,9 +42,45 @@ public void ProcessProcDefinition(DMASTProcDefinition procDefinition) {
}
ProcessBlockInner(procDefinition.Body, silenceEmptyBlockWarning : true);
+ // implicit return
+ CheckBlockReturnRecursive(procDefinition.Body);
proc.ResolveLabels();
}
+ private void CheckBlockReturnRecursive(DMASTProcBlockInner block) {
+ if (block.Statements.Length <= 0) {
+ proc.ValidateReturnType(_currentReturnType, null, block.Location);
+ return;
+ }
+
+ var lastStatement = block.Statements[^1];
+ switch (lastStatement) {
+ case DMASTProcStatementIf ifStatement:
+ CheckBlockReturnRecursive(ifStatement.Body);
+ if (ifStatement.ElseBody is not null)
+ CheckBlockReturnRecursive(ifStatement.ElseBody);
+ else // if statement is non-exhaustive, so check for an implicit return after
+ proc.ValidateReturnType(_currentReturnType, null, lastStatement.Location);
+ break;
+ case DMASTProcStatementSwitch switchStatement:
+ if (!switchStatement.Cases.Any()) // No cases to check?
+ break;
+ foreach (var switchCase in switchStatement.Cases) {
+ CheckBlockReturnRecursive(switchCase.Body);
+ }
+
+ if (!switchStatement.Cases.Any(x => x is DMASTProcStatementSwitch.SwitchCaseDefault)) // non-exhaustive, implicit return
+ proc.ValidateReturnType(_currentReturnType, null, switchStatement.Cases.Last().Body.Location);
+ break;
+ case DMASTProcStatementReturn:
+ case DMASTProcStatementExpression { Expression: DMASTAssign { LHS: DMASTCallableSelf } }:
+ break; // already checked elsewhere
+ default:
+ proc.ValidateReturnType(_currentReturnType, null, lastStatement.Location);
+ break;
+ }
+ }
+
/// The block to process
/// Used to avoid emitting noisy warnings about procs with nothing in them.
/// FIXME: Eventually we should try to be smart enough to emit the error anyway for procs that
@@ -104,8 +146,53 @@ private void ProcessStatement(DMASTProcStatement statement) {
}
}
+ ///
+ /// Returns true if the expression has DMASTCallableSelf as the leftmost innermost expression.
+ ///
+ ///
+ public void HandleCallableSelfLeft(DMASTExpression expr, ref DMComplexValueType currentReturnType, DMExpression realExpr, out bool isTemporary, bool checkConditions = false) {
+ isTemporary = false;
+ var checkedExpression = expr.GetUnwrapped();
+ switch (checkedExpression) {
+ case DMASTNot not:
+ DMASTUnary astUnary = not;
+ UnaryOp unaryExpr = (UnaryOp)realExpr;
+ HandleCallableSelfLeft(astUnary.Value.GetUnwrapped(), ref currentReturnType, unaryExpr.Expr, out var _);
+ break;
+ case DMASTEqual astEqual: // a special case: we don't set it but we know lhs = rhs inside this block anyway
+ if (!checkConditions) break;
+ if (astEqual.LHS.GetUnwrapped() is DMASTCallableSelf) {
+ isTemporary = true; // signal to the caller that this should be reset once we leave the current scope
+ if (realExpr is IsNull) {
+ // hate this, remove when it uses a bytecode op instead
+ currentReturnType = DMValueType.Null;
+ break;
+ }
+
+ Equal equalExpr = (Equal)realExpr;
+ currentReturnType = equalExpr.RHS.ValType;
+ }
+
+ break;
+ case DMASTAssign astAssignment:
+ Assignment assignExpr = (Assignment)realExpr;
+ if (astAssignment.LHS.GetUnwrapped() is DMASTCallableSelf)
+ currentReturnType = assignExpr.RHS.ValType;
+ break;
+ case DMASTLogicalOrAssign assign:
+ DMASTBinary astBinary = assign;
+ if (astBinary.LHS.GetUnwrapped() is not DMASTCallableSelf)
+ break;
+ if (currentReturnType.Type != DMValueType.Null)
+ break;
+ currentReturnType = DMComplexValueType.MergeComplexValueTypes(compiler, currentReturnType, realExpr.ValType);
+ break;
+ }
+ }
+
private void ProcessStatementExpression(DMASTProcStatementExpression statement) {
- _exprBuilder.Emit(statement.Expression);
+ _exprBuilder.Emit(statement.Expression, out var expr);
+ HandleCallableSelfLeft(statement.Expression, ref _currentReturnType, expr, out var _);
proc.Pop();
}
@@ -171,10 +258,10 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var
if (varDeclaration.Value != null) {
value = _exprBuilder.Create(varDeclaration.Value, varDeclaration.Type);
- if (!varDeclaration.ValType.MatchesType(compiler, value.ValType)) {
+ var varValType = varDeclaration.ValType ?? DMValueType.Anything;
+ if (!varValType.MatchesType(compiler, value.ValType) && (!value.ValType.IsAnything || !compiler.Settings.SkipAnythingTypecheck))
compiler.Emit(WarningCode.InvalidVarType, varDeclaration.Location,
- $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected {varDeclaration.ValType}");
- }
+ $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected one of {varDeclaration.ValType}");
} else {
value = new Null(varDeclaration.Location);
}
@@ -188,7 +275,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var
successful = proc.TryAddLocalConstVariable(varDeclaration.Name, varDeclaration.Type, constValue);
} else {
- successful = proc.TryAddLocalVariable(varDeclaration.Name, varDeclaration.Type, varDeclaration.ValType);
+ successful = proc.TryAddLocalVariable(varDeclaration.Name, varDeclaration.Type, varDeclaration.ExplicitValType);
}
if (!successful) {
@@ -202,7 +289,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var
}
private void ProcessStatementReturn(DMASTProcStatementReturn statement) {
- if (statement.Value != null) {
+ if (statement.Value is not null and not DMASTCallableSelf) {
var expr = _exprBuilder.Create(statement.Value);
// Don't type-check unimplemented procs
@@ -216,6 +303,7 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) {
expr.EmitPushValue(ExprContext);
} else {
+ proc.ValidateReturnType(_currentReturnType, null, statement.Location);
proc.PushReferenceValue(DMReference.Self); //Default return value
}
@@ -223,7 +311,10 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) {
}
private void ProcessStatementIf(DMASTProcStatementIf statement) {
- _exprBuilder.Emit(statement.Condition);
+ _exprBuilder.Emit(statement.Condition, out var expr);
+ var prevReturnType = _currentReturnType;
+ HandleCallableSelfLeft(statement.Condition, ref _currentReturnType, expr, out var isTemporary, checkConditions: true);
+ var condReturnType = _currentReturnType;
if (statement.ElseBody == null) {
string endLabel = proc.NewLabelName();
@@ -232,6 +323,7 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) {
proc.StartScope();
ProcessBlockInner(statement.Body);
proc.EndScope();
+ _currentReturnType = condReturnType;
proc.AddLabel(endLabel);
} else {
string elseLabel = proc.NewLabelName();
@@ -242,21 +334,29 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) {
proc.StartScope();
ProcessBlockInner(statement.Body);
proc.EndScope();
+ _currentReturnType = condReturnType;
+ if (isTemporary)
+ _currentReturnType = prevReturnType;
proc.Jump(endLabel);
proc.AddLabel(elseLabel);
+ // else bodies are exhaustive, don't reset returntype
+ // if it's an elseif that's handled in ProcessBlockInner
proc.StartScope();
ProcessBlockInner(statement.ElseBody);
proc.EndScope();
proc.AddLabel(endLabel);
}
+
+ if (isTemporary)
+ _currentReturnType = prevReturnType;
}
private void ProcessStatementFor(DMASTProcStatementFor statementFor) {
proc.StartScope();
{
foreach (var decl in FindVarDecls(statementFor.Expression1)) {
- ProcessStatementVarDeclaration(new DMASTProcStatementVarDeclaration(statementFor.Location, decl.DeclPath, null, DMValueType.Anything));
+ ProcessStatementVarDeclaration(new DMASTProcStatementVarDeclaration(statementFor.Location, decl.DeclPath, null, null));
}
if (statementFor is { Expression2: DMASTExpressionIn dmastIn, Expression3: null }) { // for(var/i,j in expr) or for(i,j in expr)
@@ -283,7 +383,10 @@ private void ProcessStatementFor(DMASTProcStatementFor statementFor) {
switch (keyVar) {
case Local outputLocal: {
- outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes;
+ if (statementFor.DMTypes is not null)
+ outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes;
+ else if (list.ValType is { IsList: true, ListValueTypes: not null })
+ outputLocal.LocalVar.ExplicitValueType = list.ValType.ListValueTypes.NestedListKeyType;
if(outputLocal.LocalVar is DMProc.LocalConstVariable)
compiler.Emit(WarningCode.WriteToConstant, outputExpr.Location, "Cannot change constant value");
break;
@@ -691,7 +794,9 @@ private void ProcessStatementSwitch(DMASTProcStatementSwitch statementSwitch) {
List<(string CaseLabel, DMASTProcBlockInner CaseBody)> valueCases = new();
DMASTProcBlockInner? defaultCaseBody = null;
- _exprBuilder.Emit(statementSwitch.Value);
+ _exprBuilder.Emit(statementSwitch.Value, out var expr);
+ HandleCallableSelfLeft(statementSwitch.Value, ref _currentReturnType, expr, out var _);
+ // todo: some sort of return type inference based on cases for conditions switching on .
foreach (DMASTProcStatementSwitch.SwitchCase switchCase in statementSwitch.Cases) {
if (switchCase is DMASTProcStatementSwitch.SwitchCaseValues switchCaseValues) {
string caseLabel = proc.NewLabelName();
@@ -749,26 +854,34 @@ Constant CoerceBound(Constant bound, bool upperRange) {
proc.Pop();
+ DMComplexValueType oldReturnType = _currentReturnType;
+ DMComplexValueType? defaultCaseReturnType = null;
if (defaultCaseBody != null) {
+ // if a default case exists, the switch is exhaustive so we go with its returntype
proc.StartScope();
{
ProcessBlockInner(defaultCaseBody);
}
proc.EndScope();
+ defaultCaseReturnType = _currentReturnType;
+ _currentReturnType = oldReturnType; // the default case hasn't taken effect for the rest of the cases
}
proc.Jump(endLabel);
foreach ((string CaseLabel, DMASTProcBlockInner CaseBody) valueCase in valueCases) {
proc.AddLabel(valueCase.CaseLabel);
+ oldReturnType = _currentReturnType;
proc.StartScope();
{
ProcessBlockInner(valueCase.CaseBody);
}
proc.EndScope();
+ _currentReturnType = oldReturnType;
proc.Jump(endLabel);
}
+ _currentReturnType = defaultCaseReturnType ?? oldReturnType;
proc.AddLabel(endLabel);
}
@@ -859,7 +972,7 @@ private void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) {
string endLabel = proc.NewLabelName();
if (tryCatch.CatchParameter is DMASTProcStatementVarDeclaration param) {
- if (!proc.TryAddLocalVariable(param.Name, param.Type, param.ValType)) {
+ if (!proc.TryAddLocalVariable(param.Name, param.Type, param.ExplicitValType)) {
compiler.Emit(WarningCode.DuplicateVariable, param.Location, $"Duplicate var {param.Name}");
}
diff --git a/DMCompiler/DM/DMCodeTree.Vars.cs b/DMCompiler/DM/DMCodeTree.Vars.cs
index bdb30ec3b9..6357296beb 100644
--- a/DMCompiler/DM/DMCodeTree.Vars.cs
+++ b/DMCompiler/DM/DMCodeTree.Vars.cs
@@ -37,7 +37,7 @@ protected void SetVariableValue(DMCompiler compiler, DMObject dmObject, DMVariab
compiler.Emit(WarningCode.ImplicitNullType, value.Location, $"{dmObject.Path}.{variable.Name}: Variable is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable.");
variable.ValType |= DMValueType.Null;
} else {
- compiler.Emit(WarningCode.InvalidVarType, value.Location, $"{dmObject.Path}.{variable.Name}: Invalid var value type {value.ValType}, expected {variable.ValType}");
+ compiler.Emit(WarningCode.InvalidVarType, value.Location, $"{dmObject.Path}.{variable.Name}: Invalid var value type {value.ValType}, expected one of {variable.ValType}");
}
}
diff --git a/DMCompiler/DM/DMExpression.cs b/DMCompiler/DM/DMExpression.cs
index dd450d3808..e9a9e60389 100644
--- a/DMCompiler/DM/DMExpression.cs
+++ b/DMCompiler/DM/DMExpression.cs
@@ -110,7 +110,6 @@ private void VerifyArgType(DMCompiler compiler, DMProc targetProc, int index, st
// TODO: Make a separate "UnsetStaticType" pragma for whether we should care if it's Anything
// TODO: We currently silently avoid typechecking "call()()" and "new" args (NewPath is handled)
// TODO: We currently don't handle variadic args (e.g. min())
- // TODO: Dereference.CallOperation does not pass targetProc
DMProc.LocalVariable? param;
if (name != null) {
@@ -131,9 +130,14 @@ private void VerifyArgType(DMCompiler compiler, DMProc targetProc, int index, st
DMComplexValueType paramType = param.ExplicitValueType ?? DMValueType.Anything;
- if (!expr.ValType.IsAnything && !paramType.MatchesType(compiler, expr.ValType)) {
- compiler.Emit(WarningCode.InvalidVarType, expr.Location,
- $"{targetProc.Name}(...) argument \"{param.Name}\": Invalid var value type {expr.ValType}, expected {paramType}");
+ if (!(compiler.Settings.SkipAnythingTypecheck && expr.ValType.IsAnything) && !paramType.MatchesType(compiler, expr.ValType)) {
+ if (expr is Null) {
+ compiler.Emit(WarningCode.ImplicitNullType, expr.Location, $"{targetProc.Name}(...) argument \"{param.Name}\": Argument is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable.");
+ param.ExplicitValueType |= DMValueType.Null;
+ } else {
+ compiler.Emit(WarningCode.InvalidVarType, expr.Location,
+ $"{targetProc.Name}(...) argument \"{param.Name}\": Invalid var value type {expr.ValType}, expected {paramType}");
+ }
}
}
}
diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs
index 39964c217d..70e6dbe4cd 100644
--- a/DMCompiler/DM/DMObject.cs
+++ b/DMCompiler/DM/DMObject.cs
@@ -97,6 +97,10 @@ public bool OwnsProc(string name) {
}
public DMComplexValueType? GetProcReturnTypes(string name) {
+ return GetProcReturnTypes(name, null);
+ }
+
+ public DMComplexValueType? GetProcReturnTypes(string name, ArgumentList? arguments) {
if (this == compiler.DMObjectTree.Root && compiler.DMObjectTree.TryGetGlobalProc(name, out var globalProc))
return globalProc.RawReturnTypes;
if (GetProcs(name) is not { } procs)
@@ -232,14 +236,15 @@ public bool IsSubtypeOf(DreamPath path) {
return Parent != null && Parent.IsSubtypeOf(path);
}
- public DMValueType GetDMValueType() {
- if (IsSubtypeOf(DreamPath.Mob))
- return DMValueType.Mob;
- if (IsSubtypeOf(DreamPath.Obj))
- return DMValueType.Obj;
- if (IsSubtypeOf(DreamPath.Area))
- return DMValueType.Area;
+ public DreamPath GetLastCommonAncestor(DMObject other) {
+ if(other.IsSubtypeOf(Path)) {
+ return Path;
+ }
+
+ if(IsSubtypeOf(other.Path)) {
+ return other.Path;
+ }
- return DMValueType.Anything;
+ return Parent?.GetLastCommonAncestor(other) ?? DreamPath.Root;
}
}
diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs
index 2ac610ece9..e98d12e58e 100644
--- a/DMCompiler/DM/DMObjectTree.cs
+++ b/DMCompiler/DM/DMObjectTree.cs
@@ -165,7 +165,7 @@ public bool TryGetTypeId(DreamPath path, out int typeId) {
return null;
}
- public int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, bool isFinal, DMComplexValueType valType) {
+ public int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, bool isFinal, DMComplexValueType? valType) {
int id = Globals.Count;
global = new DMVariable(type, name, true, isConst, isFinal, false, valType);
diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs
index df36b4afeb..9ac22ad130 100644
--- a/DMCompiler/DM/DMProc.cs
+++ b/DMCompiler/DM/DMProc.cs
@@ -168,10 +168,13 @@ public void Compile() {
}
public void ValidateReturnType(DMExpression expr) {
- var type = expr.ValType;
- var returnTypes = _dmObject.GetProcReturnTypes(Name)!.Value;
+ ValidateReturnType(expr.ValType, expr, expr.Location);
+ }
+
+ public void ValidateReturnType(DMComplexValueType type, DMExpression? expr, Location location) {
+ var returnTypes = _dmObject.GetProcReturnTypes(Name) ?? DMValueType.Anything;
if ((returnTypes.Type & (DMValueType.Color | DMValueType.File | DMValueType.Message)) != 0) {
- _compiler.Emit(WarningCode.UnsupportedTypeCheck, expr.Location, "color, message, and file return types are currently unsupported.");
+ _compiler.Emit(WarningCode.UnsupportedTypeCheck, Location, "color, message, and file return types are currently unsupported.");
return;
}
@@ -183,17 +186,19 @@ public void ValidateReturnType(DMExpression expr) {
switch (expr) {
case ProcCall:
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}.");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}.");
break;
case Local:
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\"");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\"");
+ break;
+ case null:
break;
default:
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub.");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub.");
break;
}
} else if (!ReturnTypes.MatchesType(_compiler, type)) { // We could determine the return types but they don't match
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}");
}
}
@@ -213,13 +218,13 @@ public ProcDefinitionJson GetJsonRepresentation() {
argumentType = DMValueType.Anything;
} else {
_compiler.DMObjectTree.TryGetDMObject(typePath, out var type);
- argumentType = type?.GetDMValueType() ?? DMValueType.Anything;
+ argumentType = type?.Path.GetAtomType(_compiler) ?? DMValueType.Anything;
}
}
arguments.Add(new ProcArgumentJson {
Name = parameter.Name,
- Type = argumentType.Type
+ Type = argumentType.Type & ~(DMValueType.Instance|DMValueType.Path)
});
}
}
@@ -500,7 +505,7 @@ public bool TryGetParameterAtIndex(int index, [NotNullWhen(true)] out LocalVaria
return label;
}
- public bool TryAddLocalVariable(string name, DreamPath? type, DMComplexValueType valType) {
+ public bool TryAddLocalVariable(string name, DreamPath? type, DMComplexValueType? valType) {
if (_parameters.ContainsKey(name)) //Parameters and local vars cannot share a name
return false;
@@ -538,6 +543,20 @@ public DMReference GetLocalVariableReference(string name) {
return local.IsParameter ? DMReference.CreateArgument(local.Id) : DMReference.CreateLocal(local.Id);
}
+ public DMProc GetBaseProc(DMObject? dmObject = null) {
+ if (dmObject == null) dmObject = _dmObject;
+ if (dmObject == _compiler.DMObjectTree.Root && _compiler.DMObjectTree.TryGetGlobalProc(Name, out var globalProc))
+ return globalProc;
+ if (dmObject.GetProcs(Name) is not { } procs)
+ return dmObject.Parent is not null ? GetBaseProc(dmObject.Parent) : this;
+
+ var proc = _compiler.DMObjectTree.AllProcs[procs[0]];
+ if ((proc.Attributes & ProcAttributes.IsOverride) != 0)
+ return dmObject.Parent is not null ? GetBaseProc(dmObject.Parent) : this;
+
+ return proc;
+ }
+
public void Error() {
WriteOpcode(DreamProcOpcode.Error);
}
diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs
index 5f02cfbe13..ce80bc5711 100644
--- a/DMCompiler/DM/DMValueType.cs
+++ b/DMCompiler/DM/DMValueType.cs
@@ -23,13 +23,14 @@ public enum DMValueType {
CommandText = 0x400,
Sound = 0x800,
Icon = 0x1000,
- Path = 0x2000, // For proc return types
+ Instance = 0x2000, // For proc return types
+ Path = 0x4000,
//Byond here be dragons
- Unimplemented = 0x4000, // Marks that a method or property is not implemented. Throws a compiler warning if accessed.
- CompiletimeReadonly = 0x8000, // Marks that a property can only ever be read from, never written to. This is a const-ier version of const, for certain standard values like list.type
- NoConstFold = 0x10000, // Marks that a const var cannot be const-folded during compile
- Unsupported = 0x20000, // Marks that a method or property will not be not implemented. Throws a compiler warning if accessed.
+ Unimplemented = 0x8000, // Marks that a method or property is not implemented. Throws a compiler warning if accessed.
+ CompiletimeReadonly = 0x10000, // Marks that a property can only ever be read from, never written to. This is a const-ier version of const, for certain standard values like list.type
+ NoConstFold = 0x20000, // Marks that a const var cannot be const-folded during compile
+ Unsupported = 0x40000 // Marks that a method or property will not be not implemented. Throws a compiler warning if accessed.
}
///
@@ -40,10 +41,19 @@ public readonly struct DMComplexValueType {
public readonly DreamPath? TypePath;
public bool IsAnything => Type == DMValueType.Anything;
- public bool IsPath => Type.HasFlag(DMValueType.Path);
+ public bool IsInstance => Type.HasFlag(DMValueType.Instance);
+ public bool HasPath => Type.HasFlag(DMValueType.Path) | Type.HasFlag(DMValueType.Instance);
public bool IsUnimplemented { get; }
public bool IsUnsupported { get; }
public bool IsCompileTimeReadOnly { get; }
+ public bool IsList => IsInstance && TypePath == DreamPath.List;
+
+ ///
+ /// A pointer to a class wrapping the key and value DMComplexValueTypes for a list.
+ /// This cannot be a struct because that would create a cycle in the struct representation.
+ /// Sorry about the heap allocation.
+ ///
+ public readonly DMListValueTypes? ListValueTypes;
public DMComplexValueType(DMValueType type, DreamPath? typePath) {
Type = type & ~(DMValueType.Unimplemented | DMValueType.CompiletimeReadonly); // Ignore these 2 types
@@ -52,19 +62,76 @@ public DMComplexValueType(DMValueType type, DreamPath? typePath) {
IsUnsupported = type.HasFlag(DMValueType.Unsupported);
IsCompileTimeReadOnly = type.HasFlag(DMValueType.CompiletimeReadonly);
- if (IsPath && TypePath == null)
- throw new Exception("A Path value type must have a type-path");
+ if (HasPath && TypePath == null)
+ throw new Exception("A Path or Instance value type must have a type-path");
+ }
+
+ public DMComplexValueType(DMValueType type, DreamPath? typePath, DMListValueTypes? listValueTypes) : this(type,
+ typePath) {
+ ListValueTypes = listValueTypes;
}
public bool MatchesType(DMValueType type) {
- return IsAnything || (Type & type) != 0;
+ if (IsAnything || (Type & type) != 0) return true;
+ if((type & DMValueType.Icon) != 0 && TypePath == DreamPath.Icon) return true; // var/icon/foo is not explicitly "as icon"
+ if((type & DMValueType.Sound) != 0 && TypePath == DreamPath.Sound) return true; // var/sound/foo is not explicitly "as sound"
+ if ((type & (DMValueType.Text | DMValueType.Message)) != 0 && (Type & (DMValueType.Text | DMValueType.Message)) != 0) return true;
+ if ((type & (DMValueType.Text | DMValueType.Color)) != 0 && (Type & (DMValueType.Text | DMValueType.Color)) != 0) return true;
+ return false;
}
internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) {
- if (IsPath && type.IsPath) {
- if (compiler.DMObjectTree.TryGetDMObject(type.TypePath!.Value, out var dmObject) &&
- dmObject.IsSubtypeOf(TypePath!.Value)) // Allow subtypes
+ // Exclude checking path and null here; primitives only.
+ if (MatchesType(type.Type & ~(DMValueType.Path|DMValueType.Instance|DMValueType.Null)))
+ return true;
+ // If we have a /icon, we have an icon; if we have a /obj, we have an obj; etc.
+ if (IsInstance) {
+ if (type.MatchesType(TypePath!.Value.GetAtomType(compiler))) {
return true;
+ }
+
+ var theirPath = type.AsPath();
+ if (type.ListValueTypes is null && theirPath is not null) {
+ compiler.DMObjectTree.TryGetDMObject(theirPath!.Value, out var theirObject);
+ if (theirObject?.IsSubtypeOf(TypePath!.Value) is true) {
+ return true;
+ }
+ }
+ }
+
+ // special case for color and lists:
+ if (type.Type.HasFlag(DMValueType.Color) && IsList && ListValueTypes?.NestedListKeyType.Type == DMValueType.Num && ListValueTypes.NestedListValType is null)
+ return true;
+ // probably only one of these is correct but i can't be assed to figure out which
+ if (Type.HasFlag(DMValueType.Color) && type is { IsList: true, ListValueTypes: { NestedListKeyType.Type: DMValueType.Num, NestedListValType: null } })
+ return true;
+ if (type.IsInstance && MatchesType(type.TypePath!.Value.GetAtomType(compiler))) {
+ return true;
+ }
+
+ if (HasPath && type.HasPath) {
+ compiler.DMObjectTree.TryGetDMObject(type.TypePath!.Value, out var dmObject);
+
+ // Allow subtypes
+ if (dmObject?.IsSubtypeOf(TypePath!.Value) is false) {
+ compiler.DMObjectTree.TryGetDMObject(TypePath!.Value, out var ourObject);
+ return ourObject?.IsSubtypeOf(type.TypePath!.Value) ?? false;
+ }
+
+ // If ListValueTypes is non-null, we do more advanced checks.
+ // The other type's must also be non-null, because we consider empty lists as matching.
+ if (ListValueTypes is not null && type.ListValueTypes is not null) {
+ // Have to do an actual match check here. This can get expensive, but thankfully it's pretty rare.
+ if (!ListValueTypes.NestedListKeyType.MatchesType(compiler, type.ListValueTypes!.NestedListKeyType))
+ return false;
+ // If we're assoc (have value types rather than just keys), then the other list must match as well.
+ if (ListValueTypes?.NestedListValType is not null) {
+ if (type.ListValueTypes!.NestedListValType is not null && !ListValueTypes.NestedListValType!.Value.MatchesType(compiler, type.ListValueTypes!.NestedListValType.Value))
+ return false;
+ if (type.ListValueTypes!.NestedListValType is null && !ListValueTypes.NestedListValType!.Value.MatchesType(DMValueType.Null))
+ return false;
+ }
+ }
}
return MatchesType(type.Type);
@@ -73,12 +140,66 @@ internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) {
public override string ToString() {
var types = Type.ToString().ToLowerInvariant();
- return $"\"{(IsPath ? types + ", " + TypePath!.Value : types)}\"";
+ return $"\"{(HasPath ? types + $" of type {TypePath!.Value}{((IsList && ListValueTypes is not null) ? $"({ListValueTypes})" : "")}" : types)}\"".Replace(", ", "|");
}
public static implicit operator DMComplexValueType(DMValueType type) => new(type, null);
- public static implicit operator DMComplexValueType(DreamPath path) => new(DMValueType.Path, path);
+ public static implicit operator DMComplexValueType(DreamPath path) => new(DMValueType.Instance, path);
public static DMComplexValueType operator |(DMComplexValueType type1, DMValueType type2) =>
new(type1.Type | type2, type1.TypePath);
+
+ // This cannot be an operator because we need DMCompiler compiler.
+ public static DMComplexValueType MergeComplexValueTypes(DMCompiler compiler, DMComplexValueType type1, DMComplexValueType type2) {
+ if (type2.TypePath is null) {
+ return type1 | type2.Type;
+ }
+
+ if (type1.TypePath is null) {
+ return type2 | type1.Type;
+ }
+
+ // Take the common ancestor of both types
+ return new(type1.Type | type2.Type, type1.TypePath.Value.GetLastCommonAncestor(compiler, type2.TypePath.Value));
+ }
+
+ public DreamPath? AsPath() {
+ return (HasPath ? TypePath : null) ?? (Type & ~DMValueType.Null) switch {
+ DMValueType.Mob => DreamPath.Mob,
+ DMValueType.Icon => DreamPath.Icon,
+ DMValueType.Obj => DreamPath.Obj,
+ DMValueType.Turf => DreamPath.Turf,
+ DMValueType.Area => DreamPath.Area,
+ DMValueType.Obj | DMValueType.Mob => DreamPath.Movable,
+ DMValueType.Area | DMValueType.Turf | DMValueType.Obj | DMValueType.Mob => DreamPath.Atom,
+ DMValueType.Sound => DreamPath.Sound,
+ _ => null
+ };
+ }
+}
+
+public class DMListValueTypes(DMComplexValueType nestedListKeyType, DMComplexValueType? nestedListValType) {
+ public DMComplexValueType NestedListKeyType => nestedListKeyType;
+ public DMComplexValueType? NestedListValType => nestedListValType;
+
+ public static DMListValueTypes MergeListValueTypes(DMCompiler compiler, DMListValueTypes type1, DMListValueTypes type2) {
+ return new(DMComplexValueType.MergeComplexValueTypes(compiler, type1.NestedListKeyType, type2.NestedListKeyType), (type1.NestedListValType.HasValue && type2.NestedListValType.HasValue) ? DMComplexValueType.MergeComplexValueTypes(compiler, type1.NestedListValType.Value, type2.NestedListValType.Value) : (type1.NestedListValType ?? type2.NestedListValType));
+ }
+
+ public static DMComplexValueType GetKeyValType(DMListValueTypes? listValueTypes) {
+ if (listValueTypes is null) return DMValueType.Anything;
+ return listValueTypes.NestedListKeyType | DMValueType.Null;
+ }
+
+ public static DMComplexValueType GetValueValType(DMListValueTypes? listValueTypes) {
+ if (listValueTypes is null) return DMValueType.Anything;
+ if (listValueTypes.NestedListValType is null) return listValueTypes.NestedListKeyType; // non-assoc list, keys are also values
+ return (DMComplexValueType)(listValueTypes.NestedListValType | DMValueType.Null);
+ }
+
+ public override string ToString() {
+ if (NestedListValType is not null)
+ return $"{NestedListKeyType} = {NestedListValType}";
+ return NestedListKeyType.ToString();
+ }
}
diff --git a/DMCompiler/DM/DMVariable.cs b/DMCompiler/DM/DMVariable.cs
index 131cdc00dc..ac9af704b5 100644
--- a/DMCompiler/DM/DMVariable.cs
+++ b/DMCompiler/DM/DMVariable.cs
@@ -28,7 +28,8 @@ public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, boo
IsFinal = isFinal;
IsTmp = isTmp;
Value = null;
- ValType = valType ?? DMValueType.Anything;
+ DMComplexValueType atomType = Type is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Path, Type) : DMValueType.Anything;
+ ValType = valType ?? (!atomType.IsAnything ? atomType | DMValueType.Null : (Type is null ? DMValueType.Anything : new DMComplexValueType(DMValueType.Instance | DMValueType.Path | DMValueType.Null, Type)));
}
public DMVariable(DMVariable copyFrom) {
diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs
index c05a7d3d4c..58f6addf79 100644
--- a/DMCompiler/DM/Expressions/Binary.cs
+++ b/DMCompiler/DM/Expressions/Binary.cs
@@ -5,8 +5,8 @@
namespace DMCompiler.DM.Expressions;
internal abstract class BinaryOp(Location location, DMExpression lhs, DMExpression rhs) : DMExpression(location) {
- protected DMExpression LHS { get; } = lhs;
- protected DMExpression RHS { get; } = rhs;
+ public DMExpression LHS { get; } = lhs;
+ public DMExpression RHS { get; } = rhs;
public override DMComplexValueType ValType => LHS.ValType;
public override bool PathIsFuzzy => true;
@@ -383,6 +383,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x == y
internal sealed class Equal(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -392,6 +394,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x != y
internal sealed class NotEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -401,6 +405,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x ~= y
internal sealed class Equivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -410,6 +416,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x ~! y
internal sealed class NotEquivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -419,6 +427,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x > y
internal sealed class GreaterThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) {
constant = null;
@@ -451,6 +461,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x >= y
internal sealed class GreaterThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
if (TryAsConstant(ctx.Compiler, out var constant)) {
constant.EmitPushValue(ctx);
@@ -483,6 +495,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out
// x < y
internal sealed class LessThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
if (TryAsConstant(ctx.Compiler, out var constant)) {
constant.EmitPushValue(ctx);
@@ -515,6 +529,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out
// x <= y
internal sealed class LessThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
if (TryAsConstant(ctx.Compiler, out var constant)) {
constant.EmitPushValue(ctx);
@@ -581,6 +597,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x && y
internal sealed class And(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => RHS.ValType;
+
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (LHS.TryAsConstant(compiler, out var lhs) && !lhs.IsTruthy()) {
constant = lhs;
@@ -623,6 +641,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x in y
internal sealed class In(Location location, DMExpression expr, DMExpression container) : BinaryOp(location, expr, container) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool
+
public override void EmitPushValue(ExpressionContext ctx) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -658,6 +678,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// x = y
internal sealed class Assignment(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) {
public override DreamPath? Path => LHS.Path;
+ public override DMComplexValueType ValType => RHS.ValType;
protected override void EmitOp(ExpressionContext ctx, DMReference reference, string endLabel) {
RHS.EmitPushValue(ctx);
@@ -668,7 +689,7 @@ protected override void EmitOp(ExpressionContext ctx, DMReference reference, str
return;
ctx.Compiler.Emit(WarningCode.InvalidVarType, Location,
- $"Invalid var type {RHS.ValType}, expected {LHS.ValType}");
+ $"Invalid var type {RHS.ValType}, expected one of {LHS.ValType}");
}
}
}
@@ -686,6 +707,8 @@ protected override void EmitOp(ExpressionContext ctx, DMReference reference,
// x += y
internal sealed class Append(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) {
+ public override DMComplexValueType ValType => LHS.ValType.Type == DMValueType.Null ? RHS.ValType : LHS.ValType;
+
protected override void EmitOp(ExpressionContext ctx, DMReference reference,
string endLabel) {
RHS.EmitPushValue(ctx);
diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs
index f8b995968f..781e3e70fe 100644
--- a/DMCompiler/DM/Expressions/Builtins.cs
+++ b/DMCompiler/DM/Expressions/Builtins.cs
@@ -62,7 +62,17 @@ public void EmitPushArglist(ExpressionContext ctx) {
internal sealed class New(DMCompiler compiler, Location location, DMExpression expr, ArgumentList arguments) : DMExpression(location) {
public override DreamPath? Path => expr.Path;
public override bool PathIsFuzzy => Path == null;
- public override DMComplexValueType ValType => !expr.ValType.IsAnything ? expr.ValType : (Path?.GetAtomType(compiler) ?? DMValueType.Anything);
+
+ public override DMComplexValueType ValType {
+ get {
+ var exprType = expr.ValType;
+ if (exprType.IsAnything)
+ return Path is not null ? new DMComplexValueType(DMValueType.Instance, Path) : DMValueType.Anything;
+ if (exprType.HasPath && exprType.Type.HasFlag(DMValueType.Path))
+ return exprType.TypePath is not null ? new DMComplexValueType(DMValueType.Instance, exprType.TypePath) : DMValueType.Anything;
+ return exprType;
+ }
+ }
public override void EmitPushValue(ExpressionContext ctx) {
var argumentInfo = arguments.EmitArguments(ctx, null);
@@ -73,9 +83,9 @@ public override void EmitPushValue(ExpressionContext ctx) {
}
// new /x/y/z (...)
-internal sealed class NewPath(DMCompiler compiler, Location location, IConstantPath create, ArgumentList arguments) : DMExpression(location) {
+internal sealed class NewPath(Location location, IConstantPath create, ArgumentList arguments) : DMExpression(location) {
public override DreamPath? Path => (create is ConstantTypeReference typeReference) ? typeReference.Path : null;
- public override DMComplexValueType ValType => Path?.GetAtomType(compiler) ?? DMValueType.Anything;
+ public override DMComplexValueType ValType => Path is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Null, Path) : DMValueType.Anything;
public override void EmitPushValue(ExpressionContext ctx) {
DMCallArgumentsType argumentsType;
@@ -104,8 +114,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
}
// locate()
-internal sealed class LocateInferred(Location location, DreamPath path, DMExpression? container) : DMExpression(location) {
- public override DMComplexValueType ValType => path;
+internal sealed class LocateInferred(DMCompiler compiler, Location location, DreamPath path, DMExpression? container) : DMExpression(location) {
+ public override DMComplexValueType ValType => path.GetAtomType(compiler);
public override void EmitPushValue(ExpressionContext ctx) {
if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) {
@@ -136,6 +146,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// locate(x)
internal sealed class Locate(Location location, DMExpression path, DMExpression? container) : DMExpression(location) {
public override bool PathIsFuzzy => true;
+ public override DMComplexValueType ValType => path.Path is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Null, path.Path) : DMValueType.Anything;
public override void EmitPushValue(ExpressionContext ctx) {
path.EmitPushValue(ctx);
@@ -184,6 +195,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
/// rgb(x, y, z, space)
/// rgb(x, y, z, a, space)
internal sealed class Rgb(Location location, ArgumentList arguments) : DMExpression(location) {
+ public override DMComplexValueType ValType => DMValueType.Color;
+
public override void EmitPushValue(ExpressionContext ctx) {
ctx.ObjectTree.TryGetGlobalProc("rgb", out var dmProc);
var argInfo = arguments.EmitArguments(ctx, dmProc);
@@ -242,12 +255,30 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out
// pick(prob(50);x, prob(200);y)
// pick(50;x, 200;y)
// pick(x, y)
-internal sealed class Pick(Location location, Pick.PickValue[] values) : DMExpression(location) {
+internal sealed class Pick(DMCompiler compiler, Location location, Pick.PickValue[] values) : DMExpression(location) {
public struct PickValue(DMExpression? weight, DMExpression value) {
public readonly DMExpression? Weight = weight;
public readonly DMExpression Value = value;
}
+ public override DMComplexValueType ValType {
+ get {
+ if (values.Length == 1) {
+ var firstValType = values[0].Value.ValType;
+ if (firstValType.IsList) {
+ return firstValType.ListValueTypes?.NestedListKeyType ?? DMValueType.Anything;
+ }
+ }
+
+ DMComplexValueType accumValues = DMValueType.Anything;
+ foreach(PickValue pickVal in values) {
+ accumValues = DMComplexValueType.MergeComplexValueTypes(compiler, accumValues, pickVal.Value.ValType);
+ }
+
+ return accumValues;
+ }
+ }
+
public override void EmitPushValue(ExpressionContext ctx) {
bool weighted = false;
foreach (PickValue pickValue in values) {
@@ -343,6 +374,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// astype(x, y)
internal sealed class AsType(Location location, DMExpression expr, DMExpression path) : DMExpression(location) {
public override DreamPath? Path => path.Path;
+ public override DMComplexValueType ValType => new(DMValueType.Instance | DMValueType.Null, Path);
public override bool PathIsFuzzy => path is not ConstantTypeReference;
public override void EmitPushValue(ExpressionContext ctx) {
@@ -355,6 +387,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// astype(x)
internal sealed class AsTypeInferred(Location location, DMExpression expr, DreamPath path) : DMExpression(location) {
public override DreamPath? Path => path;
+ public override DMComplexValueType ValType => new(DMValueType.Instance | DMValueType.Null, Path);
public override void EmitPushValue(ExpressionContext ctx) {
if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) {
@@ -422,6 +455,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// get_step(ref, dir)
internal sealed class GetStep(Location location, DMExpression refValue, DMExpression dir) : DMExpression(location) {
public override bool PathIsFuzzy => true;
+ public override DMComplexValueType ValType => DMValueType.Turf | DMValueType.Null;
public override void EmitPushValue(ExpressionContext ctx) {
refValue.EmitPushValue(ctx);
@@ -433,6 +467,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// get_dir(loc1, loc2)
internal sealed class GetDir(Location location, DMExpression loc1, DMExpression loc2) : DMExpression(location) {
public override bool PathIsFuzzy => true;
+ public override DMComplexValueType ValType => DMValueType.Num; // invalid/no dir is 0, not null
public override void EmitPushValue(ExpressionContext ctx) {
loc1.EmitPushValue(ctx);
@@ -447,9 +482,10 @@ internal sealed class List : DMExpression {
private readonly bool _isAssociative;
public override bool PathIsFuzzy => true;
- public override DMComplexValueType ValType => DreamPath.List;
+ private DMComplexValueType? _valType;
+ public override DMComplexValueType ValType => _valType ?? DMValueType.Anything;
- public List(Location location, (DMExpression? Key, DMExpression Value)[] values) : base(location) {
+ public List(DMCompiler compiler, Location location, (DMExpression? Key, DMExpression Value)[] values) : base(location) {
_values = values;
_isAssociative = false;
@@ -459,6 +495,16 @@ public List(Location location, (DMExpression? Key, DMExpression Value)[] values)
break;
}
}
+
+ DMComplexValueType keyTypes = DMValueType.Anything;
+ DMComplexValueType valTypes = DMValueType.Anything;
+ foreach((DMExpression? key, DMExpression val) in _values) {
+ valTypes = DMComplexValueType.MergeComplexValueTypes(compiler, valTypes, val.ValType);
+ if(key is not null)
+ keyTypes = DMComplexValueType.MergeComplexValueTypes(compiler, keyTypes, key.ValType);
+ }
+
+ _valType = new DMComplexValueType(DMValueType.Instance, DreamPath.List, !(keyTypes.IsAnything && valTypes.IsAnything) ? (_isAssociative ? new DMListValueTypes(keyTypes, valTypes) : new DMListValueTypes(valTypes, null)) : null);
}
public override void EmitPushValue(ExpressionContext ctx) {
@@ -561,6 +607,8 @@ public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? js
// Value of var/list/L[1][2][3]
internal sealed class DimensionalList(Location location, DMExpression[] sizes) : DMExpression(location) {
+ public override DMComplexValueType ValType => DreamPath.List;
+
public override void EmitPushValue(ExpressionContext ctx) {
foreach (var size in sizes) {
size.EmitPushValue(ctx);
@@ -619,6 +667,16 @@ public override void EmitPushValue(ExpressionContext ctx) {
// initial(x)
internal class Initial(Location location, DMExpression expr) : DMExpression(location) {
+ public override DMComplexValueType ValType {
+ get {
+ if (Expression is LValue lValue) {
+ return lValue.ValType;
+ }
+
+ return DMValueType.Anything;
+ }
+ }
+
protected DMExpression Expression { get; } = expr;
public override void EmitPushValue(ExpressionContext ctx) {
diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs
index 5b169cce5b..6aea61c1eb 100644
--- a/DMCompiler/DM/Expressions/Constant.cs
+++ b/DMCompiler/DM/Expressions/Constant.cs
@@ -100,6 +100,7 @@ internal sealed class Resource : Constant {
private readonly string _filePath;
private bool _isAmbiguous;
+ public override DMComplexValueType ValType { get; }
public Resource(DMCompiler compiler, Location location, string filePath) : base(location) {
// Treat backslashes as forward slashes on Linux
@@ -154,6 +155,16 @@ public Resource(DMCompiler compiler, Location location, string filePath) : base(
_filePath = _filePath.Replace('\\', '/');
compiler.DMObjectTree.Resources.Add(_filePath);
+ ValType = System.IO.Path.GetExtension(fileName) switch {
+ ".dmi" => DMValueType.Icon,
+ ".png" => DMValueType.Icon,
+ ".bmp" => DMValueType.Icon,
+ ".gif" => DMValueType.Icon,
+ ".ogg" => DMValueType.Sound,
+ ".wav" => DMValueType.Sound,
+ ".mid" => DMValueType.Sound,
+ _ => DMValueType.File
+ };
}
public override void EmitPushValue(ExpressionContext ctx) {
@@ -230,7 +241,7 @@ internal class ConstantTypeReference(Location location, DMObject dmObject) : Con
public DMObject Value { get; } = dmObject;
public override DreamPath? Path => Value.Path;
- public override DMComplexValueType ValType => Value.Path;
+ public override DMComplexValueType ValType => new DMComplexValueType(DMValueType.Path, Path);
public override void EmitPushValue(ExpressionContext ctx) {
ctx.Proc.PushType(Value.Id);
diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs
index 87c1e0f669..991c69ea8a 100644
--- a/DMCompiler/DM/Expressions/Dereference.cs
+++ b/DMCompiler/DM/Expressions/Dereference.cs
@@ -36,6 +36,19 @@ public sealed class IndexOperation : Operation {
/// The index expression. (eg. x[expr])
///
public required DMExpression Index { get; init; }
+
+ public DMComplexValueType UnnestValType(DMListValueTypes? listValueTypes) {
+ if (listValueTypes is null) return DMValueType.Anything;
+ if (listValueTypes.NestedListValType is null) return listValueTypes.NestedListKeyType | DMValueType.Null;
+ return Index.ValType.Type switch {
+ // if Index.ValType is only null, we return null
+ DMValueType.Null => DMValueType.Null,
+ // if it's only num, return key
+ DMValueType.Num => listValueTypes.NestedListKeyType,
+ // else, return valtype|null
+ _ => listValueTypes.NestedListValType.Value | DMValueType.Null
+ };
+ }
}
public sealed class CallOperation : NamedOperation {
@@ -48,32 +61,43 @@ public sealed class CallOperation : NamedOperation {
public override DreamPath? Path { get; }
public override DreamPath? NestedPath { get; }
public override bool PathIsFuzzy => Path == null;
- public override DMComplexValueType ValType { get; }
+
+ public override DMComplexValueType ValType {
+ get {
+ _valType ??= DetermineValType(_objectTree);
+ return _valType.Value;
+ }
+ }
+
+ private DMComplexValueType? _valType;
private readonly DMExpression _expression;
private readonly Operation[] _operations;
+ private readonly DMObjectTree _objectTree;
public Dereference(DMObjectTree objectTree, Location location, DreamPath? path, DMExpression expression, Operation[] operations)
: base(location, null) {
_expression = expression;
Path = path;
_operations = operations;
+ _objectTree = objectTree;
if (_operations.Length == 0) {
throw new InvalidOperationException("deref expression has no operations");
}
NestedPath = _operations[^1].Path;
- ValType = DetermineValType(objectTree);
}
private DMComplexValueType DetermineValType(DMObjectTree objectTree) {
var type = _expression.ValType;
+ if (type.IsAnything && _expression.Path is not null) type = new DMComplexValueType(DMValueType.Instance, _expression.Path);
var i = 0;
while (!type.IsAnything && i < _operations.Length) {
var operation = _operations[i++];
- if (type.TypePath is null || !objectTree.TryGetDMObject(type.TypePath.Value, out var dmObject)) {
+ var typePath = type.TypePath ?? type.AsPath();
+ if (typePath is null || !objectTree.TryGetDMObject(typePath.Value, out var dmObject)) {
// We're dereferencing something without a type-path, this could be anything
type = DMValueType.Anything;
break;
@@ -81,8 +105,8 @@ private DMComplexValueType DetermineValType(DMObjectTree objectTree) {
type = operation switch {
FieldOperation fieldOperation => dmObject.GetVariable(fieldOperation.Identifier)?.ValType ?? DMValueType.Anything,
- IndexOperation => DMValueType.Anything, // Lists currently can't be typed, this could be anything
- CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier) ?? DMValueType.Anything,
+ IndexOperation indexOperation => indexOperation.UnnestValType(type.ListValueTypes),
+ CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier, callOperation.Parameters) ?? DMValueType.Anything,
_ => throw new InvalidOperationException("Unimplemented dereference operation")
};
}
@@ -130,7 +154,15 @@ private void EmitOperation(ExpressionContext ctx, Operation operation, string en
break;
case CallOperation callOperation:
- var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(ctx, null);
+ DMProc? targetProc = null;
+ if (callOperation.Path is not null && ctx.ObjectTree.TryGetDMObject(callOperation.Path.Value, out var dmObj)) {
+ var procs = dmObj?.GetProcs(callOperation.Identifier);
+ if (procs is not null && procs.Count > 0) {
+ targetProc = ctx.ObjectTree.AllProcs[procs[0]];
+ }
+ }
+
+ var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(ctx, targetProc);
ctx.Proc.DereferenceCall(callOperation.Identifier, argumentsType, argumentStackSize);
break;
@@ -320,8 +352,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out
// expression::identifier
// Same as initial(expression?.identifier) except this keeps its type
-internal sealed class ScopeReference(DMObjectTree objectTree, Location location, DMExpression expression, string identifier, DMVariable dmVar)
- : Initial(location, new Dereference(objectTree, location, dmVar.Type, expression, // Just a little hacky
+internal sealed class ScopeReference(DMCompiler compiler, Location location, DMExpression expression, string identifier, DMVariable dmVar)
+ : Initial(location, new Dereference(compiler.DMObjectTree, location, dmVar.Type, expression, // Just a little hacky
[
new Dereference.FieldOperation {
Identifier = identifier,
@@ -332,6 +364,13 @@ internal sealed class ScopeReference(DMObjectTree objectTree, Location location,
) {
public override DreamPath? Path => Expression.Path;
+ public override DMComplexValueType ValType {
+ get {
+ TryAsConstant(compiler, out var constant);
+ return constant?.ValType ?? dmVar.ValType;
+ }
+ }
+
public override string GetNameof(ExpressionContext ctx) => dmVar.Name;
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs
index 79a967aa6b..19be7f49ed 100644
--- a/DMCompiler/DM/Expressions/LValue.cs
+++ b/DMCompiler/DM/Expressions/LValue.cs
@@ -110,6 +110,8 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel
// world
internal sealed class World(Location location) : LValue(location, DreamPath.World) {
+ public override DMComplexValueType ValType => new DMComplexValueType(DMValueType.Instance, DreamPath.World);
+
public override DMReference EmitReference(ExpressionContext ctx, string endLabel,
ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) {
return DMReference.World;
@@ -119,11 +121,15 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel
}
// Identifier of local variable
-internal sealed class Local(Location location, DMProc.LocalVariable localVar) : LValue(location, localVar.Type) {
+internal sealed class Local(Location location, DMProc.LocalVariable localVar, DMComplexValueType? valType) : LValue(location, localVar.Type) {
public DMProc.LocalVariable LocalVar { get; } = localVar;
- // TODO: non-const local var static typing
- public override DMComplexValueType ValType => LocalVar.ExplicitValueType ?? DMValueType.Anything;
+ public override DMComplexValueType ValType {
+ get {
+ if (valType is not null && !valType.Value.IsAnything) return valType.Value;
+ return LocalVar.ExplicitValueType ?? (LocalVar.Type is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Path | DMValueType.Null, LocalVar.Type) : DMValueType.Anything);
+ }
+ }
public override DMReference EmitReference(ExpressionContext ctx, string endLabel,
ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) {
diff --git a/DMCompiler/DM/Expressions/Procs.cs b/DMCompiler/DM/Expressions/Procs.cs
index d33663790e..2500111102 100644
--- a/DMCompiler/DM/Expressions/Procs.cs
+++ b/DMCompiler/DM/Expressions/Procs.cs
@@ -5,7 +5,11 @@
namespace DMCompiler.DM.Expressions;
// x() (only the identifier)
-internal sealed class Proc(Location location, string identifier) : DMExpression(location) {
+internal sealed class Proc(Location location, string identifier, DMObject theObject) : DMExpression(location) {
+ public DMObject DMObject => theObject;
+ public string Identifier => identifier;
+ public override DMComplexValueType ValType => GetReturnType(DMObject);
+
public override void EmitPushValue(ExpressionContext ctx) {
ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to use proc as value");
ctx.Proc.Error();
@@ -57,8 +61,8 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel
/// .
/// This is an LValue _and_ a proc!
///
-internal sealed class ProcSelf(Location location, DMComplexValueType valType) : LValue(location, null) {
- public override DMComplexValueType ValType => valType;
+internal sealed class ProcSelf(Location location, DMComplexValueType? valType) : LValue(location, null) {
+ public override DMComplexValueType ValType => valType ?? DMValueType.Anything;
public override DMReference EmitReference(ExpressionContext ctx, string endLabel,
ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) {
@@ -88,10 +92,26 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel
}
// x(y, z, ...)
-internal sealed class ProcCall(Location location, DMExpression target, ArgumentList arguments, DMComplexValueType valType)
+internal sealed class ProcCall(DMCompiler compiler, Location location, DMExpression target, ArgumentList arguments, DMComplexValueType valType)
: DMExpression(location) {
public override bool PathIsFuzzy => Path == null;
- public override DMComplexValueType ValType => valType.IsAnything ? target.ValType : valType;
+
+ public override DMComplexValueType ValType {
+ get {
+ if (!valType.IsAnything)
+ return valType;
+ switch (target) {
+ case Proc procTarget:
+ return procTarget.DMObject.GetProcReturnTypes(procTarget.Identifier, arguments) ?? DMValueType.Anything;
+ case GlobalProc procTarget:
+ if(compiler.DMObjectTree.TryGetGlobalProc(procTarget.Proc.Name, out var globalProc))
+ return globalProc.ReturnTypes;
+ return DMValueType.Anything;
+ }
+
+ return target.ValType;
+ }
+ }
public (DMObject? ProcOwner, DMProc? Proc) GetTargetProc(DMCompiler compiler, DMObject dmObject) {
return target switch {
diff --git a/DMCompiler/DM/Expressions/Ternary.cs b/DMCompiler/DM/Expressions/Ternary.cs
index 8c5ba9afba..790f310432 100644
--- a/DMCompiler/DM/Expressions/Ternary.cs
+++ b/DMCompiler/DM/Expressions/Ternary.cs
@@ -3,10 +3,10 @@
namespace DMCompiler.DM.Expressions;
// x ? y : z
-internal sealed class Ternary(Location location, DMExpression a, DMExpression b, DMExpression c)
+internal sealed class Ternary(DMCompiler compiler, Location location, DMExpression a, DMExpression b, DMExpression c)
: DMExpression(location) {
public override bool PathIsFuzzy => true;
- public override DMComplexValueType ValType { get; } = new(b.ValType.Type | c.ValType.Type, b.ValType.TypePath ?? c.ValType.TypePath);
+ public override DMComplexValueType ValType { get; } = (b.ValType.IsAnything || c.ValType.IsAnything) ? DMValueType.Anything : DMComplexValueType.MergeComplexValueTypes(compiler, b.ValType, c.ValType);
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (!a.TryAsConstant(compiler, out var constant1)) {
diff --git a/DMCompiler/DM/Expressions/Unary.cs b/DMCompiler/DM/Expressions/Unary.cs
index 4de2a46821..928df5b240 100644
--- a/DMCompiler/DM/Expressions/Unary.cs
+++ b/DMCompiler/DM/Expressions/Unary.cs
@@ -4,11 +4,13 @@
namespace DMCompiler.DM.Expressions;
internal abstract class UnaryOp(Location location, DMExpression expr) : DMExpression(location) {
- protected DMExpression Expr { get; } = expr;
+ public DMExpression Expr { get; } = expr;
}
// -x
internal sealed class Negate(Location location, DMExpression expr) : UnaryOp(location, expr) {
+ public override DMComplexValueType ValType => Expr.ValType; // could be a num, could be a matrix, you never know
+
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number number)
return false;
@@ -30,6 +32,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// !x
internal sealed class Not(Location location, DMExpression expr) : UnaryOp(location, expr) {
+ public override DMComplexValueType ValType => DMValueType.Num; // todo: could eventually be updated to be a bool
+
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (!Expr.TryAsConstant(compiler, out constant)) return false;
@@ -50,6 +54,8 @@ public override void EmitPushValue(ExpressionContext ctx) {
// ~x
internal sealed class BinaryNot(Location location, DMExpression expr) : UnaryOp(location, expr) {
+ public override DMComplexValueType ValType => DMValueType.Num;
+
public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) {
if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number constantNum)
return false;
diff --git a/DMCompiler/DMStandard/Types/Atoms/Mob.dm b/DMCompiler/DMStandard/Types/Atoms/Mob.dm
index d8550a7728..6433e62340 100644
--- a/DMCompiler/DMStandard/Types/Atoms/Mob.dm
+++ b/DMCompiler/DMStandard/Types/Atoms/Mob.dm
@@ -1,16 +1,16 @@
/mob
parent_type = /atom/movable
- var/client/client
+ var/client/client as /client|null
var/key as text|null
var/tmp/ckey as text|null
var/tmp/list/group as opendream_unimplemented
- var/see_invisible = 0
- var/see_infrared = 0 as opendream_unimplemented
- var/sight = 0
- var/see_in_dark = 2 as opendream_unimplemented
+ var/see_invisible = 0 as num
+ var/see_infrared = 0 as num|opendream_unimplemented
+ var/sight = 0 as num
+ var/see_in_dark = 2 as num|opendream_unimplemented
density = TRUE
layer = MOB_LAYER
diff --git a/DMCompiler/DMStandard/Types/Atoms/Movable.dm b/DMCompiler/DMStandard/Types/Atoms/Movable.dm
index ff0a4b589e..4bcba702aa 100644
--- a/DMCompiler/DMStandard/Types/Atoms/Movable.dm
+++ b/DMCompiler/DMStandard/Types/Atoms/Movable.dm
@@ -1,9 +1,9 @@
/atom/movable
- var/screen_loc
+ var/screen_loc as text|null
var/animate_movement = FORWARD_STEPS as opendream_unimplemented
var/list/locs = null as opendream_unimplemented
- var/glide_size = 0
+ var/glide_size = 0 as num
var/step_size as opendream_unimplemented
var/tmp/bound_x as opendream_unimplemented
var/tmp/bound_y as opendream_unimplemented
@@ -17,7 +17,7 @@
proc/Bump(atom/Obstacle)
- proc/Move(atom/NewLoc, Dir=0) as num
+ proc/Move(atom/NewLoc, Dir=0 as num) as num|null // NOTE: BYOND doesn't return null, but it's common for SS13 proc overrides to
if (isnull(NewLoc) || loc == NewLoc)
return FALSE
@@ -57,5 +57,4 @@
newarea.Entered(src, oldloc)
return TRUE
- else
- return FALSE
+ return FALSE
diff --git a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm
index 2168de66b1..313f9bf2a4 100644
--- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm
+++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm
@@ -1,10 +1,10 @@
/atom
parent_type = /datum
- var/name = null
- var/text = null
- var/desc = null
- var/suffix = null as opendream_unimplemented
+ var/name = null as text|null
+ var/text = null as text|null
+ var/desc = null as text|null
+ var/suffix = null as text|null|opendream_unimplemented
// The initialization/usage of these lists is handled internally by the runtime
var/tmp/list/verbs = null
@@ -14,35 +14,38 @@
var/tmp/list/vis_locs = null as opendream_unimplemented
var/list/vis_contents = null
- var/tmp/atom/loc
- var/dir = SOUTH
- var/tmp/x = 0
- var/tmp/y = 0
- var/tmp/z = 0
- var/pixel_x = 0
- var/pixel_y = 0
- var/pixel_z = 0
- var/pixel_w = 0
+ var/tmp/atom/loc as /atom|null
+ var/dir = SOUTH as num
+ var/tmp/x = 0 as num
+ var/tmp/y = 0 as num
+ var/tmp/z = 0 as num
+ var/pixel_x = 0 as num
+ var/pixel_y = 0 as num
+ var/pixel_z = 0 as num
+ var/pixel_w = 0 as num
- var/icon_w = 0 as opendream_unimplemented
- var/icon_z = 0 as opendream_unimplemented
-
- var/icon = null
- var/icon_state = ""
- var/layer = 2.0
- var/plane = 0
- var/alpha = 255
- var/color = "#FFFFFF"
- var/invisibility = 0
- var/mouse_opacity = 1
- var/infra_luminosity = 0 as opendream_unimplemented
- var/luminosity = 0 as opendream_unimplemented
- var/opacity = 0
+ var/icon_w = 0 as num|opendream_unimplemented
+ var/icon_z = 0 as num|opendream_unimplemented
+
+ var/icon = null as icon|null
+ var/icon_state = "" as text|null
+ var/layer = 2.0 as num
+ var/plane = 0 as num
+ var/alpha = 255 as num
+ // currently we coerce text (hex string) and list(num) (color matrix) to the color type
+ // in the future this should probably be colorstring|colormatrix|null or something
+ // or 'as color' could be a shorthand for that
+ var/color = "#FFFFFF" as color|null
+ var/invisibility = 0 as num
+ var/mouse_opacity = 1 as num
+ var/infra_luminosity = 0 as num|opendream_unimplemented
+ var/luminosity = 0 as num|opendream_unimplemented
+ var/opacity = 0 as num
var/matrix/transform
- var/blend_mode = 0
+ var/blend_mode = 0 as num
var/gender = NEUTER
- var/density = FALSE
+ var/density = FALSE as num
var/maptext = null
@@ -104,4 +107,4 @@
proc/Stat()
New(loc)
- ..()
+ ..()
\ No newline at end of file
diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm
index c875e82f23..35a7220c00 100644
--- a/DMCompiler/DMStandard/Types/Client.dm
+++ b/DMCompiler/DMStandard/Types/Client.dm
@@ -11,7 +11,7 @@
var/tag
var/const/type = /client
- var/mob/mob // TODO: as /mob|null
+ var/mob/mob as /mob|null
var/atom/eye
var/lazy_eye = 0 as opendream_unimplemented
var/perspective = MOB_PERSPECTIVE
@@ -56,8 +56,7 @@
proc/New(TopicData)
// Search every mob for one with our ckey
- // TODO: This /mob|mob thing is kinda silly huh?
- for (var/mob/M as /mob|mob in world)
+ for (var/mob/M in world)
if (M.key == key)
mob = M
break
@@ -96,7 +95,7 @@
proc/MeasureText(text, style, width=0)
set opendream_unimplemented = TRUE
- proc/Move(loc, dir)
+ proc/Move(loc, dir as num)
mob.Move(loc, dir)
verb/North()
diff --git a/DMCompiler/DMStandard/Types/Datum.dm b/DMCompiler/DMStandard/Types/Datum.dm
index c14bfb2a23..96e33e8744 100644
--- a/DMCompiler/DMStandard/Types/Datum.dm
+++ b/DMCompiler/DMStandard/Types/Datum.dm
@@ -1,5 +1,5 @@
/datum
- var/tmp/type as opendream_compiletimereadonly
+ var/tmp/type as path(/datum)|opendream_compiletimereadonly
var/tmp/parent_type
var/tmp/list/vars as opendream_compiletimereadonly
diff --git a/DMCompiler/DMStandard/Types/Icon.dm b/DMCompiler/DMStandard/Types/Icon.dm
index bd211b7de7..806ca704c6 100644
--- a/DMCompiler/DMStandard/Types/Icon.dm
+++ b/DMCompiler/DMStandard/Types/Icon.dm
@@ -44,5 +44,5 @@
proc/Width()
-proc/icon(icon, icon_state, dir, frame, moving)
+proc/icon(icon, icon_state, dir, frame, moving) as icon
return new /icon(icon, icon_state, dir, frame, moving)
diff --git a/DMCompiler/DMStandard/Types/List.dm b/DMCompiler/DMStandard/Types/List.dm
index 45fa285f8c..8f209a2b3c 100644
--- a/DMCompiler/DMStandard/Types/List.dm
+++ b/DMCompiler/DMStandard/Types/List.dm
@@ -6,13 +6,13 @@
proc/New(Size)
- proc/Add(Item1)
- proc/Copy(Start = 1, End = 0)
- proc/Cut(Start = 1, End = 0)
- proc/Find(Elem, Start = 1, End = 0)
- proc/Insert(Index, Item1)
+ proc/Add(Item1) as num
+ proc/Copy(Start = 1, End = 0) as /list
+ proc/Cut(Start = 1, End = 0) as num
+ proc/Find(Elem, Start = 1, End = 0) as num
+ proc/Insert(Index, Item1) as num
proc/Join(Glue as text|null, Start = 1 as num, End = 0 as num) as text
proc/Remove(Item1)
proc/RemoveAll(Item1)
- proc/Swap(Index1, Index2)
+ proc/Swap(Index1, Index2) as null
proc/Splice(Start = 1 as num, End = 0 as num, Item1, ...) as null
diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm
index 2d0d132b44..6b56ff332c 100644
--- a/DMCompiler/DMStandard/Types/World.dm
+++ b/DMCompiler/DMStandard/Types/World.dm
@@ -4,20 +4,20 @@
var/log = null
- var/area = /area as /area
- var/turf = /turf as /turf
- var/mob = /mob as /mob
+ var/area = /area as path(/area)
+ var/turf = /turf as path(/turf)
+ var/mob = /mob as path(/mob)
var/name = "OpenDream World"
- var/time
- var/timezone = 0
- var/timeofday
- var/realtime
- var/tick_lag = 1
- var/cpu = 0 as opendream_unimplemented
- var/fps = 10
+ var/time as num
+ var/timezone = 0 as num
+ var/timeofday as num
+ var/realtime as num
+ var/tick_lag = 1 as num
+ var/cpu = 0 as opendream_unimplemented|num
+ var/fps = 10 as num
var/tick_usage
- var/loop_checks = 0 as opendream_unimplemented
+ var/loop_checks = 0 as opendream_unimplemented|num
var/maxx = null as num|null
var/maxy = null as num|null
@@ -32,7 +32,7 @@
var/version = 0 as num|opendream_unsupported //only used to notify users on the hub - unsupported due to no hub
var/address
- var/port = 0 as opendream_compiletimereadonly
+ var/port = 0 as opendream_compiletimereadonly|num
var/internet_address = "127.0.0.1" as opendream_unimplemented
var/url as opendream_unimplemented
var/visibility = 0 as num|opendream_unimplemented //used to control server appearing on the hub - this will have to use ServerStatusCode.Offline/Online
@@ -40,7 +40,7 @@
var/process
var/list/params = null
- var/sleep_offline = 0
+ var/sleep_offline = 0 as num
var/const/system_type as opendream_noconstfold
@@ -118,4 +118,4 @@
proc/ODHotReloadInterface()
- proc/ODHotReloadResource(var/file_name)
+ proc/ODHotReloadResource(var/file_name)
\ No newline at end of file
diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm
index 3657df57ce..a6c7091d00 100644
--- a/DMCompiler/DMStandard/_Standard.dm
+++ b/DMCompiler/DMStandard/_Standard.dm
@@ -1,17 +1,17 @@
//These procs should be in alphabetical order, as in DreamProcNativeRoot.cs
proc/alert(Usr = usr, Message, Title, Button1 = "Ok", Button2, Button3) as text
proc/animate(Object, time, loop, easing, flags, delay, pixel_x, pixel_y, pixel_z, maptext, maptext_width, maptext_height, maptext_x, maptext_y, dir, alpha, transform, color, luminosity, infra_luminosity, layer, glide_size, icon, icon_state, invisibility, suffix) as null
-proc/ascii2text(N) as text
-proc/block(atom/Start, atom/End, StartZ, EndX=Start, EndY=End, EndZ=StartZ) as /list
+proc/ascii2text(N as num|null) as text
+proc/block(atom/Start, atom/End, StartZ, EndX=Start, EndY=End, EndZ=StartZ) as /list(/turf)
proc/bounds_dist(atom/Ref, atom/Target) as num
-proc/ceil(A) as num
-proc/ckey(Key) as text|null
+proc/ceil(A as num|null) as num
+proc/ckey(Key as text|null) as text|null
proc/ckeyEx(Text) as text|null
proc/clamp(Value, Low, High) as /list|num|null
proc/cmptext(T1) as num
proc/cmptextEx(T1) as num
-proc/copytext(T, Start = 1, End = 0) as text|null
-proc/copytext_char(T,Start=1,End=0) as text|null
+proc/copytext(T as text|null, Start = 1, End = 0) as text|null
+proc/copytext_char(T as text|null,Start=1,End=0) as text|null
proc/CRASH(msg) as null
proc/fcopy(Src, Dst) as num
proc/fcopy_rsc(File) as num|null
@@ -20,10 +20,10 @@ proc/fexists(File) as num
proc/file(Path)
proc/file2text(File) as text|null
proc/filter(type, ...)
-proc/findtext(Haystack, Needle, Start = 1, End = 0) as num
-proc/findtextEx(Haystack, Needle, Start = 1, End = 0) as num
-proc/findlasttext(Haystack, Needle, Start = 0, End = 1) as num
-proc/findlasttextEx(Haystack, Needle, Start = 0, End = 1) as num
+proc/findtext(Haystack as text|null, Needle as text|/regex|null, Start = 1, End = 0) as num
+proc/findtextEx(Haystack as text|null, Needle as text|/regex|null, Start = 1, End = 0) as num
+proc/findlasttext(Haystack as text|null, Needle as text|/regex|null, Start = 0, End = 1) as num
+proc/findlasttextEx(Haystack as text|null, Needle as text|/regex|null, Start = 0, End = 1) as num
proc/flick(Icon, Object)
proc/flist(Path) as /list
proc/floor(A) as num
@@ -31,14 +31,14 @@ proc/fract(n) as num
proc/ftime(File, IsCreationTime = 0) as num
proc/generator(type, A, B, rand) as /generator
proc/get_step_to(Ref, Trg, Min=0) as num
-proc/get_steps_to(Ref, Trg, Min=0) as /list
+proc/get_steps_to(Ref, Trg, Min=0) as num
proc/gradient(A, index)
proc/hascall(Object, ProcName) as num
proc/hearers(Depth = world.view, Center = usr) as /list
proc/html_decode(HtmlText) as text
proc/html_encode(PlainText) as text
proc/icon_states(Icon, mode = 0) as text|null
-proc/image(icon, loc, icon_state, layer, dir, pixel_x, pixel_y)
+proc/image(icon, loc, icon_state, layer, dir, pixel_x, pixel_y) as /image
proc/isarea(Loc1) as num
proc/isfile(File) as num
proc/isicon(Icon) as num
@@ -59,7 +59,7 @@ proc/json_encode(Value, flags)
proc/length_char(E) as num
proc/lerp(A, B, factor)
proc/list2params(List) as text
-proc/lowertext(T as text) as text
+proc/lowertext(T as text|null) as text
proc/max(A) as num|text|null
proc/md5(T) as text|null
proc/min(A) as num|text|null
@@ -67,26 +67,26 @@ proc/noise_hash(...) as num
set opendream_unimplemented = 1
return 0.5
proc/nonspantext(Haystack, Needles, Start = 1) as num
-proc/num2text(N, A, B) as text
+proc/num2text(N as num|null, A, B) as text
proc/orange(Dist = 5, Center = usr) as /list|null // NOTE: Not sure if return types have BYOND parity
proc/oview(Dist = 5, Center = usr) as /list
proc/oviewers(Depth = 5, Center = usr) as /list
proc/ohearers(Depth = world.view, Center = usr) as /list
-proc/params2list(Params) as /list
-proc/rand(L, H) as num
+proc/params2list(Params) as /list(text)
+proc/rand(L as num, H as num) as num
proc/rand_seed(Seed) as null
proc/range(Dist, Center) as /list|null // NOTE: Not sure if return types have BYOND parity
proc/ref(Object) as text
-proc/replacetext(Haystack, Needle, Replacement, Start = 1, End = 0) as text|null
-proc/replacetextEx(Haystack, Needle, Replacement, Start = 1, End = 0) as text|null
-proc/rgb(R, G, B, A, space) as text|null
+proc/replacetext(Haystack as text|null, Needle as text|/regex|null, Replacement, Start = 1, End = 0) as text|null
+proc/replacetextEx(Haystack as text|null, Needle as text|/regex|null, Replacement, Start = 1, End = 0) as text|null
+proc/rgb(R, G, B, A, space) as color|null
proc/rgb2num(color, space = COLORSPACE_RGB) as /list
-proc/roll(ndice = 1, sides) as num
-proc/round(A, B) as num
+proc/roll(ndice = 1 as num|text, sides as num|null) as num
+proc/round(A as num|null, B as num|null) as num
proc/sha1(input) as text|null
proc/shutdown(Addr,Natural = 0)
proc/sign(A) as num
-proc/sleep(Delay)
+proc/sleep(Delay) as num|null
proc/sorttext(T1, T2) as num
proc/sorttextEx(T1, T2) as num
proc/sound(file, repeat = 0, wait, channel, volume)
@@ -94,19 +94,19 @@ proc/spantext(Haystack,Needles,Start=1) as num
proc/spantext_char(Haystack,Needles,Start=1) as num
proc/splicetext(Text, Start = 1, End = 0, Insert = "") as text|null
proc/splicetext_char(Text, Start = 1, End = 0, Insert = "") as text|null
-proc/splittext(Text, Delimiter) as /list
+proc/splittext(Text, Delimiter) as /list(text)
proc/stat(Name, Value)
proc/statpanel(Panel, Name, Value)
-proc/text2ascii(T, pos = 1) as text
-proc/text2ascii_char(T, pos = 1) as text
+proc/text2ascii(T as text|null, pos = 1) as num
+proc/text2ascii_char(T as text|null, pos = 1) as num
proc/text2file(Text, File)
proc/text2num(T, radix = 10) as num|null
-proc/text2path(T)
+proc/text2path(T as text|null) as null|path(/datum)|path(/world) // todo: allow path(/)
proc/time2text(timestamp, format) as text
proc/trimtext(Text) as text|null
-proc/trunc(n) as num
-proc/turn(Dir, Angle)
-proc/typesof(Item1) as /list
+proc/trunc(n as num|null) as num
+proc/turn(Dir as null|num|/matrix|icon, Angle as num) as null|num|/matrix|icon
+proc/typesof(Item1 as text|path(/datum)|path(/proc)|/list(text|path(/datum)|path(/proc))) as /list(path(/datum)|path(/proc))
proc/uppertext(T as text) as text
proc/url_decode(UrlText) as text
proc/url_encode(PlainText, format = 0) as text
@@ -156,27 +156,27 @@ proc/winset(player, control_id, params)
#include "Types\Atoms\Turf.dm"
#include "UnsortedAdditions.dm"
-proc/replacetextEx_char(Haystack as text, Needle, Replacement, Start = 1, End = 0) as text
+proc/replacetextEx_char(Haystack as text, Needle, Replacement, Start = 1 as num, End = 0 as num) as text
set opendream_unimplemented = TRUE
return Haystack
-/proc/step(atom/movable/Ref as /atom/movable, var/Dir, var/Speed=0) as num
+/proc/step(atom/movable/Ref, var/Dir as num|null, var/Speed=0 as num) as num
//TODO: Speed = step_size if Speed is 0
return Ref.Move(get_step(Ref, Dir), Dir)
-/proc/step_away(atom/movable/Ref as /atom/movable, /atom/Trg, Max=5, Speed=0) as num
+/proc/step_away(atom/movable/Ref, /atom/Trg, Max=5 as num, Speed=0 as num) as num
return Ref.Move(get_step_away(Ref, Trg, Max), turn(get_dir(Ref, Trg), 180))
-/proc/step_to(atom/movable/Ref, atom/Trg, Min = 0, Speed = 0) as num
+/proc/step_to(atom/movable/Ref, atom/Trg, Min = 0 as num, Speed = 0 as num) as num
//TODO: Consider obstacles
var/dist = get_dist(Ref, Trg)
- if (dist <= Min) return
+ if (dist <= Min) return 0
- var/step_dir = get_dir(Ref, Trg)
+ var/step_dir = get_dir(Ref, Trg) as num
return step(Ref, step_dir, Speed)
-/proc/walk_away(Ref,Trg,Max=5,Lag=0,Speed=0)
+/proc/walk_away(Ref,Trg,Max=5 as num,Lag=0 as num,Speed=0 as num)
set opendream_unimplemented = TRUE
CRASH("/walk_away() is not implemented")
@@ -188,12 +188,12 @@ proc/get_dist(atom/Loc1, atom/Loc2) as num
var/distY = Loc2.y - Loc1.y
return round(sqrt(distX ** 2 + distY ** 2))
-proc/get_step_towards(atom/movable/Ref, /atom/Trg)
+proc/get_step_towards(atom/movable/Ref, atom/Trg)
var/dir = get_dir(Ref, Trg)
return get_step(Ref, dir)
-proc/get_step_away(atom/movable/Ref, /atom/Trg, Max = 5)
+proc/get_step_away(atom/movable/Ref, atom/Trg, Max = 5 as num)
var/dir = turn(get_dir(Ref, Trg), 180)
return get_step(Ref, dir)
@@ -204,14 +204,14 @@ proc/get_step_rand(atom/movable/Ref)
return get_step(Ref, dir)
-proc/step_towards(atom/movable/Ref as /atom/movable, /atom/Trg, Speed) as num
+proc/step_towards(atom/movable/Ref, /atom/Trg, Speed) as num
return Ref.Move(get_step_towards(Ref, Trg), get_dir(Ref, Trg))
proc/step_rand(atom/movable/Ref, Speed=0)
var/target = get_step_rand(Ref)
return Ref.Move(target, get_dir(Ref, target))
-proc/jointext(list/List as /list|text, Glue as text|null, Start = 1 as num, End = 0 as num) as text
+proc/jointext(list/List as /list|text, Glue as text|null, Start = 1 as num, End = 0 as num) as text|null
if(islist(List))
return List.Join(Glue, Start, End)
if(istext(List))
diff --git a/DMCompiler/DreamPath.cs b/DMCompiler/DreamPath.cs
index 71fdafca8b..3eddcefbcc 100644
--- a/DMCompiler/DreamPath.cs
+++ b/DMCompiler/DreamPath.cs
@@ -108,6 +108,14 @@ internal DMValueType GetAtomType(DMCompiler compiler) {
return DMValueType.Turf;
if (dmType.IsSubtypeOf(Area))
return DMValueType.Area;
+ if (dmType.IsSubtypeOf(Movable))
+ return DMValueType.Mob | DMValueType.Obj;
+ if (dmType.IsSubtypeOf(Atom))
+ return DMValueType.Area | DMValueType.Turf | DMValueType.Obj | DMValueType.Mob;
+ if (dmType.IsSubtypeOf(Icon))
+ return DMValueType.Icon;
+ if (dmType.IsSubtypeOf(Sound))
+ return DMValueType.Sound;
return DMValueType.Anything;
}
@@ -258,4 +266,12 @@ private void Normalize(bool canHaveEmptyEntries) {
Elements = _elements[..writeIdx];
}
+
+ public DreamPath GetLastCommonAncestor(DMCompiler compiler, DreamPath other) {
+ var thisObject = compiler.DMObjectTree.GetOrCreateDMObject(this);
+ var otherObject = compiler.DMObjectTree.GetOrCreateDMObject(other);
+ if (thisObject is null || otherObject is null)
+ return Root;
+ return thisObject.GetLastCommonAncestor(otherObject);
+ }
}
diff --git a/DMCompiler/Location.cs b/DMCompiler/Location.cs
index 4fab0e9378..d7dd4866ac 100644
--- a/DMCompiler/Location.cs
+++ b/DMCompiler/Location.cs
@@ -1,3 +1,4 @@
+using System.IO;
using System.Text;
namespace DMCompiler;
@@ -19,7 +20,7 @@ public readonly struct Location(string filePath, int? line, int? column, bool in
public bool InDMStandard { get; } = inDMStandard;
public override string ToString() {
- var builder = new StringBuilder(SourceFile ?? "");
+ var builder = new StringBuilder((SourceFile is null) ? "" : Path.GetRelativePath(".", SourceFile));
if (Line is not null) {
builder.Append(":" + Line);