diff --git a/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm b/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm
index 46c609064c..38f340fd44 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
\ No newline at end of file
diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
index c025bcf7e6..73c8252747 100644
--- a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
+++ b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
@@ -61,7 +61,7 @@ public sealed class DMASTObjectVarDefinition(
Location location,
DreamPath path,
DMASTExpression value,
- DMComplexValueType valType,
+ DMComplexValueType? valType,
DreamPath? valPath = null) : DMASTStatement(location) {
/// The path of the object that we are a property of.
public DreamPath ObjectPath => _varDecl.ObjectPath;
@@ -80,7 +80,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..3333cfca94 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 ec01b396ae..567ac27e1c 100644
--- a/DMCompiler/Compiler/DM/DMParser.cs
+++ b/DMCompiler/Compiler/DM/DMParser.cs
@@ -242,9 +242,9 @@ public DMASTFile File() {
Whitespace();
// Proc return type
- var types = AsComplexTypes();
+ var types = AsComplexTypes(parameters);
- DMASTProcBlockInner? procBlock = ProcBlock();
+ DMASTProcBlockInner? procBlock = ProcBlock(parameters);
if (procBlock is null) {
DMASTProcStatement? procStatement = ProcStatement();
@@ -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);
varDefinitions.Add(varDef);
@@ -571,12 +571,12 @@ public DMASTFile File() {
return null;
}
- private DMASTProcBlockInner? ProcBlock() {
+ private DMASTProcBlockInner? ProcBlock(List? procParameters = null) {
Token beforeBlockToken = Current();
bool hasNewline = Newline();
- DMASTProcBlockInner? procBlock = BracedProcBlock();
- procBlock ??= IndentedProcBlock();
+ DMASTProcBlockInner? procBlock = BracedProcBlock(procParameters);
+ procBlock ??= IndentedProcBlock(procParameters);
if (procBlock == null && hasNewline) {
ReuseToken(beforeBlockToken);
@@ -585,7 +585,7 @@ public DMASTFile File() {
return procBlock;
}
- private DMASTProcBlockInner? BracedProcBlock() {
+ private DMASTProcBlockInner? BracedProcBlock(List? procParameters = null) {
var loc = Current().Location;
if (Check(TokenType.DM_LeftCurlyBracket)) {
DMASTProcBlockInner? block;
@@ -593,7 +593,7 @@ public DMASTFile File() {
Whitespace();
Newline();
if (Current().Type == TokenType.DM_Indent) {
- block = IndentedProcBlock();
+ block = IndentedProcBlock(procParameters);
Newline();
Consume(TokenType.DM_RightCurlyBracket, "Expected '}'");
} else {
@@ -625,14 +625,14 @@ public DMASTFile File() {
return null;
}
- private DMASTProcBlockInner? IndentedProcBlock() {
+ private DMASTProcBlockInner? IndentedProcBlock(List? procParameters = null) {
var loc = Current().Location;
if (Check(TokenType.DM_Indent)) {
List statements = new();
List setStatements = new(); // set statements are weird and must be held separately.
do {
- (List? statements, List? setStatements) blockInner = ProcBlockInner();
+ (List? statements, List? setStatements) blockInner = ProcBlockInner(procParameters);
if (blockInner.statements is not null)
statements.AddRange(blockInner.statements);
if (blockInner.setStatements is not null)
@@ -653,14 +653,14 @@ public DMASTFile File() {
return null;
}
- private (List?, List?) ProcBlockInner() {
+ private (List?, List?) ProcBlockInner(List? procParameters = null) {
List procStatements = new();
List setStatements = new(); // We have to store them separately because they're evaluated first
DMASTProcStatement? statement;
do {
Whitespace();
- statement = ProcStatement();
+ statement = ProcStatement(procParameters);
if (statement is not null) {
Whitespace();
@@ -675,7 +675,7 @@ public DMASTFile File() {
return (procStatements.Count > 0 ? procStatements : null, setStatements.Count > 0 ? setStatements : null);
}
- private DMASTProcStatement? ProcStatement() {
+ private DMASTProcStatement? ProcStatement(List? procParameters = null) {
var loc = Current().Location;
if (Current().Type == TokenType.DM_Semicolon) { // A lone semicolon creates a "null statement" (like C)
@@ -790,21 +790,21 @@ public DMASTFile File() {
return new DMASTProcStatementExpression(loc, expression);
} else {
DMASTProcStatement? procStatement = Current().Type switch {
- TokenType.DM_If => If(),
+ TokenType.DM_If => If(procParameters),
TokenType.DM_Return => Return(),
- TokenType.DM_For => For(),
+ TokenType.DM_For => For(procParameters),
TokenType.DM_Set => Set(),
- TokenType.DM_Switch => Switch(),
+ TokenType.DM_Switch => Switch(procParameters),
TokenType.DM_Continue => Continue(),
TokenType.DM_Break => Break(),
- TokenType.DM_Spawn => Spawn(),
- TokenType.DM_While => While(),
- TokenType.DM_Do => DoWhile(),
+ TokenType.DM_Spawn => Spawn(procParameters),
+ TokenType.DM_While => While(procParameters),
+ TokenType.DM_Do => DoWhile(procParameters),
TokenType.DM_Throw => Throw(),
TokenType.DM_Del => Del(),
- TokenType.DM_Try => TryCatch(),
+ TokenType.DM_Try => TryCatch(procParameters),
TokenType.DM_Goto => Goto(),
- TokenType.DM_Slash or TokenType.DM_Var => ProcVarDeclaration(),
+ TokenType.DM_Slash or TokenType.DM_Var => ProcVarDeclaration(procParameters),
_ => null
};
@@ -816,7 +816,7 @@ public DMASTFile File() {
}
}
- private DMASTProcStatement? ProcVarDeclaration(bool allowMultiple = true) {
+ private DMASTProcStatement? ProcVarDeclaration(List? procParameters = null, bool allowMultiple = true) {
Token firstToken = Current();
bool wasSlash = Check(TokenType.DM_Slash);
@@ -827,7 +827,7 @@ public DMASTFile File() {
}
Whitespace(); // We have to consume whitespace here since "var foo = 1" (for example) is valid DM code.
- DMASTProcStatementVarDeclaration[]? vars = ProcVarEnd(allowMultiple);
+ DMASTProcStatementVarDeclaration[]? vars = ProcVarEnd(procParameters, allowMultiple);
if (vars == null) {
Emit(WarningCode.InvalidVarDefinition, "Expected a var declaration");
return new DMASTInvalidProcStatement(firstToken.Location);
@@ -846,7 +846,7 @@ public DMASTFile File() {
///
/// This proc calls itself recursively.
///
- private DMASTProcStatementVarDeclaration[]? ProcVarBlock(DMASTPath? varPath) {
+ private DMASTProcStatementVarDeclaration[]? ProcVarBlock(List? procParameters, DMASTPath? varPath) {
Token newlineToken = Current();
bool hasNewline = Newline();
@@ -854,7 +854,7 @@ public DMASTFile File() {
List varDeclarations = new();
while (!Check(TokenType.DM_Dedent)) {
- DMASTProcStatementVarDeclaration[]? varDecl = ProcVarEnd(true, path: varPath);
+ DMASTProcStatementVarDeclaration[]? varDecl = ProcVarEnd(procParameters, true, path: varPath);
if (varDecl != null) {
varDeclarations.AddRange(varDecl);
} else {
@@ -875,7 +875,7 @@ public DMASTFile File() {
List varDeclarations = new();
TokenType type = isIndented ? TokenType.DM_Dedent : TokenType.DM_RightCurlyBracket;
while (!Check(type)) {
- DMASTProcStatementVarDeclaration[]? varDecl = ProcVarEnd(true, path: varPath);
+ DMASTProcStatementVarDeclaration[]? varDecl = ProcVarEnd(procParameters, true, path: varPath);
Delimiter();
Whitespace();
if (varDecl == null) {
@@ -901,12 +901,12 @@ public DMASTFile File() {
return null;
}
- private DMASTProcStatementVarDeclaration[]? ProcVarEnd(bool allowMultiple, DMASTPath? path = null) {
+ private DMASTProcStatementVarDeclaration[]? ProcVarEnd(List? procParameters, bool allowMultiple, DMASTPath? path = null) {
var loc = Current().Location;
DMASTPath? varPath = Path();
if (allowMultiple) {
- DMASTProcStatementVarDeclaration[]? block = ProcVarBlock(varPath);
+ DMASTProcStatementVarDeclaration[]? block = ProcVarBlock(procParameters, varPath);
if (block != null) return block;
}
@@ -928,7 +928,11 @@ public DMASTFile File() {
RequireExpression(ref value);
}
- var valType = AsComplexTypes() ?? DMValueType.Anything;
+ var valType = AsComplexTypes(procParameters);
+ // 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)) {
@@ -1113,7 +1117,7 @@ private DMASTProcStatement Set() {
return sets[0];
}
- private DMASTProcStatementSpawn Spawn() {
+ private DMASTProcStatementSpawn Spawn(List? procParameters = null) {
var loc = Current().Location;
Advance();
@@ -1136,9 +1140,9 @@ private DMASTProcStatementSpawn Spawn() {
Newline();
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
if (statement != null) {
body = new DMASTProcBlockInner(loc, statement);
@@ -1151,7 +1155,7 @@ private DMASTProcStatementSpawn Spawn() {
return new DMASTProcStatementSpawn(loc, delay ?? new DMASTConstantInteger(loc, 0), body);
}
- private DMASTProcStatementIf If() {
+ private DMASTProcStatementIf If(List? procParameters = null) {
var loc = Current().Location;
Advance();
@@ -1173,7 +1177,7 @@ private DMASTProcStatementIf If() {
Whitespace();
- DMASTProcStatement? procStatement = ProcStatement();
+ DMASTProcStatement? procStatement = ProcStatement(procParameters);
DMASTProcBlockInner? elseBody = null;
DMASTProcBlockInner? body = (procStatement != null)
? new DMASTProcBlockInner(loc, procStatement)
@@ -1187,11 +1191,11 @@ private DMASTProcStatementIf If() {
Whitespace();
Check(TokenType.DM_Colon);
Whitespace();
- procStatement = ProcStatement();
+ procStatement = ProcStatement(procParameters);
elseBody = (procStatement != null)
? new DMASTProcBlockInner(loc, procStatement)
- : ProcBlock();
+ : ProcBlock(procParameters);
elseBody ??= new DMASTProcBlockInner(loc);
} else if (newLineAfterIf) {
ReuseToken(afterIfBody);
@@ -1200,7 +1204,7 @@ private DMASTProcStatementIf If() {
return new DMASTProcStatementIf(loc, condition, body, elseBody);
}
- private DMASTProcStatement For() {
+ private DMASTProcStatement For(List? procParameters = null) {
var loc = Current().Location;
Advance();
@@ -1213,7 +1217,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementInfLoop(loc, GetForBody(loc));
+ return new DMASTProcStatementInfLoop(loc, GetForBody(loc, procParameters));
}
_allowVarDeclExpression = true;
@@ -1237,7 +1241,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, new DMASTExpressionInRange(loc, assign.LHS, assign.RHS, endRange, step), null, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, new DMASTExpressionInRange(loc, assign.LHS, assign.RHS, endRange, step), null, null, dmTypes, GetForBody(loc, procParameters));
} else {
Emit(WarningCode.BadExpression, "Expected = before to in for");
return new DMASTInvalidProcStatement(loc);
@@ -1253,7 +1257,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, new DMASTExpressionIn(loc, expr1, listExpr), null, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, new DMASTExpressionIn(loc, expr1, listExpr), null, null, dmTypes, GetForBody(loc, procParameters));
}
if (!Check(ForSeparatorTypes)) {
@@ -1262,7 +1266,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc, procParameters));
}
if (Check(TokenType.DM_RightParenthesis)) {
@@ -1270,7 +1274,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc, procParameters));
}
Whitespace();
@@ -1289,7 +1293,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc, procParameters));
}
if (Check(TokenType.DM_RightParenthesis)) {
@@ -1297,7 +1301,7 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc, procParameters));
}
Whitespace();
@@ -1315,20 +1319,20 @@ private DMASTProcStatement For() {
Emit(WarningCode.ExtraToken, loc, "Extra token at end of proc statement");
}
- return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc));
+ return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc, procParameters));
- DMASTProcBlockInner GetForBody(Location forLocation) {
+ DMASTProcBlockInner GetForBody(Location forLocation, List? procParameters = null) {
Whitespace();
Newline();
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
var loc = Current().Location;
DMASTProcStatement? statement;
if (Check(TokenType.DM_Semicolon)) {
statement = new DMASTProcStatementExpression(loc, new DMASTConstantNull(loc));
} else {
- statement = ProcStatement();
+ statement = ProcStatement(procParameters);
if (statement == null) {
Compiler.Emit(WarningCode.MissingBody, forLocation, "Expected body or statement");
statement = new DMASTInvalidProcStatement(loc);
@@ -1342,7 +1346,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) {
}
}
- private DMASTProcStatement While() {
+ private DMASTProcStatement While(List? procParameters = null) {
var loc = Current().Location;
Advance();
@@ -1354,10 +1358,10 @@ private DMASTProcStatement While() {
ConsumeRightParenthesis();
Check(TokenType.DM_Semicolon);
Whitespace();
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
//Loops without a body are valid DM
statement ??= new DMASTProcStatementContinue(loc);
@@ -1372,15 +1376,15 @@ private DMASTProcStatement While() {
return new DMASTProcStatementWhile(loc, conditional, body);
}
- private DMASTProcStatementDoWhile DoWhile() {
+ private DMASTProcStatementDoWhile DoWhile(List? procParameters = null) {
var loc = Current().Location;
Advance();
Whitespace();
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
if (statement is null) { // This is consistently fatal in BYOND
Emit(WarningCode.MissingBody, "Expected statement - do-while requires a non-empty block");
//For the sake of argument, add a statement (avoids repetitive warning emissions down the line :^) )
@@ -1404,7 +1408,7 @@ private DMASTProcStatementDoWhile DoWhile() {
return new DMASTProcStatementDoWhile(loc, conditional, body);
}
- private DMASTProcStatementSwitch Switch() {
+ private DMASTProcStatementSwitch Switch(List? procParameters = null) {
var loc = Current().Location;
Advance();
@@ -1416,7 +1420,7 @@ private DMASTProcStatementSwitch Switch() {
ConsumeRightParenthesis();
Whitespace();
- DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases();
+ DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases(procParameters);
if (switchCases == null) {
switchCases = [];
Emit(WarningCode.MissingBody, "Expected switch cases");
@@ -1425,11 +1429,11 @@ private DMASTProcStatementSwitch Switch() {
return new DMASTProcStatementSwitch(loc, value, switchCases);
}
- private DMASTProcStatementSwitch.SwitchCase[]? SwitchCases() {
+ private DMASTProcStatementSwitch.SwitchCase[]? SwitchCases(List? procParameters = null) {
Token beforeSwitchBlock = Current();
bool hasNewline = Newline();
- DMASTProcStatementSwitch.SwitchCase[]? switchCases = BracedSwitchInner() ?? IndentedSwitchInner();
+ DMASTProcStatementSwitch.SwitchCase[]? switchCases = BracedSwitchInner(procParameters) ?? IndentedSwitchInner(procParameters);
if (switchCases == null && hasNewline) {
ReuseToken(beforeSwitchBlock);
@@ -1438,12 +1442,12 @@ private DMASTProcStatementSwitch Switch() {
return switchCases;
}
- private DMASTProcStatementSwitch.SwitchCase[]? BracedSwitchInner() {
+ private DMASTProcStatementSwitch.SwitchCase[]? BracedSwitchInner(List? procParameters = null) {
if (Check(TokenType.DM_LeftCurlyBracket)) {
Whitespace();
Newline();
bool isIndented = Check(TokenType.DM_Indent);
- DMASTProcStatementSwitch.SwitchCase[] switchInner = SwitchInner();
+ DMASTProcStatementSwitch.SwitchCase[] switchInner = SwitchInner(procParameters);
if (isIndented) Check(TokenType.DM_Dedent);
Newline();
Consume(TokenType.DM_RightCurlyBracket, "Expected '}'");
@@ -1454,9 +1458,9 @@ private DMASTProcStatementSwitch Switch() {
return null;
}
- private DMASTProcStatementSwitch.SwitchCase[]? IndentedSwitchInner() {
+ private DMASTProcStatementSwitch.SwitchCase[]? IndentedSwitchInner(List? procParameters = null) {
if (Check(TokenType.DM_Indent)) {
- DMASTProcStatementSwitch.SwitchCase[] switchInner = SwitchInner();
+ DMASTProcStatementSwitch.SwitchCase[] switchInner = SwitchInner(procParameters);
Consume(TokenType.DM_Dedent, "Expected \"if\" or \"else\"");
return switchInner;
@@ -1465,21 +1469,21 @@ private DMASTProcStatementSwitch Switch() {
return null;
}
- private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() {
+ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner(List? procParameters = null) {
List switchCases = new();
- DMASTProcStatementSwitch.SwitchCase? switchCase = SwitchCase();
+ DMASTProcStatementSwitch.SwitchCase? switchCase = SwitchCase(procParameters);
while (switchCase is not null) {
switchCases.Add(switchCase);
Newline();
Whitespace();
- switchCase = SwitchCase();
+ switchCase = SwitchCase(procParameters);
}
return switchCases.ToArray();
}
- private DMASTProcStatementSwitch.SwitchCase? SwitchCase() {
+ private DMASTProcStatementSwitch.SwitchCase? SwitchCase(List? procParameters = null) {
if (Check(TokenType.DM_If)) {
List expressions = new();
@@ -1516,10 +1520,10 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() {
Whitespace();
ConsumeRightParenthesis();
Whitespace();
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
body = (statement != null)
? new DMASTProcBlockInner(statement.Location, statement)
@@ -1537,10 +1541,10 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() {
"Expected \"if\" or \"else\" - \"else if\" is ambiguous as a switch case and may cause unintended flow");
}
- DMASTProcBlockInner? body = ProcBlock();
+ DMASTProcBlockInner? body = ProcBlock(procParameters);
if (body == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
body = (statement != null)
? new DMASTProcBlockInner(loc, statement)
@@ -1553,15 +1557,15 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() {
return null;
}
- private DMASTProcStatementTryCatch TryCatch() {
+ private DMASTProcStatementTryCatch TryCatch(List? procParameters = null) {
var loc = Current().Location;
Advance();
Whitespace();
- DMASTProcBlockInner? tryBody = ProcBlock();
+ DMASTProcBlockInner? tryBody = ProcBlock(procParameters);
if (tryBody == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
if (statement == null) {
statement = new DMASTInvalidProcStatement(loc);
Emit(WarningCode.MissingBody, "Expected body or statement");
@@ -1579,15 +1583,15 @@ private DMASTProcStatementTryCatch TryCatch() {
DMASTProcStatement? parameter = null;
if (Check(TokenType.DM_LeftParenthesis)) {
BracketWhitespace();
- parameter = ProcVarDeclaration(allowMultiple: false);
+ parameter = ProcVarDeclaration(procParameters, allowMultiple: false);
BracketWhitespace();
ConsumeRightParenthesis();
Whitespace();
}
- DMASTProcBlockInner? catchBody = ProcBlock();
+ DMASTProcBlockInner? catchBody = ProcBlock(procParameters);
if (catchBody == null) {
- DMASTProcStatement? statement = ProcStatement();
+ DMASTProcStatement? statement = ProcStatement(procParameters);
if (statement != null) catchBody = new DMASTProcBlockInner(loc, statement);
}
@@ -2756,7 +2760,7 @@ private void BracketWhitespace() {
do {
Whitespace();
- type |= SingleAsType(out _);
+ type |= SingleAsType(out _, out _, out _);
Whitespace();
} while (Check(TokenType.DM_Bar));
@@ -2769,36 +2773,59 @@ private void BracketWhitespace() {
///
/// AsTypes(), but can handle more complex types such as type paths
+ /// If procParameters is non-null, allow as params[1] and as params[foo] syntax
///
- private DMComplexValueType? AsComplexTypes() {
+ private DMComplexValueType? AsComplexTypes(List? procParameters = null) {
if (!AsTypesStart(out var parenthetical))
return null;
if (parenthetical && Check(TokenType.DM_RightParenthesis)) // as ()
return DMValueType.Anything; // TODO: BYOND doesn't allow this for proc return types
+ var outType = UnionComplexTypes(procParameters);
+
+ if (parenthetical) {
+ ConsumeRightParenthesis();
+ }
+
+ return outType;
+ }
+
+ private DMComplexValueType? UnionComplexTypes(List? procParameters = null) {
DMValueType type = DMValueType.Anything;
DreamPath? path = null;
+ List<(int, bool)> paramIndices = new List<(int, bool)>();
+ DMListValueTypes? outListTypes = null;
do {
Whitespace();
- type |= SingleAsType(out var pathType, allowPath: true);
+ type |= SingleAsType(out var pathType, out var param, out var listTypes, allowPath: true, procParameters: procParameters);
Whitespace();
- if (pathType != null) {
- if (path == null)
+ if (param.paramIndex is not null) {
+ paramIndices.Add((param.paramIndex.Value, param.upcasted));
+ }
+ 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, paramIndices.Count > 0 ? paramIndices.ToArray() : null, outListTypes);
}
private bool AsTypesStart(out bool parenthetical) {
@@ -2812,10 +2839,22 @@ private bool AsTypesStart(out bool parenthetical) {
return false;
}
- private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
+ private DMValueType SingleAsType(out DreamPath? path, out (int? paramIndex, bool upcasted) outParam, out DMListValueTypes? listTypes, bool allowPath = false, List? procParameters = null) {
Token typeToken = Current();
- if (!Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) {
+ outParam = (null, false);
+ 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) {
@@ -2823,11 +2862,30 @@ 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(procParameters);
+ 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(procParameters);
+ }
+ 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;
@@ -2846,7 +2904,49 @@ 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 "params":
+ if (procParameters is null) {
+ Emit(WarningCode.BadToken, typeToken.Location, "Cannot use 'as params' outside of a proc or its return type");
+ }
+ if (Check(TokenType.DM_LeftBracket)) {
+ Whitespace();
+ // Check for an identifier first
+ Token paramToken = Current();
+ switch (paramToken.Type) {
+ case TokenType.DM_Identifier:
+ outParam.paramIndex = procParameters?.FindIndex(x => x.Name == paramToken.Text);
+ if (outParam.paramIndex < 0) {
+ outParam.paramIndex = null;
+ }
+ break;
+ case TokenType.DM_Integer:
+ outParam.paramIndex = paramToken.ValueAsInt();
+ if (procParameters is null || (outParam.paramIndex is not null && outParam.paramIndex >= procParameters.Count)) {
+ Emit(WarningCode.BadToken, paramToken.Location, $"Parameter index out of range ({outParam.paramIndex} >= {procParameters?.Count ?? 0})");
+ outParam.paramIndex = null;
+ }
+ break;
+ default:
+ Emit(WarningCode.BadToken, paramToken.Location, $"Unrecognized parameter {paramToken.PrintableText}, expected parameter identifier or index");
+ break;
+ }
+ Advance();
+ Whitespace();
+ // Allow recasting a path to an instance with the instance keyword, here and only here
+ if(AsTypesStart(out var parenthetical)) {
+ Token nextToken = Current();
+ if (nextToken.Type is not TokenType.DM_Identifier || nextToken.Text != "instance")
+ Emit(WarningCode.BadToken, nextToken.Location, $"Expected 'as instance', got 'as {nextToken.PrintableText}'");
+ else
+ outParam.upcasted = true;
+ Advance();
+ if (parenthetical)
+ ConsumeRightParenthesis();
+ }
+ ConsumeRightBracket();
+ }
+ path = null;
+ return DMValueType.Anything;
case "opendream_unimplemented": return DMValueType.Unimplemented;
case "opendream_compiletimereadonly": return DMValueType.CompiletimeReadonly;
case "opendream_noconstfold": return DMValueType.NoConstFold;
diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs
index 9ef101ba4a..9a704eb312 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,11 +289,11 @@ 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:
- result = new New(Compiler, newExpr.Location,
+ result = new New(newExpr.Location,
BuildExpression(newExpr.Expression, inferredPath),
BuildArgumentList(newExpr.Location, newExpr.Parameters, inferredPath));
break;
@@ -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:
@@ -581,7 +586,7 @@ private DMExpression BuildIdentifier(DMASTIdentifier identifier, DreamPath? infe
if (scopeMode == Normal) {
var localVar = ctx.Proc?.GetLocalVariable(name);
if (localVar != null)
- return new Local(identifier.Location, localVar);
+ return new Local(identifier.Location, localVar, localVar.ExplicitValueType);
}
var field = ctx.Type.GetVariable(name);
@@ -615,7 +620,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
@@ -701,7 +706,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);
}
}
@@ -715,7 +720,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)) {
@@ -752,10 +757,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) {
@@ -885,7 +890,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:
@@ -1021,6 +1026,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) {
@@ -1031,16 +1037,25 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
return UnknownReference(callOperation.Location, $"Type {prevPath.Value} does not exist");
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;
+ }
}
operation = new Dereference.CallOperation {
Parameters = argumentList,
Safe = callOperation.Safe,
Identifier = field,
- Path = null
+ Path = prevPath
};
- prevPath = null;
- pathIsFuzzy = true;
+ prevPath = nextPath;
+ if(prevPath is null)
+ pathIsFuzzy = true;
break;
}
@@ -1062,7 +1077,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);
@@ -1103,7 +1118,7 @@ private DMExpression BuildList(DMASTList list, DreamPath? inferredPath) {
}
}
- return new List(list.Location, values);
+ return new List(Compiler, list.Location, values);
}
private DMExpression BuildDimensionalList(DMASTDimensionalList list, DreamPath? inferredPath) {
@@ -1209,7 +1224,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 250ed587e0..3db97c0294 100644
--- a/DMCompiler/DM/Builders/DMProcBuilder.cs
+++ b/DMCompiler/DM/Builders/DMProcBuilder.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using System.Linq;
using DMCompiler.Bytecode;
using DMCompiler.Compiler;
using DMCompiler.Compiler.DM;
@@ -18,6 +19,10 @@ internal sealed class DMProcBuilder(DMCompiler compiler, DMObject dmObject, DMPr
///
// Starts null; marks that we've never seen one before and should just error like normal people.
private Constant? _previousSetStatementValue;
+ ///
+ /// This tracks the current return type.
+ ///
+ private DMComplexValueType CurrentReturnType = DMValueType.Null;
private ExpressionContext ExprContext => new(compiler, dmObject, proc);
@@ -46,9 +51,43 @@ 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 when expression.Expression is DMASTAssign assign && assign.LHS is DMASTCallableSelf:
+ break; // already checked elsewhere
+ default:
+ proc.ValidateReturnType(CurrentReturnType, null, lastStatement.Location);
+ break;
+ }
+ }
+
/// 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 anyways for procs that
/// A.) are not marked opendream_unimplemented and
@@ -62,13 +101,13 @@ private void ProcessBlockInner(DMASTProcBlockInner block, bool silenceEmptyBlock
ProcessStatement(stmt);
}
- if(!silenceEmptyBlockWarning && block.Statements.Length == 0) { // If this block has no real statements
+ if (!silenceEmptyBlockWarning && block.Statements.Length == 0) { // If this block has no real statements
// Not an error in BYOND, but we do have an emission for this!
if (block.SetStatements.Length != 0) {
// Give a more articulate message about this, since it's kinda weird
- compiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected - set statements are executed outside of, before, and unconditional to, this block");
+ compiler.Emit(WarningCode.EmptyBlock, block.Location, "Empty block detected - set statements are executed outside of, before, and unconditional to, this block");
} else {
- compiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected");
+ compiler.Emit(WarningCode.EmptyBlock, block.Location, "Empty block detected");
}
return;
@@ -125,8 +164,52 @@ public 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:
+ DMASTUnary astUnary = (DMASTUnary)checkedExpression;
+ 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:
+ DMASTBinary astBinary = (DMASTBinary)checkedExpression;
+ if (astBinary.LHS.GetUnwrapped() is not DMASTCallableSelf)
+ break;
+ if (currentReturnType.Type != DMValueType.Null)
+ break;
+ currentReturnType = DMComplexValueType.MergeComplexValueTypes(compiler, currentReturnType, realExpr.ValType);
+ break;
+ }
+ }
+
public 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();
}
@@ -365,10 +448,9 @@ public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varD
if (varDeclaration.Value != null) {
value = _exprBuilder.Create(varDeclaration.Value, varDeclaration.Type);
- if (!varDeclaration.ValType.MatchesType(compiler, value.ValType)) {
+ if (!varDeclaration.ValType.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}");
- }
} else {
value = new Null(varDeclaration.Location);
}
@@ -382,7 +464,7 @@ public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varD
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) {
@@ -396,7 +478,7 @@ public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varD
}
public 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
@@ -410,6 +492,7 @@ public void ProcessStatementReturn(DMASTProcStatementReturn statement) {
expr.EmitPushValue(ExprContext);
} else {
+ proc.ValidateReturnType(CurrentReturnType, null, statement.Location);
proc.PushReferenceValue(DMReference.Self); //Default return value
}
@@ -417,7 +500,10 @@ public void ProcessStatementReturn(DMASTProcStatementReturn statement) {
}
public 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();
@@ -426,6 +512,7 @@ public void ProcessStatementIf(DMASTProcStatementIf statement) {
proc.StartScope();
ProcessBlockInner(statement.Body);
proc.EndScope();
+ CurrentReturnType = condReturnType;
proc.AddLabel(endLabel);
} else {
string elseLabel = proc.NewLabelName();
@@ -436,21 +523,28 @@ public 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;
}
public 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.Expression2 != null || statementFor.Expression3 != null) {
@@ -524,7 +618,10 @@ public void ProcessStatementFor(DMASTProcStatementFor statementFor) {
var list = _exprBuilder.Create(exprIn.RHS);
if (outputVar is Local outputLocal) {
- outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes;
+ if (statementFor.DMTypes is not null)
+ outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes;
+ else if (list.ValType.IsList && list.ValType.ListValueTypes is 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");
} else if (outputVar is Field { IsConst: true })
@@ -794,7 +891,9 @@ public 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();
@@ -851,25 +950,33 @@ Constant CoerceBound(Constant bound) {
}
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);
}
@@ -962,7 +1069,7 @@ public void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) {
if (tryCatch.CatchParameter != null) {
var param = tryCatch.CatchParameter as DMASTProcStatementVarDeclaration;
- 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/DMExpression.cs b/DMCompiler/DM/DMExpression.cs
index 73de3895ba..313eaea55f 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,7 +130,7 @@ 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)) {
+ if (!(compiler.Settings.SkipAnythingTypecheck && 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}");
}
diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs
index 10834f8433..87900d3a04 100644
--- a/DMCompiler/DM/DMObject.cs
+++ b/DMCompiler/DM/DMObject.cs
@@ -97,16 +97,21 @@ 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;
+ return globalProc.GetParameterValueTypes(arguments);
if (GetProcs(name) is not { } procs)
- return Parent?.GetProcReturnTypes(name);
+ return Parent?.GetProcReturnTypes(name, arguments);
var proc = compiler.DMObjectTree.AllProcs[procs[0]];
if ((proc.Attributes & ProcAttributes.IsOverride) != 0)
- return Parent?.GetProcReturnTypes(name) ?? DMValueType.Anything;
+ return Parent?.GetProcReturnTypes(name, arguments) ?? DMValueType.Anything;
- return proc.RawReturnTypes;
+ return proc.GetParameterValueTypes(arguments);
}
public void AddVerb(DMProc verb) {
@@ -232,14 +237,12 @@ 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;
-
- return DMValueType.Anything;
+ public DreamPath GetLastCommonAncestor(DMObject other) {
+ if(other.IsSubtypeOf(Path)) {
+ return Path;
+ } else if(IsSubtypeOf(other.Path)) {
+ return other.Path;
+ }
+ return Parent?.GetLastCommonAncestor(other) ?? DreamPath.Root;
}
}
diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs
index 1d763352b5..3eb983be37 100644
--- a/DMCompiler/DM/DMObjectTree.cs
+++ b/DMCompiler/DM/DMObjectTree.cs
@@ -163,7 +163,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 670442f16b..534af2c5a0 100644
--- a/DMCompiler/DM/DMProc.cs
+++ b/DMCompiler/DM/DMProc.cs
@@ -140,10 +140,13 @@ public void Compile() {
}
public void ValidateReturnType(DMExpression expr) {
- var type = expr.ValType;
+ ValidateReturnType(expr.ValType, expr, expr.Location);
+ }
+
+ public void ValidateReturnType(DMComplexValueType type, DMExpression? expr, Location location) {
var returnTypes = _dmObject.GetProcReturnTypes(Name)!.Value;
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;
}
@@ -155,17 +158,19 @@ public void ValidateReturnType(DMExpression expr) {
switch (expr) {
case ProcCall:
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}.");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}.");
break;
case Local:
- _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{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() ?? "Unknown"}.{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() ?? "Unknown"}.{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() ?? "Unknown"}.{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() ?? "Unknown"}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}");
+ _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject?.Path.ToString() ?? "Unknown"}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}");
}
}
@@ -185,13 +190,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)
});
}
}
@@ -278,7 +283,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;
@@ -316,6 +321,57 @@ 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 DMComplexValueType GetParameterValueTypes(ArgumentList? arguments) {
+ return GetParameterValueTypes(RawReturnTypes, arguments);
+ }
+
+ public DMComplexValueType GetParameterValueTypes(DMComplexValueType? baseType, ArgumentList? arguments) {
+ if (baseType?.ParameterIndices is null) {
+ return baseType ?? DMValueType.Anything;
+ }
+ DMComplexValueType returnType = baseType ?? DMValueType.Anything;
+ foreach ((int parameterIndex, bool upcasted) in baseType!.Value.ParameterIndices) {
+ DMComplexValueType intermediateType = DMValueType.Anything;
+ if (arguments is null || parameterIndex >= arguments.Expressions.Length) {
+ if (!TryGetParameterAtIndex(parameterIndex, out var parameter)) {
+ _compiler.Emit(WarningCode.BadArgument, Location, $"Unable to find argument with index {parameterIndex}");
+ continue;
+ }
+ intermediateType = parameter.ExplicitValueType ?? DMValueType.Anything;
+ } else if (arguments is not null) {
+ intermediateType = arguments.Expressions[parameterIndex].Expr.ValType;
+ }
+ if (upcasted) {
+ if (intermediateType.HasPath) {
+ if (intermediateType.Type.HasFlag(~(DMValueType.Path | DMValueType.Null)))
+ _compiler.Emit(WarningCode.InvalidVarType, arguments?.Location ?? Location, "Expected an exclusively path (or null) typed parameter");
+ else
+ intermediateType = new DMComplexValueType((intermediateType.Type & DMValueType.Null) | DMValueType.Instance, intermediateType.TypePath);
+ } else if (_compiler.Settings.SkipAnythingTypecheck && intermediateType.IsAnything) {
+ //pass
+ } else {
+ _compiler.Emit(WarningCode.InvalidVarType, arguments?.Location ?? Location, "Expected a path (or path|null) typed parameter");
+ }
+ }
+ returnType = DMComplexValueType.MergeComplexValueTypes(_compiler, returnType, intermediateType);
+ }
+ return returnType;
+ }
+
public void Error() {
WriteOpcode(DreamProcOpcode.Error);
}
diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs
index 61f23652aa..a6ef541b8d 100644
--- a/DMCompiler/DM/DMValueType.cs
+++ b/DMCompiler/DM/DMValueType.cs
@@ -1,4 +1,4 @@
-namespace DMCompiler.DM;
+namespace DMCompiler.DM;
// If you are modifying this, you must also modify OpenDreamShared.Dream.DreamValueType !!
// Unfortunately the client needs this and it can't reference DMCompiler due to the sandbox
@@ -23,12 +23,13 @@ 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
+ 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
}
///
@@ -37,11 +38,25 @@ public enum DMValueType {
public readonly struct DMComplexValueType {
public readonly DMValueType Type;
public readonly DreamPath? TypePath;
+ ///
+ /// The indices of the proc parameters used to infer proc return types.
+ /// The resulting type is the union of all the parameter types with `this.Type`.
+ /// If two or more of those types have paths, their common ancestor is used.
+ ///
+ public readonly (int, bool)[]? ParameterIndices;
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 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
@@ -49,33 +64,135 @@ public DMComplexValueType(DMValueType type, DreamPath? typePath) {
IsUnimplemented = type.HasFlag(DMValueType.Unimplemented);
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, (int, bool)[]? parameterIndices) : this(type, typePath) {
+ ParameterIndices = parameterIndices;
+ }
+
+ public DMComplexValueType(DMValueType type, DreamPath? typePath, (int, bool)[]? parameterIndices, DMListValueTypes? listValueTypes) : this(type, typePath, parameterIndices) {
+ ListValueTypes = listValueTypes;
+ }
+
+ public DMComplexValueType(DMValueType type, DreamPath? typePath, DMListValueTypes? listValueTypes) : this(type, typePath, null, listValueTypes) { }
+
public bool MatchesType(DMValueType type) {
- return IsAnything || (Type & type) != 0;
+ if (IsAnything || (Type & type) != 0) return true;
+ 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.IsList && type.ListValueTypes?.NestedListKeyType.Type == DMValueType.Num && type.ListValueTypes.NestedListValType is 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);
}
public override string ToString() {
var types = Type.ToString().ToLowerInvariant();
- return $"\"{(IsPath ? types + ", " + TypePath!.Value : types)}\"";
+ return $"\"{(HasPath ? types + $", {TypePath!.Value}{((IsList && ListValueTypes is not null) ? $"({ListValueTypes})" : "")}" : types)}\"";
}
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;
+ } else 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 dc5cd772cc..3d0b22b8c4 100644
--- a/DMCompiler/DM/DMVariable.cs
+++ b/DMCompiler/DM/DMVariable.cs
@@ -27,7 +27,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 0d9420faeb..c519baf187 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;
@@ -323,6 +323,7 @@ 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);
@@ -332,6 +333,7 @@ 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);
@@ -341,6 +343,7 @@ 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);
@@ -350,6 +353,7 @@ 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);
@@ -359,6 +363,7 @@ 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;
@@ -386,6 +391,7 @@ 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) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -413,6 +419,7 @@ 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) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -440,6 +447,7 @@ 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) {
LHS.EmitPushValue(ctx);
RHS.EmitPushValue(ctx);
@@ -496,6 +504,7 @@ 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;
@@ -523,6 +532,7 @@ 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);
@@ -558,6 +568,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);
@@ -586,6 +597,7 @@ 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 : RHS.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 6092ce591d..2c5e47349b 100644
--- a/DMCompiler/DM/Expressions/Builtins.cs
+++ b/DMCompiler/DM/Expressions/Builtins.cs
@@ -58,10 +58,19 @@ public void EmitPushArglist(ExpressionContext ctx) {
}
// new x (...)
-internal sealed class New(DMCompiler compiler, Location location, DMExpression expr, ArgumentList arguments) : DMExpression(location) {
+internal sealed class New(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);
@@ -72,9 +81,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;
@@ -103,8 +112,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)) {
@@ -134,6 +143,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);
@@ -182,6 +192,7 @@ 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);
@@ -193,12 +204,28 @@ public override void EmitPushValue(ExpressionContext ctx) {
// 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) {
@@ -368,6 +395,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);
@@ -379,6 +407,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);
@@ -393,9 +422,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;
@@ -405,6 +435,15 @@ 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) {
+ // why did I do it like this
+ 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) {
@@ -463,6 +502,7 @@ 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);
@@ -521,6 +561,14 @@ 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..4df37c60ee 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,17 @@ 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 +242,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 06338f2468..74357d4107 100644
--- a/DMCompiler/DM/Expressions/Dereference.cs
+++ b/DMCompiler/DM/Expressions/Dereference.cs
@@ -36,6 +36,18 @@ 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 +60,41 @@ 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 {
+ if (_valType is null) _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 +102,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")
};
}
@@ -125,7 +146,14 @@ 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;
@@ -314,8 +342,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,
@@ -325,6 +353,12 @@ 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 is not null ? constant.ValType : dmVar.ValType;
+ }
+ }
public override string GetNameof(ExpressionContext ctx) => dmVar.Name;
diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs
index d5fd05f468..58cfdd5d5b 100644
--- a/DMCompiler/DM/Expressions/LValue.cs
+++ b/DMCompiler/DM/Expressions/LValue.cs
@@ -77,6 +77,7 @@ 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;
@@ -86,11 +87,17 @@ 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 {
+ //todo: allow local variables to be param-typed again
+ // WITHOUT having to pass procParameters through the whole parser chain
+ //if (valType is not null) return proc.GetBaseProc().GetParameterValueTypes(valType.Value, null);
+ if (valType is not null && !valType.Value.IsAnything) return valType.Value;
+ return 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 ba5105dba0..4334acb0be 100644
--- a/DMCompiler/DM/Expressions/Procs.cs
+++ b/DMCompiler/DM/Expressions/Procs.cs
@@ -5,7 +5,10 @@
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(theObject);
public override void EmitPushValue(ExpressionContext ctx) {
ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to use proc as value");
ctx.Proc.Error();
@@ -57,8 +60,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 +91,24 @@ 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.GetParameterValueTypes(arguments);
+ 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 c5b874a89b..def215e275 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 f6b07c94fb..4e6a0704ee 100644
--- a/DMCompiler/DM/Expressions/Unary.cs
+++ b/DMCompiler/DM/Expressions/Unary.cs
@@ -4,11 +4,12 @@
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;
@@ -25,6 +26,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// !x
internal sealed class Not(Location location, DMExpression expr) : UnaryOp(location, expr) {
+ public override DMComplexValueType ValType => DMValueType.Num; // 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;
@@ -40,6 +42,7 @@ 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 185e87280f..5f7ff02ece 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
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 62c079e2a3..250e152a09 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
diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm
index 136e2ee2fb..2b13dcf026 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
@@ -49,8 +49,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
@@ -89,7 +88,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)
proc/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 e8d25d096d..20bd076cc1 100644
--- a/DMCompiler/DMStandard/Types/List.dm
+++ b/DMCompiler/DMStandard/Types/List.dm
@@ -4,13 +4,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 null
+ 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 333d3eec9e..c0243a78a3 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 opendream_unimplemented
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 opendream_unimplemented
@@ -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
diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm
index 4795053892..cdf2d59cac 100644
--- a/DMCompiler/DMStandard/_Standard.dm
+++ b/DMCompiler/DMStandard/_Standard.dm
@@ -1,16 +1,18 @@
+/var/world/world = /world as /world|path(/world)
+
//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/ceil(A) as num
-proc/ckey(Key) as text|null
+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/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
@@ -19,25 +21,25 @@ 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)
set opendream_unimplemented = 1
proc/flist(Path) as /list
proc/floor(A) as num
proc/fract(n) as num
proc/ftime(File, IsCreationTime = 0) as num
-proc/get_step_to(Ref, Trg, Min=0) as num
-proc/get_steps_to(Ref, Trg, Min=0) as /list
+proc/get_step_to(Ref, Trg, Min=0 as num)
+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
@@ -57,7 +59,7 @@ proc/json_decode(JSON)
proc/json_encode(Value, flags)
proc/length_char(E) as num
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
@@ -65,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)
@@ -92,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
@@ -145,27 +147,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")
@@ -177,12 +179,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)
@@ -193,14 +195,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 1812e745a3..93d7647f60 100644
--- a/DMCompiler/DreamPath.cs
+++ b/DMCompiler/DreamPath.cs
@@ -106,6 +106,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;
}
@@ -256,4 +264,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);