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