diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index da9286f54e..a253af8d6a 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -237,9 +237,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(); @@ -539,12 +539,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); @@ -553,7 +553,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; @@ -561,7 +561,7 @@ public DMASTFile File() { Whitespace(); Newline(); if (Current().Type == TokenType.DM_Indent) { - block = IndentedProcBlock(); + block = IndentedProcBlock(procParameters); Newline(); Consume(TokenType.DM_RightCurlyBracket, "Expected '}'"); } else { @@ -593,14 +593,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) @@ -621,14 +621,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(); @@ -643,7 +643,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) @@ -748,20 +748,20 @@ public DMASTFile File() { return new DMASTProcStatementExpression(loc, expression); } else { // These are sorted by frequency - DMASTProcStatement? procStatement = If(); + DMASTProcStatement? procStatement = If(procParameters); procStatement ??= Return(); - procStatement ??= ProcVarDeclaration(); - procStatement ??= For(); + procStatement ??= ProcVarDeclaration(procParameters); + procStatement ??= For(procParameters); procStatement ??= Set(); - procStatement ??= Switch(); + procStatement ??= Switch(procParameters); procStatement ??= Continue(); procStatement ??= Break(); - procStatement ??= Spawn(); - procStatement ??= While(); - procStatement ??= DoWhile(); + procStatement ??= Spawn(procParameters); + procStatement ??= While(procParameters); + procStatement ??= DoWhile(procParameters); procStatement ??= Throw(); procStatement ??= Del(); - procStatement ??= TryCatch(); + procStatement ??= TryCatch(procParameters); procStatement ??= Goto(); if (procStatement != null) { @@ -772,7 +772,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); @@ -783,7 +783,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); @@ -802,7 +802,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(); @@ -810,7 +810,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 { @@ -831,7 +831,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) { @@ -857,12 +857,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; } @@ -884,7 +884,7 @@ public DMASTFile File() { RequireExpression(ref value); } - var valType = AsComplexTypes() ?? DMValueType.Anything; + var valType = AsComplexTypes(procParameters); varDeclarations.Add(new DMASTProcStatementVarDeclaration(loc, varPath, value, valType)); if (allowMultiple && Check(TokenType.DM_Comma)) { @@ -1087,7 +1087,7 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { return null; } - private DMASTProcStatementSpawn? Spawn() { + private DMASTProcStatementSpawn? Spawn(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_Spawn)) { @@ -1110,9 +1110,9 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { 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); @@ -1128,7 +1128,7 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { } } - private DMASTProcStatementIf? If() { + private DMASTProcStatementIf? If(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_If)) { @@ -1148,11 +1148,11 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { Check(TokenType.DM_Colon); Whitespace(); - DMASTProcStatement? procStatement = ProcStatement(); + DMASTProcStatement? procStatement = ProcStatement(procParameters); DMASTProcBlockInner? elseBody = null; DMASTProcBlockInner? body = (procStatement != null) ? new DMASTProcBlockInner(loc, procStatement) - : ProcBlock(); + : ProcBlock(procParameters); body ??= new DMASTProcBlockInner(loc); Token afterIfBody = Current(); @@ -1162,11 +1162,11 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { 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); @@ -1178,7 +1178,7 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { } } - private DMASTProcStatement? For() { + private DMASTProcStatement? For(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_For)) { @@ -1187,7 +1187,7 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { Whitespace(); if (Check(TokenType.DM_RightParenthesis)) { - return new DMASTProcStatementInfLoop(loc, GetForBody(loc)); + return new DMASTProcStatementInfLoop(loc, GetForBody(loc, procParameters)); } _allowVarDeclExpression = true; @@ -1207,7 +1207,7 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { if (expr1 is DMASTAssign assign) { ExpressionTo(out var endRange, out var step); Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after to expression"); - 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); @@ -1219,16 +1219,16 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { DMASTExpression? listExpr = Expression(); Whitespace(); Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); - 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)) { Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 1"); - 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)) { - return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc)); + return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc, procParameters)); } Whitespace(); @@ -1243,11 +1243,11 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { if (!Check(ForSeparatorTypes)) { Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); - 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)) { - return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc)); + return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc, procParameters)); } Whitespace(); @@ -1261,23 +1261,23 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { } Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 3"); - return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc)); + return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc, procParameters)); } return null; - 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) { DMCompiler.Emit(WarningCode.MissingBody, forLocation, "Expected body or statement"); statement = new DMASTInvalidProcStatement(loc); @@ -1291,7 +1291,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) { } } - private DMASTProcStatement? While() { + private DMASTProcStatement? While(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_While)) { @@ -1303,10 +1303,10 @@ DMASTProcBlockInner GetForBody(Location forLocation) { 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); @@ -1324,7 +1324,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) { return null; } - private DMASTProcStatementDoWhile? DoWhile() { + private DMASTProcStatementDoWhile? DoWhile(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_Do)) { @@ -1359,7 +1359,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) { return null; } - private DMASTProcStatementSwitch? Switch() { + private DMASTProcStatementSwitch? Switch(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_Switch)) { @@ -1371,7 +1371,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) { ConsumeRightParenthesis(); Whitespace(); - DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases(); + DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases(procParameters); if (switchCases == null) { switchCases = []; Emit(WarningCode.MissingBody, "Expected switch cases"); @@ -1383,11 +1383,11 @@ DMASTProcBlockInner GetForBody(Location forLocation) { return null; } - 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); @@ -1396,12 +1396,12 @@ DMASTProcBlockInner GetForBody(Location forLocation) { 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 '}'"); @@ -1412,9 +1412,9 @@ DMASTProcBlockInner GetForBody(Location forLocation) { 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; @@ -1423,21 +1423,21 @@ DMASTProcBlockInner GetForBody(Location forLocation) { 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(); @@ -1474,10 +1474,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) @@ -1495,10 +1495,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) @@ -1511,15 +1511,15 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { return null; } - private DMASTProcStatementTryCatch? TryCatch() { + private DMASTProcStatementTryCatch? TryCatch(List? procParameters = null) { var loc = Current().Location; if (Check(TokenType.DM_Try)) { 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"); @@ -1537,15 +1537,15 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { 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); } @@ -2699,7 +2699,7 @@ private void BracketWhitespace() { do { Whitespace(); - type |= SingleAsType(out _); + type |= SingleAsType(out _, out _, out _); Whitespace(); } while (Check(TokenType.DM_Bar)); @@ -2712,22 +2712,37 @@ 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, out var listTypes, allowPath: true); + type |= SingleAsType(out var pathType, out var param, out var listTypes, allowPath: true, procParameters: procParameters); Whitespace(); + if (param.paramIndex is not null) { + paramIndices.Add((param.paramIndex.Value, param.upcasted)); + } if (pathType is not null) { if (path is null) path = pathType; @@ -2749,7 +2764,7 @@ private void BracketWhitespace() { } } while (Check(TokenType.DM_Bar)); - return new(type, path, outListTypes); + return new(type, path, paramIndices.Count > 0 ? paramIndices.ToArray() : null, outListTypes); } private bool AsTypesStart(out bool parenthetical) { @@ -2763,9 +2778,10 @@ private bool AsTypesStart(out bool parenthetical) { return false; } - private DMValueType SingleAsType(out DreamPath? path, out DMListValueTypes? listTypes, 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(); + outParam = (null, false); listTypes = null; var inPath = false; @@ -2827,7 +2843,49 @@ private DMValueType SingleAsType(out DreamPath? path, out DMListValueTypes? list 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) { + DMCompiler.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)) { + DMCompiler.Emit(WarningCode.BadToken, paramToken.Location, $"Parameter index out of range ({outParam.paramIndex} >= {procParameters?.Count ?? 0})"); + outParam.paramIndex = null; + } + break; + default: + DMCompiler.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") + DMCompiler.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; default: diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs index f0dc753ddb..0fcf430bfa 100644 --- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs @@ -779,7 +779,7 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm return BadExpression(WarningCode.ItemDoesntExist, callOperation.Location, $"Type {prevPath.Value} does not have a proc named \"{field}\""); - var returnTypes = fromObject.GetProcReturnTypes(field) ?? DMValueType.Anything; + var returnTypes = fromObject.GetProcReturnTypes(field, argumentList) ?? DMValueType.Anything; nextPath = returnTypes.HasPath ? returnTypes.TypePath : returnTypes.AsPath(); if (!returnTypes.HasPath & nextPath.HasValue) { var thePath = nextPath!.Value; diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index 5d8afb09e7..dc93e26c15 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -1,4 +1,4 @@ -using DMCompiler.Bytecode; +using DMCompiler.Bytecode; using DMCompiler.Compiler; using DMCompiler.Json; @@ -95,16 +95,21 @@ public bool HasProcNoInheritance(string name) { } public DMComplexValueType? GetProcReturnTypes(string name) { + + return GetProcReturnTypes(name, null); + } + + public DMComplexValueType? GetProcReturnTypes(string name, ArgumentList? arguments) { if (this == DMObjectTree.Root && 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 = 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) { diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 90fd6bc22f..ee76d87b36 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -318,6 +318,52 @@ 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 == DMObjectTree.Root && 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 = 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)) { + DMCompiler.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 || (intermediateType.Type & ~(DMValueType.Path|DMValueType.Null)) != DMValueType.Anything) { + DMCompiler.Emit(WarningCode.InvalidVarType, arguments?.Location ?? Location, "Expected an exclusively path (or null) typed parameter"); + } else { + intermediateType = new DMComplexValueType(intermediateType.Type & ~DMValueType.Path | DMValueType.Instance, intermediateType.TypePath); + } + } + returnType |= intermediateType; + } + return returnType; + } + public void Error() { WriteOpcode(DreamProcOpcode.Error); } diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index 6369e9057d..5b6da7a872 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 @@ -37,6 +37,12 @@ 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 IsInstance => Type.HasFlag(DMValueType.Instance); @@ -61,9 +67,16 @@ public DMComplexValueType(DMValueType type, DreamPath? typePath) { throw new Exception("A Path or Instance value type must have a type-path"); } - public DMComplexValueType(DMValueType type, DreamPath? typePath, DMListValueTypes? listValueTypes) : this(type, typePath) { + 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; } diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs index fa0e5e0f9b..41357e71e4 100644 --- a/DMCompiler/DM/Expressions/Dereference.cs +++ b/DMCompiler/DM/Expressions/Dereference.cs @@ -83,7 +83,7 @@ private DMComplexValueType DetermineValType() { type = operation switch { FieldOperation fieldOperation => dmObject.GetVariable(fieldOperation.Identifier)?.ValType ?? DMValueType.Anything, IndexOperation indexOperation => type.ListValueTypes is null ? DMValueType.Anything : (indexOperation.Index.ValType.Type.HasFlag(DMValueType.Num) ? type.ListValueTypes.NestedListKeyType : type.ListValueTypes.NestedListValType ?? type.ListValueTypes.NestedListKeyType) | DMValueType.Null, // TODO: Keys of assoc lists - CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier) ?? DMValueType.Anything, + CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier, callOperation.Parameters) ?? DMValueType.Anything, _ => throw new InvalidOperationException("Unimplemented dereference operation") }; } diff --git a/DMCompiler/DM/Expressions/Procs.cs b/DMCompiler/DM/Expressions/Procs.cs index 01dd584097..4ff8df6ec4 100644 --- a/DMCompiler/DM/Expressions/Procs.cs +++ b/DMCompiler/DM/Expressions/Procs.cs @@ -72,7 +72,7 @@ public DMProc GetProc() { /// This is an LValue _and_ a proc! /// internal sealed class ProcSelf(Location location, DreamPath? path, DMProc proc) : LValue(location, path) { - public override DMComplexValueType ValType => proc.RawReturnTypes; + public override DMComplexValueType ValType => proc.GetParameterValueTypes(null); public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.Self; @@ -110,10 +110,10 @@ public override DMComplexValueType ValType { return valType; switch (target) { case Proc procTarget: - return procTarget.dmObject.GetProcReturnTypes(procTarget.Identifier) ?? DMValueType.Anything; + return procTarget.dmObject.GetProcReturnTypes(procTarget.Identifier, arguments) ?? DMValueType.Anything; case GlobalProc procTarget: if(DMObjectTree.TryGetGlobalProc(procTarget.Identifier, out var globalProc)) - return globalProc.RawReturnTypes ?? DMValueType.Anything; + return globalProc.GetParameterValueTypes(arguments); return DMValueType.Anything; } return target.ValType;