From 18421408d75104569eac3df52fed63ef715da861 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 05:36:41 -0500 Subject: [PATCH 01/12] Initial commit --- .../Compiler/DM/AST/DMAST.ObjectStatements.cs | 4 +- .../Compiler/DM/AST/DMAST.ProcStatements.cs | 5 +- DMCompiler/Compiler/DM/DMParser.cs | 93 ++++++++--- DMCompiler/DM/Builders/DMExpressionBuilder.cs | 51 +++--- DMCompiler/DM/Builders/DMProcBuilder.cs | 133 ++++++++++++++-- DMCompiler/DM/DMExpression.cs | 3 +- DMCompiler/DM/DMObject.cs | 21 ++- DMCompiler/DM/DMObjectTree.cs | 2 +- DMCompiler/DM/DMProc.cs | 37 +++-- DMCompiler/DM/DMValueType.cs | 145 ++++++++++++++++-- DMCompiler/DM/DMVariable.cs | 3 +- DMCompiler/DM/Expressions/Binary.cs | 29 +++- DMCompiler/DM/Expressions/Builtins.cs | 70 ++++++++- DMCompiler/DM/Expressions/Constant.cs | 13 +- DMCompiler/DM/Expressions/Dereference.cs | 55 ++++++- DMCompiler/DM/Expressions/LValue.cs | 12 +- DMCompiler/DM/Expressions/Procs.cs | 30 +++- DMCompiler/DM/Expressions/Ternary.cs | 4 +- DMCompiler/DM/Expressions/Unary.cs | 8 +- DMCompiler/DMStandard/Types/Atoms/Mob.dm | 10 +- DMCompiler/DMStandard/Types/Atoms/Movable.dm | 9 +- DMCompiler/DMStandard/Types/Atoms/_Atom.dm | 63 ++++---- DMCompiler/DMStandard/Types/Client.dm | 7 +- DMCompiler/DMStandard/Types/Datum.dm | 2 +- DMCompiler/DMStandard/Types/Icon.dm | 2 +- DMCompiler/DMStandard/Types/List.dm | 12 +- DMCompiler/DMStandard/Types/World.dm | 28 ++-- DMCompiler/DMStandard/_Standard.dm | 84 +++++----- DMCompiler/DreamPath.cs | 16 ++ DMCompiler/Location.cs | 3 +- 30 files changed, 731 insertions(+), 223 deletions(-) diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs index a2f210bdfc..845b38e10e 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs @@ -70,7 +70,7 @@ public sealed class DMASTObjectVarDefinition( Location location, DreamPath path, DMASTExpression value, - DMComplexValueType valType) : DMASTStatement(location) { + DMComplexValueType? valType) : DMASTStatement(location) { /// The path of the object that we are a property of. public DreamPath ObjectPath => _varDecl.ObjectPath; @@ -88,7 +88,7 @@ public sealed class DMASTObjectVarDefinition( public bool IsFinal => _varDecl.IsFinal; public bool IsTmp => _varDecl.IsTmp; - public readonly DMComplexValueType ValType = valType; + public readonly DMComplexValueType? ValType = valType; } public sealed class DMASTMultipleObjectVarDefinitions(Location location, DMASTObjectVarDefinition[] varDefinitions) diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs index 37067a9929..20c2835b4e 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs @@ -29,13 +29,14 @@ public sealed class DMASTProcStatementExpression(Location location, DMASTExpress public DMASTExpression Expression = expression; } -public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType valType) +public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType? valType) : DMASTProcStatement(location) { public DMASTExpression? Value = value; public DreamPath? Type => _varDecl.IsList ? DreamPath.List : _varDecl.TypePath; + public DMComplexValueType? ExplicitValType => valType; - public DMComplexValueType ValType => valType; + public DMComplexValueType? ValType => ExplicitValType ?? DMValueType.Anything; public string Name => _varDecl.VarName; public bool IsGlobal => _varDecl.IsStatic; diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index 99e1274629..e9a1a89a46 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -324,7 +324,7 @@ public DMASTFile File() { value = new DMASTConstantNull(loc); } - var valType = AsComplexTypes() ?? DMValueType.Anything; + var valType = AsComplexTypes(); var varDef = new DMASTObjectVarDefinition(loc, varPath, value, valType); varDefinitions.Add(varDef); @@ -928,7 +928,11 @@ public DMASTFile File() { RequireExpression(ref value); } - var valType = AsComplexTypes() ?? DMValueType.Anything; + var valType = AsComplexTypes(); + // the != DMValueType.Null check is a hacky workaround for Anything|Null being equal to just Null + if (valType is null && value?.GetUnwrapped() is DMASTInput input && input.Types != DMValueType.Null) { + valType = input.Types; + } varDeclarations.Add(new DMASTProcStatementVarDeclaration(loc, varPath, value, valType)); if (allowMultiple && Check(TokenType.DM_Comma)) { @@ -2757,7 +2761,7 @@ private void BracketWhitespace() { do { Whitespace(); - type |= SingleAsType(out _); + type |= SingleAsType(out _, out _); Whitespace(); } while (Check(TokenType.DM_Bar)); @@ -2777,29 +2781,50 @@ private void BracketWhitespace() { if (parenthetical && Check(TokenType.DM_RightParenthesis)) // as () return DMValueType.Anything; // TODO: BYOND doesn't allow this for proc return types + var outType = UnionComplexTypes(); + + if (parenthetical) { + ConsumeRightParenthesis(); + } + + return outType; + } + + private DMComplexValueType? UnionComplexTypes() { + DMValueType type = DMValueType.Anything; DreamPath? path = null; + DMListValueTypes? outListTypes = null; do { Whitespace(); - type |= SingleAsType(out var pathType, allowPath: true); + type |= SingleAsType(out var pathType, out var listTypes, allowPath: true); Whitespace(); - if (pathType != null) { - if (path == null) + if (pathType is not null) { + if (path is null) path = pathType; - else - Compiler.Emit(WarningCode.BadToken, CurrentLoc, - $"Only one type path can be used, ignoring {pathType}"); + else { + var newPath = path.Value.GetLastCommonAncestor(compiler, pathType.Value); + if (newPath != path) { + Compiler.Emit(WarningCode.LostTypeInfo, CurrentLoc, + $"Only one type path can be used, using last common ancestor {newPath}"); + path = newPath; + } + } } - } while (Check(TokenType.DM_Bar)); + if (listTypes is not null) { + if (outListTypes is null) + outListTypes = listTypes; + else { + outListTypes = DMListValueTypes.MergeListValueTypes(compiler, outListTypes, listTypes); + } + } - if (parenthetical) { - ConsumeRightParenthesis(); - } + } while (Check(TokenType.DM_Bar)); - return new(type, path); + return new(type, path, outListTypes); } private bool AsTypesStart(out bool parenthetical) { @@ -2813,10 +2838,21 @@ private bool AsTypesStart(out bool parenthetical) { return false; } - private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) { + private DMValueType SingleAsType(out DreamPath? path, out DMListValueTypes? listTypes, bool allowPath = false) { Token typeToken = Current(); - if (!Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) { + listTypes = null; + + var inPath = false; + if (typeToken.Type is TokenType.DM_Identifier && typeToken.Text == "path") { + Advance(); + if(Check(TokenType.DM_LeftParenthesis)) { + inPath = true; + Whitespace(); + } + } + + if (inPath || !Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) { // Proc return types path = Path()?.Path; if (allowPath) { @@ -2824,11 +2860,31 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) { Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type or path"); } - return DMValueType.Path; + if (inPath) { + Whitespace(); + ConsumeRightParenthesis(); + } else if (path == DreamPath.List) { + // check for list types + if (Check(TokenType.DM_LeftParenthesis)) { + DMComplexValueType? nestedKeyType = UnionComplexTypes(); + if (nestedKeyType is null) + Compiler.Emit(WarningCode.BadToken, CurrentLoc, "Expected value type or path"); + DMComplexValueType? nestedValType = null; + if (Check(TokenType.DM_Comma)) { // Value + nestedValType = UnionComplexTypes(); + } + + ConsumeRightParenthesis(); + listTypes = new(nestedKeyType!.Value, nestedValType); + return DMValueType.Instance; + } + } + + return inPath ? DMValueType.Path : DMValueType.Instance; } Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type"); - return 0; + return DMValueType.Anything; } path = null; @@ -2847,7 +2903,6 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) { case "command_text": return DMValueType.CommandText; case "sound": return DMValueType.Sound; case "icon": return DMValueType.Icon; - case "path": return DMValueType.Path; case "opendream_unimplemented": return DMValueType.Unimplemented; case "opendream_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 fb20e59a0e..61c307a518 100644 --- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs @@ -46,6 +46,11 @@ public void Emit(DMASTExpression expression, DreamPath? inferredPath = null) { expr.EmitPushValue(ctx); } + public void Emit(DMASTExpression expression, out DMExpression returnedExpr, DreamPath? inferredPath = null) { + returnedExpr = Create(expression, inferredPath); + returnedExpr.EmitPushValue(ctx); + } + public bool TryConstant(DMASTExpression expression, out Constant? constant) { var expr = Create(expression); return expr.TryAsConstant(Compiler, out constant); @@ -64,7 +69,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe case DMASTStringFormat stringFormat: result = BuildStringFormat(stringFormat, inferredPath); break; case DMASTIdentifier identifier: result = BuildIdentifier(identifier, inferredPath); break; case DMASTScopeIdentifier globalIdentifier: result = BuildScopeIdentifier(globalIdentifier, inferredPath); break; - case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.ReturnTypes); break; + case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.RawReturnTypes); break; case DMASTCallableSuper: result = new ProcSuper(expression.Location, ctx.Type.GetProcReturnTypes(ctx.Proc.Name)); break; case DMASTCallableProcIdentifier procIdentifier: result = BuildCallableProcIdentifier(procIdentifier, ctx.Type); break; case DMASTProcCall procCall: result = BuildProcCall(procCall, inferredPath); break; @@ -272,10 +277,10 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe if (b.ValType.TypePath != null && c.ValType.TypePath != null && b.ValType.TypePath != c.ValType.TypePath) { Compiler.Emit(WarningCode.LostTypeInfo, ternary.Location, - $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using {b.ValType.TypePath}."); + $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using their common ancestor, {b.ValType.TypePath.Value.GetLastCommonAncestor(Compiler, c.ValType.TypePath.Value)}."); } - result = new Ternary(ternary.Location, a, b, c); + result = new Ternary(Compiler, ternary.Location, a, b, c); break; case DMASTNewPath newPath: if (BuildExpression(newPath.Path, inferredPath) is not IConstantPath path) { @@ -284,7 +289,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe break; } - result = new NewPath(Compiler, newPath.Location, path, + result = new NewPath(newPath.Location, path, BuildArgumentList(newPath.Location, newPath.Parameters, inferredPath)); break; case DMASTNewExpr newExpr: @@ -305,7 +310,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe break; } - result = new NewPath(Compiler, newInferred.Location, inferredType, + result = new NewPath(newInferred.Location, inferredType, BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath)); break; case DMASTPreIncrement preIncrement: @@ -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 @@ -708,7 +713,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); } } @@ -722,7 +727,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)) { @@ -759,10 +764,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) { @@ -892,7 +897,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: @@ -1028,6 +1033,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) { @@ -1038,16 +1044,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; } @@ -1069,7 +1084,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); @@ -1116,7 +1131,7 @@ private DMExpression BuildList(DMASTList list, DreamPath? inferredPath) { if (list.IsAList) { return new AList(list.Location, values!); } else { - return new List(list.Location, values); + return new List(Compiler, list.Location, values); } } @@ -1223,7 +1238,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 743b7433db..c4cd8ffe56 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -1,3 +1,4 @@ +using System.Linq; using DMCompiler.Bytecode; using DMCompiler.Compiler; using DMCompiler.Compiler.DM; @@ -9,6 +10,11 @@ namespace DMCompiler.DM.Builders; internal sealed class DMProcBuilder(DMCompiler compiler, DMObject dmObject, DMProc proc) { private readonly DMExpressionBuilder _exprBuilder = new(new(compiler, dmObject, proc)); + /// + /// This tracks the current return type. + /// + private DMComplexValueType CurrentReturnType = DMValueType.Null; + private ExpressionContext ExprContext => new(compiler, dmObject, proc); public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { @@ -36,9 +42,45 @@ public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { } ProcessBlockInner(procDefinition.Body, silenceEmptyBlockWarning : true); + // implicit return + CheckBlockReturnRecursive(procDefinition.Body); proc.ResolveLabels(); } + private void CheckBlockReturnRecursive(DMASTProcBlockInner block) { + if (block.Statements.Length <= 0) { + proc.ValidateReturnType(CurrentReturnType, null, block.Location); + return; + } + + var lastStatement = block.Statements[^1]; + switch (lastStatement) { + case DMASTProcStatementIf ifStatement: + CheckBlockReturnRecursive(ifStatement.Body); + if (ifStatement.ElseBody is not null) + CheckBlockReturnRecursive(ifStatement.ElseBody); + else // if statement is non-exhaustive, so check for an implicit return after + proc.ValidateReturnType(CurrentReturnType, null, lastStatement.Location); + break; + case DMASTProcStatementSwitch switchStatement: + if (!switchStatement.Cases.Any()) // No cases to check? + break; + foreach (var switchCase in switchStatement.Cases) { + CheckBlockReturnRecursive(switchCase.Body); + } + + if (!switchStatement.Cases.Any(x => x is DMASTProcStatementSwitch.SwitchCaseDefault)) // non-exhaustive, implicit return + proc.ValidateReturnType(CurrentReturnType, null, switchStatement.Cases.Last().Body.Location); + break; + case DMASTProcStatementReturn: + case DMASTProcStatementExpression expression when expression.Expression is DMASTAssign assign && assign.LHS is DMASTCallableSelf: + break; // already checked elsewhere + default: + proc.ValidateReturnType(CurrentReturnType, null, lastStatement.Location); + break; + } + } + /// The block to process /// Used to avoid emitting noisy warnings about procs with nothing in them.
/// FIXME: Eventually we should try to be smart enough to emit the error anyway for procs that
@@ -104,8 +146,54 @@ private void ProcessStatement(DMASTProcStatement statement) { } } + /// + /// Returns true if the expression has DMASTCallableSelf as the leftmost innermost expression. + /// + /// + /// + public void HandleCallableSelfLeft(DMASTExpression expr, ref DMComplexValueType currentReturnType, DMExpression realExpr, out bool isTemporary, bool checkConditions = false) { + isTemporary = false; + var checkedExpression = expr.GetUnwrapped(); + switch (checkedExpression) { + case DMASTNot: + 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; + } + } + private void ProcessStatementExpression(DMASTProcStatementExpression statement) { - _exprBuilder.Emit(statement.Expression); + _exprBuilder.Emit(statement.Expression, out var expr); + HandleCallableSelfLeft(statement.Expression, ref CurrentReturnType, expr, out var _); proc.Pop(); } @@ -171,10 +259,9 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var if (varDeclaration.Value != null) { value = _exprBuilder.Create(varDeclaration.Value, varDeclaration.Type); - if (!varDeclaration.ValType.MatchesType(compiler, value.ValType)) { + if (!varDeclaration.ValType.Value.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); } @@ -188,7 +275,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var successful = proc.TryAddLocalConstVariable(varDeclaration.Name, varDeclaration.Type, constValue); } else { - successful = proc.TryAddLocalVariable(varDeclaration.Name, varDeclaration.Type, varDeclaration.ValType); + successful = proc.TryAddLocalVariable(varDeclaration.Name, varDeclaration.Type, varDeclaration.ExplicitValType); } if (!successful) { @@ -202,7 +289,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var } private void ProcessStatementReturn(DMASTProcStatementReturn statement) { - if (statement.Value != null) { + if (statement.Value is not null and not DMASTCallableSelf) { var expr = _exprBuilder.Create(statement.Value); // Don't type-check unimplemented procs @@ -216,6 +303,7 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) { expr.EmitPushValue(ExprContext); } else { + proc.ValidateReturnType(CurrentReturnType, null, statement.Location); proc.PushReferenceValue(DMReference.Self); //Default return value } @@ -223,7 +311,10 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) { } private void ProcessStatementIf(DMASTProcStatementIf statement) { - _exprBuilder.Emit(statement.Condition); + _exprBuilder.Emit(statement.Condition, out var expr); + var prevReturnType = CurrentReturnType; + HandleCallableSelfLeft(statement.Condition, ref CurrentReturnType, expr, out var isTemporary, checkConditions: true); + var condReturnType = CurrentReturnType; if (statement.ElseBody == null) { string endLabel = proc.NewLabelName(); @@ -232,6 +323,7 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) { proc.StartScope(); ProcessBlockInner(statement.Body); proc.EndScope(); + CurrentReturnType = condReturnType; proc.AddLabel(endLabel); } else { string elseLabel = proc.NewLabelName(); @@ -242,21 +334,29 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) { proc.StartScope(); ProcessBlockInner(statement.Body); proc.EndScope(); + CurrentReturnType = condReturnType; + if (isTemporary) + CurrentReturnType = prevReturnType; proc.Jump(endLabel); proc.AddLabel(elseLabel); + // else bodies are exhaustive, don't reset returntype + // if it's an elseif that's handled in ProcessBlockInner proc.StartScope(); ProcessBlockInner(statement.ElseBody); proc.EndScope(); proc.AddLabel(endLabel); } + + if (isTemporary) + CurrentReturnType = prevReturnType; } private void ProcessStatementFor(DMASTProcStatementFor statementFor) { proc.StartScope(); { foreach (var decl in FindVarDecls(statementFor.Expression1)) { - ProcessStatementVarDeclaration(new DMASTProcStatementVarDeclaration(statementFor.Location, decl.DeclPath, null, DMValueType.Anything)); + ProcessStatementVarDeclaration(new DMASTProcStatementVarDeclaration(statementFor.Location, decl.DeclPath, null, null)); } if (statementFor is { Expression2: DMASTExpressionIn dmastIn, Expression3: null }) { @@ -287,7 +387,10 @@ private void ProcessStatementFor(DMASTProcStatementFor statementFor) { switch (outputVar) { case 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"); break; @@ -696,7 +799,9 @@ private void ProcessStatementSwitch(DMASTProcStatementSwitch statementSwitch) { List<(string CaseLabel, DMASTProcBlockInner CaseBody)> valueCases = new(); DMASTProcBlockInner? defaultCaseBody = null; - _exprBuilder.Emit(statementSwitch.Value); + _exprBuilder.Emit(statementSwitch.Value, out var expr); + HandleCallableSelfLeft(statementSwitch.Value, ref CurrentReturnType, expr, out var _); + // todo: some sort of return type inference based on cases for conditions switching on . foreach (DMASTProcStatementSwitch.SwitchCase switchCase in statementSwitch.Cases) { if (switchCase is DMASTProcStatementSwitch.SwitchCaseValues switchCaseValues) { string caseLabel = proc.NewLabelName(); @@ -754,26 +859,34 @@ 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); } @@ -864,7 +977,7 @@ private void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) { string endLabel = proc.NewLabelName(); if (tryCatch.CatchParameter is DMASTProcStatementVarDeclaration param) { - if (!proc.TryAddLocalVariable(param.Name, param.Type, param.ValType)) { + if (!proc.TryAddLocalVariable(param.Name, param.Type, param.ExplicitValType)) { compiler.Emit(WarningCode.DuplicateVariable, param.Location, $"Duplicate var {param.Name}"); } diff --git a/DMCompiler/DM/DMExpression.cs b/DMCompiler/DM/DMExpression.cs index dd450d3808..54c74140bf 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..046f823441 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -97,6 +97,10 @@ public bool OwnsProc(string name) { } public DMComplexValueType? GetProcReturnTypes(string name) { + return GetProcReturnTypes(name, null); + } + + public DMComplexValueType? GetProcReturnTypes(string name, ArgumentList? arguments) { if (this == compiler.DMObjectTree.Root && compiler.DMObjectTree.TryGetGlobalProc(name, out var globalProc)) return globalProc.RawReturnTypes; if (GetProcs(name) is not { } procs) @@ -232,14 +236,15 @@ public bool IsSubtypeOf(DreamPath path) { return Parent != null && Parent.IsSubtypeOf(path); } - public DMValueType GetDMValueType() { - if (IsSubtypeOf(DreamPath.Mob)) - return DMValueType.Mob; - if (IsSubtypeOf(DreamPath.Obj)) - return DMValueType.Obj; - if (IsSubtypeOf(DreamPath.Area)) - return DMValueType.Area; + public DreamPath GetLastCommonAncestor(DMObject other) { + if(other.IsSubtypeOf(Path)) { + return Path; + } + + if(IsSubtypeOf(other.Path)) { + return other.Path; + } - return DMValueType.Anything; + return Parent?.GetLastCommonAncestor(other) ?? DreamPath.Root; } } diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index 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 db36427543..2dd1cf91c6 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -163,10 +163,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; } @@ -178,17 +181,19 @@ public void ValidateReturnType(DMExpression expr) { switch (expr) { case ProcCall: - _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}."); + _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}."); break; case Local: - _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\""); + _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\""); + break; + case null: break; default: - _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub."); + _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub."); break; } } else if (!ReturnTypes.MatchesType(_compiler, type)) { // We could determine the return types but they don't match - _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject.Path.ToString()}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}"); + _compiler.Emit(WarningCode.InvalidReturnType, location, $"{_dmObject.Path.ToString()}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}"); } } @@ -208,13 +213,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) }); } } @@ -475,7 +480,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; @@ -513,6 +518,20 @@ public DMReference GetLocalVariableReference(string name) { return local.IsParameter ? DMReference.CreateArgument(local.Id) : DMReference.CreateLocal(local.Id); } + public DMProc GetBaseProc(DMObject? dmObject = null) { + if (dmObject == null) dmObject = _dmObject; + if (dmObject == _compiler.DMObjectTree.Root && _compiler.DMObjectTree.TryGetGlobalProc(Name, out var globalProc)) + return globalProc; + if (dmObject.GetProcs(Name) is not { } procs) + return dmObject.Parent is not null ? GetBaseProc(dmObject.Parent) : this; + + var proc = _compiler.DMObjectTree.AllProcs[procs[0]]; + if ((proc.Attributes & ProcAttributes.IsOverride) != 0) + return dmObject.Parent is not null ? GetBaseProc(dmObject.Parent) : this; + + return proc; + } + public void Error() { WriteOpcode(DreamProcOpcode.Error); } diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index 61f23652aa..5d6cc5c7c9 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -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 } /// @@ -39,9 +40,18 @@ public readonly struct DMComplexValueType { public readonly DreamPath? TypePath; public bool IsAnything => Type == DMValueType.Anything; - public bool IsPath => Type.HasFlag(DMValueType.Path); + public bool IsInstance => Type.HasFlag(DMValueType.Instance); + public bool HasPath => Type.HasFlag(DMValueType.Path) | Type.HasFlag(DMValueType.Instance); public bool IsUnimplemented { get; } public bool 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,19 +59,74 @@ 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, DMListValueTypes? listValueTypes) : this(type, + typePath) { + ListValueTypes = listValueTypes; } public bool MatchesType(DMValueType type) { - return IsAnything || (Type & type) != 0; + if (IsAnything || (Type & type) != 0) return true; + if ((type & (DMValueType.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); @@ -70,12 +135,66 @@ internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) { public override string ToString() { var types = Type.ToString().ToLowerInvariant(); - return $"\"{(IsPath ? types + ", " + TypePath!.Value : types)}\""; + return $"\"{(HasPath ? types + $", {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; + } + + if (type1.TypePath is null) { + return type2 | type1.Type; + } + + // Take the common ancestor of both types + return new(type1.Type | type2.Type, type1.TypePath.Value.GetLastCommonAncestor(compiler, type2.TypePath.Value)); + } + + public DreamPath? AsPath() { + return (HasPath ? TypePath : null) ?? (Type & ~DMValueType.Null) switch { + DMValueType.Mob => DreamPath.Mob, + DMValueType.Icon => DreamPath.Icon, + DMValueType.Obj => DreamPath.Obj, + DMValueType.Turf => DreamPath.Turf, + DMValueType.Area => DreamPath.Area, + DMValueType.Obj | DMValueType.Mob => DreamPath.Movable, + DMValueType.Area | DMValueType.Turf | DMValueType.Obj | DMValueType.Mob => DreamPath.Atom, + DMValueType.Sound => DreamPath.Sound, + _ => null + }; + } +} + +public class DMListValueTypes(DMComplexValueType nestedListKeyType, DMComplexValueType? nestedListValType) { + public DMComplexValueType NestedListKeyType => nestedListKeyType; + public DMComplexValueType? NestedListValType => nestedListValType; + + public static DMListValueTypes MergeListValueTypes(DMCompiler compiler, DMListValueTypes type1, DMListValueTypes type2) { + return new(DMComplexValueType.MergeComplexValueTypes(compiler, type1.NestedListKeyType, type2.NestedListKeyType), (type1.NestedListValType.HasValue && type2.NestedListValType.HasValue) ? DMComplexValueType.MergeComplexValueTypes(compiler, type1.NestedListValType.Value, type2.NestedListValType.Value) : (type1.NestedListValType ?? type2.NestedListValType)); + } + + public static DMComplexValueType GetKeyValType(DMListValueTypes? listValueTypes) { + if (listValueTypes is null) return DMValueType.Anything; + return listValueTypes.NestedListKeyType | DMValueType.Null; + } + + public static DMComplexValueType GetValueValType(DMListValueTypes? listValueTypes) { + if (listValueTypes is null) return DMValueType.Anything; + if (listValueTypes.NestedListValType is null) return listValueTypes.NestedListKeyType; // non-assoc list, keys are also values + return (DMComplexValueType)(listValueTypes.NestedListValType | DMValueType.Null); + } + + public override string ToString() { + if (NestedListValType is not null) + return $"{NestedListKeyType} = {NestedListValType}"; + return NestedListKeyType.ToString(); + } } diff --git a/DMCompiler/DM/DMVariable.cs b/DMCompiler/DM/DMVariable.cs index 131cdc00dc..ac9af704b5 100644 --- a/DMCompiler/DM/DMVariable.cs +++ b/DMCompiler/DM/DMVariable.cs @@ -28,7 +28,8 @@ public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, boo IsFinal = isFinal; IsTmp = isTmp; Value = null; - ValType = valType ?? DMValueType.Anything; + DMComplexValueType atomType = Type is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Path, Type) : DMValueType.Anything; + ValType = valType ?? (!atomType.IsAnything ? atomType | DMValueType.Null : (Type is null ? DMValueType.Anything : new DMComplexValueType(DMValueType.Instance | DMValueType.Path | DMValueType.Null, Type))); } public DMVariable(DMVariable copyFrom) { diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs index 0d9420faeb..d8b8a51de7 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,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x == y internal sealed class Equal(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -332,6 +334,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x != y internal sealed class NotEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -341,6 +345,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x ~= y internal sealed class Equivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -350,6 +356,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x ~! y internal sealed class NotEquivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -359,6 +367,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x > y internal sealed class GreaterThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; @@ -386,6 +396,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x >= y internal sealed class GreaterThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -413,6 +425,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out // x < y internal sealed class LessThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -440,6 +454,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out // x <= y internal sealed class LessThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -467,6 +483,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out // x || y internal sealed class Or(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)) { if (lhs.IsTruthy()) { @@ -496,6 +514,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x && y internal sealed class And(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => RHS.ValType; + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (LHS.TryAsConstant(compiler, out var lhs) && !lhs.IsTruthy()) { constant = lhs; @@ -523,6 +543,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // x in y internal sealed class In(Location location, DMExpression expr, DMExpression container) : BinaryOp(location, expr, container) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: DMValueType.Bool + public override void EmitPushValue(ExpressionContext ctx) { LHS.EmitPushValue(ctx); RHS.EmitPushValue(ctx); @@ -558,6 +580,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 +609,8 @@ protected override void EmitOp(ExpressionContext ctx, DMReference reference, // x += y internal sealed class Append(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { + public override DMComplexValueType ValType => LHS.ValType.Type == DMValueType.Null ? RHS.ValType : LHS.ValType; + protected override void EmitOp(ExpressionContext ctx, DMReference reference, string endLabel) { RHS.EmitPushValue(ctx); diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index c5e8e61347..085afe8573 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -61,7 +61,17 @@ public void EmitPushArglist(ExpressionContext ctx) { internal sealed class New(DMCompiler compiler, Location location, DMExpression expr, ArgumentList arguments) : DMExpression(location) { public override DreamPath? Path => expr.Path; public override bool PathIsFuzzy => Path == null; - public override DMComplexValueType ValType => !expr.ValType.IsAnything ? expr.ValType : (Path?.GetAtomType(compiler) ?? DMValueType.Anything); + + public override DMComplexValueType ValType { + get { + var exprType = expr.ValType; + if (exprType.IsAnything) + return Path is not null ? new DMComplexValueType(DMValueType.Instance, Path) : DMValueType.Anything; + if (exprType.HasPath && exprType.Type.HasFlag(DMValueType.Path)) + return exprType.TypePath is not null ? new DMComplexValueType(DMValueType.Instance, exprType.TypePath) : DMValueType.Anything; + return exprType; + } + } public override void EmitPushValue(ExpressionContext ctx) { var argumentInfo = arguments.EmitArguments(ctx, null); @@ -72,9 +82,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 +113,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 +144,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 +193,8 @@ public override void EmitPushValue(ExpressionContext ctx) { /// rgb(x, y, z, space) /// rgb(x, y, z, a, space) internal sealed class Rgb(Location location, ArgumentList arguments) : DMExpression(location) { + public override DMComplexValueType ValType => DMValueType.Color; + public override void EmitPushValue(ExpressionContext ctx) { ctx.ObjectTree.TryGetGlobalProc("rgb", out var dmProc); var argInfo = arguments.EmitArguments(ctx, dmProc); @@ -193,12 +206,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) { @@ -373,6 +402,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); @@ -384,6 +414,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); @@ -398,9 +429,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; @@ -410,6 +442,16 @@ public List(Location location, (DMExpression? Key, DMExpression Value)[] values) break; } } + + DMComplexValueType keyTypes = DMValueType.Anything; + DMComplexValueType valTypes = DMValueType.Anything; + foreach((DMExpression? key, DMExpression val) in _values) { + valTypes = DMComplexValueType.MergeComplexValueTypes(compiler, valTypes, val.ValType); + if(key is not null) + keyTypes = DMComplexValueType.MergeComplexValueTypes(compiler, keyTypes, key.ValType); + } + + _valType = new DMComplexValueType(DMValueType.Instance, DreamPath.List, !(keyTypes.IsAnything && valTypes.IsAnything) ? (_isAssociative ? new DMListValueTypes(keyTypes, valTypes) : new DMListValueTypes(valTypes, null)) : null); } public override void EmitPushValue(ExpressionContext ctx) { @@ -512,6 +554,8 @@ public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? js // Value of var/list/L[1][2][3] internal sealed class DimensionalList(Location location, DMExpression[] sizes) : DMExpression(location) { + public override DMComplexValueType ValType => DreamPath.List; + public override void EmitPushValue(ExpressionContext ctx) { foreach (var size in sizes) { size.EmitPushValue(ctx); @@ -570,6 +614,16 @@ public override void EmitPushValue(ExpressionContext ctx) { // initial(x) internal class Initial(Location location, DMExpression expr) : DMExpression(location) { + public override DMComplexValueType ValType { + get { + if (Expression is LValue lValue) { + return lValue.ValType; + } + + return DMValueType.Anything; + } + } + protected DMExpression Expression { get; } = expr; public override void EmitPushValue(ExpressionContext ctx) { diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 5b169cce5b..6aea61c1eb 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -100,6 +100,7 @@ internal sealed class Resource : Constant { private readonly string _filePath; private bool _isAmbiguous; + public override DMComplexValueType ValType { get; } public Resource(DMCompiler compiler, Location location, string filePath) : base(location) { // Treat backslashes as forward slashes on Linux @@ -154,6 +155,16 @@ public Resource(DMCompiler compiler, Location location, string filePath) : base( _filePath = _filePath.Replace('\\', '/'); compiler.DMObjectTree.Resources.Add(_filePath); + ValType = System.IO.Path.GetExtension(fileName) switch { + ".dmi" => DMValueType.Icon, + ".png" => DMValueType.Icon, + ".bmp" => DMValueType.Icon, + ".gif" => DMValueType.Icon, + ".ogg" => DMValueType.Sound, + ".wav" => DMValueType.Sound, + ".mid" => DMValueType.Sound, + _ => DMValueType.File + }; } public override void EmitPushValue(ExpressionContext ctx) { @@ -230,7 +241,7 @@ internal class ConstantTypeReference(Location location, DMObject dmObject) : Con public DMObject Value { get; } = dmObject; public override DreamPath? Path => Value.Path; - public override DMComplexValueType ValType => Value.Path; + public override DMComplexValueType ValType => new DMComplexValueType(DMValueType.Path, Path); public override void EmitPushValue(ExpressionContext ctx) { ctx.Proc.PushType(Value.Id); diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs index 122514c524..0fd5c08577 100644 --- a/DMCompiler/DM/Expressions/Dereference.cs +++ b/DMCompiler/DM/Expressions/Dereference.cs @@ -36,6 +36,19 @@ public sealed class IndexOperation : Operation { /// The index expression. (eg. x[expr]) /// public required DMExpression Index { get; init; } + + public DMComplexValueType UnnestValType(DMListValueTypes? listValueTypes) { + if (listValueTypes is null) return DMValueType.Anything; + if (listValueTypes.NestedListValType is null) return listValueTypes.NestedListKeyType | DMValueType.Null; + return Index.ValType.Type switch { + // if Index.ValType is only null, we return null + DMValueType.Null => DMValueType.Null, + // if it's only num, return key + DMValueType.Num => listValueTypes.NestedListKeyType, + // else, return valtype|null + _ => listValueTypes.NestedListValType.Value | DMValueType.Null + }; + } } public sealed class CallOperation : NamedOperation { @@ -48,32 +61,43 @@ public sealed class CallOperation : NamedOperation { public override DreamPath? Path { get; } public override DreamPath? NestedPath { get; } public override bool PathIsFuzzy => Path == null; - public override DMComplexValueType ValType { get; } + + public override DMComplexValueType ValType { + get { + 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 +105,8 @@ private DMComplexValueType DetermineValType(DMObjectTree objectTree) { type = operation switch { FieldOperation fieldOperation => dmObject.GetVariable(fieldOperation.Identifier)?.ValType ?? DMValueType.Anything, - IndexOperation => DMValueType.Anything, // Lists currently can't be typed, this could be anything - CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier) ?? DMValueType.Anything, + IndexOperation indexOperation => indexOperation.UnnestValType(type.ListValueTypes), + CallOperation callOperation => dmObject.GetProcReturnTypes(callOperation.Identifier, callOperation.Parameters) ?? DMValueType.Anything, _ => throw new InvalidOperationException("Unimplemented dereference operation") }; } @@ -130,7 +154,15 @@ private void EmitOperation(ExpressionContext ctx, Operation operation, string en break; case CallOperation callOperation: - var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(ctx, null); + DMProc? targetProc = null; + if (callOperation.Path is not null && ctx.ObjectTree.TryGetDMObject(callOperation.Path.Value, out var dmObj)) { + var procs = dmObj?.GetProcs(callOperation.Identifier); + if (procs is not null && procs.Count > 0) { + targetProc = ctx.ObjectTree.AllProcs[procs[0]]; + } + } + + var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(ctx, targetProc); ctx.Proc.DereferenceCall(callOperation.Identifier, argumentsType, argumentStackSize); break; @@ -320,8 +352,8 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out // expression::identifier // Same as initial(expression?.identifier) except this keeps its type -internal sealed class ScopeReference(DMObjectTree objectTree, Location location, DMExpression expression, string identifier, DMVariable dmVar) - : Initial(location, new Dereference(objectTree, location, dmVar.Type, expression, // Just a little hacky +internal sealed class ScopeReference(DMCompiler compiler, Location location, DMExpression expression, string identifier, DMVariable dmVar) + : Initial(location, new Dereference(compiler.DMObjectTree, location, dmVar.Type, expression, // Just a little hacky [ new Dereference.FieldOperation { Identifier = identifier, @@ -332,6 +364,13 @@ internal sealed class ScopeReference(DMObjectTree objectTree, Location location, ) { public override DreamPath? Path => Expression.Path; + public override DMComplexValueType ValType { + get { + TryAsConstant(compiler, out var constant); + return constant is not null ? constant.ValType : dmVar.ValType; + } + } + public override string GetNameof(ExpressionContext ctx) => dmVar.Name; public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs index 1db9b84bfa..34e1188d95 100644 --- a/DMCompiler/DM/Expressions/LValue.cs +++ b/DMCompiler/DM/Expressions/LValue.cs @@ -90,6 +90,8 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel // world internal sealed class World(Location location) : LValue(location, DreamPath.World) { + public override DMComplexValueType ValType => new DMComplexValueType(DMValueType.Instance, DreamPath.World); + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.World; @@ -99,11 +101,15 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel } // Identifier of local variable -internal sealed class Local(Location location, DMProc.LocalVariable localVar) : LValue(location, localVar.Type) { +internal sealed class Local(Location location, DMProc.LocalVariable localVar, DMComplexValueType? valType) : LValue(location, localVar.Type) { public DMProc.LocalVariable LocalVar { get; } = localVar; - // TODO: non-const local var static typing - public override DMComplexValueType ValType => LocalVar.ExplicitValueType ?? DMValueType.Anything; + public override DMComplexValueType ValType { + get { + if (valType is not null && !valType.Value.IsAnything) return valType.Value; + return LocalVar.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..75c50c5eb1 100644 --- a/DMCompiler/DM/Expressions/Procs.cs +++ b/DMCompiler/DM/Expressions/Procs.cs @@ -5,7 +5,11 @@ namespace DMCompiler.DM.Expressions; // x() (only the identifier) -internal sealed class Proc(Location location, string identifier) : DMExpression(location) { +internal sealed class Proc(Location location, string identifier, DMObject theObject) : DMExpression(location) { + public DMObject DMObject => theObject; + public string Identifier => identifier; + public override DMComplexValueType ValType => GetReturnType(DMObject); + public override void EmitPushValue(ExpressionContext ctx) { ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to use proc as value"); ctx.Proc.Error(); @@ -57,8 +61,8 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel /// .
/// This is an LValue _and_ a proc! /// -internal sealed class ProcSelf(Location location, DMComplexValueType valType) : LValue(location, null) { - public override DMComplexValueType ValType => valType; +internal sealed class ProcSelf(Location location, DMComplexValueType? valType) : LValue(location, null) { + public override DMComplexValueType ValType => valType ?? DMValueType.Anything; public override DMReference EmitReference(ExpressionContext ctx, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { @@ -88,10 +92,26 @@ public override DMReference EmitReference(ExpressionContext ctx, string endLabel } // x(y, z, ...) -internal sealed class ProcCall(Location location, DMExpression target, ArgumentList arguments, DMComplexValueType valType) +internal sealed class ProcCall(DMCompiler compiler, Location location, DMExpression target, ArgumentList arguments, DMComplexValueType valType) : DMExpression(location) { public override bool PathIsFuzzy => Path == null; - public override DMComplexValueType ValType => valType.IsAnything ? target.ValType : valType; + + public override DMComplexValueType ValType { + get { + if (!valType.IsAnything) + return valType; + switch (target) { + case Proc procTarget: + return procTarget.DMObject.GetProcReturnTypes(procTarget.Identifier, arguments) ?? DMValueType.Anything; + case GlobalProc procTarget: + if(compiler.DMObjectTree.TryGetGlobalProc(procTarget.Proc.Name, out var globalProc)) + return globalProc.ReturnTypes; + return DMValueType.Anything; + } + + return target.ValType; + } + } public (DMObject? ProcOwner, DMProc? Proc) GetTargetProc(DMCompiler compiler, DMObject dmObject) { return target switch { diff --git a/DMCompiler/DM/Expressions/Ternary.cs b/DMCompiler/DM/Expressions/Ternary.cs index 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..7f9e6e0d3e 100644 --- a/DMCompiler/DM/Expressions/Unary.cs +++ b/DMCompiler/DM/Expressions/Unary.cs @@ -4,11 +4,13 @@ namespace DMCompiler.DM.Expressions; internal abstract class UnaryOp(Location location, DMExpression expr) : DMExpression(location) { - protected DMExpression Expr { get; } = expr; + public DMExpression Expr { get; } = expr; } // -x internal sealed class Negate(Location location, DMExpression expr) : UnaryOp(location, expr) { + public override DMComplexValueType ValType => Expr.ValType; // could be a num, could be a matrix, you never know + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number number) return false; @@ -25,6 +27,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // !x internal sealed class Not(Location location, DMExpression expr) : UnaryOp(location, expr) { + public override DMComplexValueType ValType => DMValueType.Num; // todo: could eventually be updated to be a bool + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (!Expr.TryAsConstant(compiler, out constant)) return false; @@ -40,6 +44,8 @@ public override void EmitPushValue(ExpressionContext ctx) { // ~x internal sealed class BinaryNot(Location location, DMExpression expr) : UnaryOp(location, expr) { + public override DMComplexValueType ValType => DMValueType.Num; + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number constantNum) return false; diff --git a/DMCompiler/DMStandard/Types/Atoms/Mob.dm b/DMCompiler/DMStandard/Types/Atoms/Mob.dm index d8550a7728..6433e62340 100644 --- a/DMCompiler/DMStandard/Types/Atoms/Mob.dm +++ b/DMCompiler/DMStandard/Types/Atoms/Mob.dm @@ -1,16 +1,16 @@ /mob parent_type = /atom/movable - var/client/client + var/client/client as /client|null var/key as text|null var/tmp/ckey as text|null var/tmp/list/group as opendream_unimplemented - var/see_invisible = 0 - var/see_infrared = 0 as opendream_unimplemented - var/sight = 0 - var/see_in_dark = 2 as opendream_unimplemented + var/see_invisible = 0 as num + var/see_infrared = 0 as num|opendream_unimplemented + var/sight = 0 as num + var/see_in_dark = 2 as num|opendream_unimplemented density = TRUE layer = MOB_LAYER diff --git a/DMCompiler/DMStandard/Types/Atoms/Movable.dm b/DMCompiler/DMStandard/Types/Atoms/Movable.dm index 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 2168de66b1..313f9bf2a4 100644 --- a/DMCompiler/DMStandard/Types/Atoms/_Atom.dm +++ b/DMCompiler/DMStandard/Types/Atoms/_Atom.dm @@ -1,10 +1,10 @@ /atom parent_type = /datum - var/name = null - var/text = null - var/desc = null - var/suffix = null as opendream_unimplemented + var/name = null as text|null + var/text = null as text|null + var/desc = null as text|null + var/suffix = null as text|null|opendream_unimplemented // The initialization/usage of these lists is handled internally by the runtime var/tmp/list/verbs = null @@ -14,35 +14,38 @@ var/tmp/list/vis_locs = null as opendream_unimplemented var/list/vis_contents = null - var/tmp/atom/loc - var/dir = SOUTH - var/tmp/x = 0 - var/tmp/y = 0 - var/tmp/z = 0 - var/pixel_x = 0 - var/pixel_y = 0 - var/pixel_z = 0 - var/pixel_w = 0 + var/tmp/atom/loc as /atom|null + var/dir = SOUTH as num + var/tmp/x = 0 as num + var/tmp/y = 0 as num + var/tmp/z = 0 as num + var/pixel_x = 0 as num + var/pixel_y = 0 as num + var/pixel_z = 0 as num + var/pixel_w = 0 as num - var/icon_w = 0 as opendream_unimplemented - var/icon_z = 0 as opendream_unimplemented - - var/icon = null - var/icon_state = "" - var/layer = 2.0 - var/plane = 0 - var/alpha = 255 - var/color = "#FFFFFF" - var/invisibility = 0 - var/mouse_opacity = 1 - var/infra_luminosity = 0 as opendream_unimplemented - var/luminosity = 0 as opendream_unimplemented - var/opacity = 0 + var/icon_w = 0 as num|opendream_unimplemented + var/icon_z = 0 as num|opendream_unimplemented + + var/icon = null as icon|null + var/icon_state = "" as text|null + var/layer = 2.0 as num + var/plane = 0 as num + var/alpha = 255 as num + // currently we coerce text (hex string) and list(num) (color matrix) to the color type + // in the future this should probably be colorstring|colormatrix|null or something + // or 'as color' could be a shorthand for that + var/color = "#FFFFFF" as color|null + var/invisibility = 0 as num + var/mouse_opacity = 1 as num + var/infra_luminosity = 0 as num|opendream_unimplemented + var/luminosity = 0 as num|opendream_unimplemented + var/opacity = 0 as num var/matrix/transform - var/blend_mode = 0 + var/blend_mode = 0 as num var/gender = NEUTER - var/density = FALSE + var/density = FALSE as num var/maptext = null @@ -104,4 +107,4 @@ proc/Stat() New(loc) - ..() + ..() \ No newline at end of file diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm index 907f62339d..55be0c6be5 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 45fa285f8c..8f209a2b3c 100644 --- a/DMCompiler/DMStandard/Types/List.dm +++ b/DMCompiler/DMStandard/Types/List.dm @@ -6,13 +6,13 @@ proc/New(Size) - proc/Add(Item1) - proc/Copy(Start = 1, End = 0) - proc/Cut(Start = 1, End = 0) - proc/Find(Elem, Start = 1, End = 0) - proc/Insert(Index, Item1) + proc/Add(Item1) as num + proc/Copy(Start = 1, End = 0) as /list + proc/Cut(Start = 1, End = 0) as num + proc/Find(Elem, Start = 1, End = 0) as num + proc/Insert(Index, Item1) as num proc/Join(Glue as text|null, Start = 1 as num, End = 0 as num) as text proc/Remove(Item1) proc/RemoveAll(Item1) - proc/Swap(Index1, Index2) + proc/Swap(Index1, Index2) as null proc/Splice(Start = 1 as num, End = 0 as num, Item1, ...) as null diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index 333d3eec9e..017e17861c 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 @@ -119,4 +119,4 @@ proc/ODHotReloadInterface() - proc/ODHotReloadResource(var/file_name) + proc/ODHotReloadResource(var/file_name) \ No newline at end of file diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 0800abed25..8e614f293c 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 @@ -58,7 +60,7 @@ proc/json_encode(Value, flags) proc/length_char(E) as num proc/lerp(A, B, factor) proc/list2params(List) as text -proc/lowertext(T as text) as text +proc/lowertext(T as text|null) as text proc/max(A) as num|text|null proc/md5(T) as text|null proc/min(A) as num|text|null @@ -66,26 +68,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) @@ -93,19 +95,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 @@ -152,27 +154,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") @@ -184,12 +186,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) @@ -200,14 +202,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 f0ae5744b8..48a052048c 100644 --- a/DMCompiler/DreamPath.cs +++ b/DMCompiler/DreamPath.cs @@ -107,6 +107,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; } @@ -257,4 +265,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); From 5e515b8abb1df22382d3e6b49b0bf6d129ab7b8f Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 05:54:33 -0500 Subject: [PATCH 02/12] fixes --- Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm | 1 + DMCompiler/DM/DMProc.cs | 2 +- DMCompiler/DMStandard/_Standard.dm | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm b/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm index 46c609064c..c1de60bd32 100644 --- a/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm +++ b/Content.Tests/DMProject/Tests/Typemaker/arg_implicit_null2.dm @@ -4,3 +4,4 @@ /proc/RunTest() return +#pragma ImplicitNullType notice diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 2dd1cf91c6..e05b40d783 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -167,7 +167,7 @@ public void ValidateReturnType(DMExpression expr) { } public void ValidateReturnType(DMComplexValueType type, DMExpression? expr, Location location) { - var returnTypes = _dmObject.GetProcReturnTypes(Name)!.Value; + var returnTypes = _dmObject.GetProcReturnTypes(Name) ?? DMValueType.Anything; if ((returnTypes.Type & (DMValueType.Color | DMValueType.File | DMValueType.Message)) != 0) { _compiler.Emit(WarningCode.UnsupportedTypeCheck, Location, "color, message, and file return types are currently unsupported."); return; diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 8e614f293c..568a53794c 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -31,8 +31,8 @@ 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 num) +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 From 8696a0031d754a71573ce62e2b657cdd398aec11 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 06:02:55 -0500 Subject: [PATCH 03/12] fix --- DMCompiler/DM/Expressions/Binary.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs index d8b8a51de7..72086037d7 100644 --- a/DMCompiler/DM/Expressions/Binary.cs +++ b/DMCompiler/DM/Expressions/Binary.cs @@ -483,8 +483,6 @@ public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out // x || y internal sealed class Or(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)) { if (lhs.IsTruthy()) { From 817443ee46a796d09f2e990cab0a414031b2b3d1 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 06:10:24 -0500 Subject: [PATCH 04/12] fix + astype() support --- DMCompiler/DM/Expressions/Builtins.cs | 2 ++ DMCompiler/DMStandard/Types/Atoms/Movable.dm | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index 085afe8573..982435a395 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -323,6 +323,7 @@ public override void EmitPushValue(ExpressionContext ctx) { // astype(x, y) internal sealed class AsType(Location location, DMExpression expr, DMExpression path) : DMExpression(location) { public override DreamPath? Path => path.Path; + public override DMComplexValueType ValType => new(DMValueType.Instance | DMValueType.Null, Path); public override bool PathIsFuzzy => path is not ConstantTypeReference; public override void EmitPushValue(ExpressionContext ctx) { @@ -335,6 +336,7 @@ public override void EmitPushValue(ExpressionContext ctx) { // astype(x) internal sealed class AsTypeInferred(Location location, DMExpression expr, DreamPath path) : DMExpression(location) { public override DreamPath? Path => path; + public override DMComplexValueType ValType => new(DMValueType.Instance | DMValueType.Null, Path); public override void EmitPushValue(ExpressionContext ctx) { if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) { diff --git a/DMCompiler/DMStandard/Types/Atoms/Movable.dm b/DMCompiler/DMStandard/Types/Atoms/Movable.dm index 5f7ff02ece..9c81b37664 100644 --- a/DMCompiler/DMStandard/Types/Atoms/Movable.dm +++ b/DMCompiler/DMStandard/Types/Atoms/Movable.dm @@ -17,7 +17,7 @@ proc/Bump(atom/Obstacle) - proc/Move(atom/NewLoc, Dir=0 as num) as num + proc/Move(atom/NewLoc, Dir=0 as num) as num|null // NOTE: BYOND doesn't return null, but it's common for SS13 proc overrides to if (isnull(NewLoc) || loc == NewLoc) return FALSE From 97df0f171cc9ed3d115eba929dd67915bc9c4808 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 06:35:22 -0500 Subject: [PATCH 05/12] tweak messages --- DMCompiler/DM/Builders/DMProcBuilder.cs | 2 +- DMCompiler/DM/DMCodeTree.Vars.cs | 2 +- DMCompiler/DM/DMValueType.cs | 2 +- DMCompiler/DM/Expressions/Binary.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index c4cd8ffe56..54b121ef3a 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -261,7 +261,7 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var if (!varDeclaration.ValType.Value.MatchesType(compiler, value.ValType) && (!value.ValType.IsAnything || !compiler.Settings.SkipAnythingTypecheck)) compiler.Emit(WarningCode.InvalidVarType, varDeclaration.Location, - $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected {varDeclaration.ValType}"); + $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected one of {varDeclaration.ValType}"); } else { value = new Null(varDeclaration.Location); } diff --git a/DMCompiler/DM/DMCodeTree.Vars.cs b/DMCompiler/DM/DMCodeTree.Vars.cs index bdb30ec3b9..6357296beb 100644 --- a/DMCompiler/DM/DMCodeTree.Vars.cs +++ b/DMCompiler/DM/DMCodeTree.Vars.cs @@ -37,7 +37,7 @@ protected void SetVariableValue(DMCompiler compiler, DMObject dmObject, DMVariab compiler.Emit(WarningCode.ImplicitNullType, value.Location, $"{dmObject.Path}.{variable.Name}: Variable is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable."); variable.ValType |= DMValueType.Null; } else { - compiler.Emit(WarningCode.InvalidVarType, value.Location, $"{dmObject.Path}.{variable.Name}: Invalid var value type {value.ValType}, expected {variable.ValType}"); + compiler.Emit(WarningCode.InvalidVarType, value.Location, $"{dmObject.Path}.{variable.Name}: Invalid var value type {value.ValType}, expected one of {variable.ValType}"); } } diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index 5d6cc5c7c9..80f05fac72 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -135,7 +135,7 @@ internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) { public override string ToString() { var types = Type.ToString().ToLowerInvariant(); - return $"\"{(HasPath ? types + $", {TypePath!.Value}{((IsList && ListValueTypes is not null) ? $"({ListValueTypes})" : "")}" : types)}\""; + return $"\"{(HasPath ? types + $" of type {TypePath!.Value}{((IsList && ListValueTypes is not null) ? $"({ListValueTypes})" : "")}" : types)}\"".Replace(", ", "|"); } public static implicit operator DMComplexValueType(DMValueType type) => new(type, null); diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs index 72086037d7..93f2f0c531 100644 --- a/DMCompiler/DM/Expressions/Binary.cs +++ b/DMCompiler/DM/Expressions/Binary.cs @@ -589,7 +589,7 @@ protected override void EmitOp(ExpressionContext ctx, DMReference reference, str return; ctx.Compiler.Emit(WarningCode.InvalidVarType, Location, - $"Invalid var type {RHS.ValType}, expected {LHS.ValType}"); + $"Invalid var type {RHS.ValType}, expected one of {LHS.ValType}"); } } } From ee138ead9a917f31535504a151aa77c91e5a4a8a Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 06:51:03 -0500 Subject: [PATCH 06/12] Emit `ImplicitNullType` for args --- DMCompiler/DM/DMExpression.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/DMCompiler/DM/DMExpression.cs b/DMCompiler/DM/DMExpression.cs index 54c74140bf..e9a9e60389 100644 --- a/DMCompiler/DM/DMExpression.cs +++ b/DMCompiler/DM/DMExpression.cs @@ -131,8 +131,13 @@ private void VerifyArgType(DMCompiler compiler, DMProc targetProc, int index, st DMComplexValueType paramType = param.ExplicitValueType ?? DMValueType.Anything; 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}"); + if (expr is Null) { + compiler.Emit(WarningCode.ImplicitNullType, expr.Location, $"{targetProc.Name}(...) argument \"{param.Name}\": Argument is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable."); + param.ExplicitValueType |= DMValueType.Null; + } else { + compiler.Emit(WarningCode.InvalidVarType, expr.Location, + $"{targetProc.Name}(...) argument \"{param.Name}\": Invalid var value type {expr.ValType}, expected {paramType}"); + } } } } From 8112c6d5baafe41e598280c9d313a45e3d160659 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 07:38:12 -0500 Subject: [PATCH 07/12] more formatting fixes --- DMCompiler/Compiler/DM/DMParser.cs | 1 - DMCompiler/DM/Builders/DMProcBuilder.cs | 58 ++++++++++++------------ DMCompiler/DM/DMValueType.cs | 2 +- DMCompiler/DM/Expressions/Builtins.cs | 2 + DMCompiler/DM/Expressions/Dereference.cs | 4 +- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index e9a1a89a46..d7392ab9d0 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2821,7 +2821,6 @@ private void BracketWhitespace() { outListTypes = DMListValueTypes.MergeListValueTypes(compiler, outListTypes, listTypes); } } - } while (Check(TokenType.DM_Bar)); return new(type, path, outListTypes); diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index 54b121ef3a..ab428ae7e1 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -13,7 +13,7 @@ internal sealed class DMProcBuilder(DMCompiler compiler, DMObject dmObject, DMPr /// /// This tracks the current return type. /// - private DMComplexValueType CurrentReturnType = DMValueType.Null; + private DMComplexValueType _currentReturnType = DMValueType.Null; private ExpressionContext ExprContext => new(compiler, dmObject, proc); @@ -49,7 +49,7 @@ public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { private void CheckBlockReturnRecursive(DMASTProcBlockInner block) { if (block.Statements.Length <= 0) { - proc.ValidateReturnType(CurrentReturnType, null, block.Location); + proc.ValidateReturnType(_currentReturnType, null, block.Location); return; } @@ -60,7 +60,7 @@ private void CheckBlockReturnRecursive(DMASTProcBlockInner block) { 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); + proc.ValidateReturnType(_currentReturnType, null, lastStatement.Location); break; case DMASTProcStatementSwitch switchStatement: if (!switchStatement.Cases.Any()) // No cases to check? @@ -70,13 +70,13 @@ private void CheckBlockReturnRecursive(DMASTProcBlockInner block) { } if (!switchStatement.Cases.Any(x => x is DMASTProcStatementSwitch.SwitchCaseDefault)) // non-exhaustive, implicit return - proc.ValidateReturnType(CurrentReturnType, null, switchStatement.Cases.Last().Body.Location); + 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: + case DMASTProcStatementExpression { Expression: DMASTAssign { LHS: DMASTCallableSelf } }: break; // already checked elsewhere default: - proc.ValidateReturnType(CurrentReturnType, null, lastStatement.Location); + proc.ValidateReturnType(_currentReturnType, null, lastStatement.Location); break; } } @@ -149,14 +149,13 @@ private void ProcessStatement(DMASTProcStatement statement) { /// /// Returns true if the expression has DMASTCallableSelf as the leftmost innermost expression. /// - /// /// public void HandleCallableSelfLeft(DMASTExpression expr, ref DMComplexValueType currentReturnType, DMExpression realExpr, out bool isTemporary, bool checkConditions = false) { isTemporary = false; var checkedExpression = expr.GetUnwrapped(); switch (checkedExpression) { - case DMASTNot: - DMASTUnary astUnary = (DMASTUnary)checkedExpression; + case DMASTNot not: + DMASTUnary astUnary = not; UnaryOp unaryExpr = (UnaryOp)realExpr; HandleCallableSelfLeft(astUnary.Value.GetUnwrapped(), ref currentReturnType, unaryExpr.Expr, out var _); break; @@ -180,8 +179,8 @@ public void HandleCallableSelfLeft(DMASTExpression expr, ref DMComplexValueType if (astAssignment.LHS.GetUnwrapped() is DMASTCallableSelf) currentReturnType = assignExpr.RHS.ValType; break; - case DMASTLogicalOrAssign: - DMASTBinary astBinary = (DMASTBinary)checkedExpression; + case DMASTLogicalOrAssign assign: + DMASTBinary astBinary = assign; if (astBinary.LHS.GetUnwrapped() is not DMASTCallableSelf) break; if (currentReturnType.Type != DMValueType.Null) @@ -193,7 +192,7 @@ public void HandleCallableSelfLeft(DMASTExpression expr, ref DMComplexValueType private void ProcessStatementExpression(DMASTProcStatementExpression statement) { _exprBuilder.Emit(statement.Expression, out var expr); - HandleCallableSelfLeft(statement.Expression, ref CurrentReturnType, expr, out var _); + HandleCallableSelfLeft(statement.Expression, ref _currentReturnType, expr, out var _); proc.Pop(); } @@ -259,7 +258,8 @@ private void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration var if (varDeclaration.Value != null) { value = _exprBuilder.Create(varDeclaration.Value, varDeclaration.Type); - if (!varDeclaration.ValType.Value.MatchesType(compiler, value.ValType) && (!value.ValType.IsAnything || !compiler.Settings.SkipAnythingTypecheck)) + var varValType = varDeclaration.ValType ?? DMValueType.Anything; + if (!varValType.MatchesType(compiler, value.ValType) && (!value.ValType.IsAnything || !compiler.Settings.SkipAnythingTypecheck)) compiler.Emit(WarningCode.InvalidVarType, varDeclaration.Location, $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected one of {varDeclaration.ValType}"); } else { @@ -303,7 +303,7 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) { expr.EmitPushValue(ExprContext); } else { - proc.ValidateReturnType(CurrentReturnType, null, statement.Location); + proc.ValidateReturnType(_currentReturnType, null, statement.Location); proc.PushReferenceValue(DMReference.Self); //Default return value } @@ -312,9 +312,9 @@ private void ProcessStatementReturn(DMASTProcStatementReturn statement) { private void ProcessStatementIf(DMASTProcStatementIf statement) { _exprBuilder.Emit(statement.Condition, out var expr); - var prevReturnType = CurrentReturnType; - HandleCallableSelfLeft(statement.Condition, ref CurrentReturnType, expr, out var isTemporary, checkConditions: true); - var condReturnType = CurrentReturnType; + 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(); @@ -323,7 +323,7 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) { proc.StartScope(); ProcessBlockInner(statement.Body); proc.EndScope(); - CurrentReturnType = condReturnType; + _currentReturnType = condReturnType; proc.AddLabel(endLabel); } else { string elseLabel = proc.NewLabelName(); @@ -334,9 +334,9 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) { proc.StartScope(); ProcessBlockInner(statement.Body); proc.EndScope(); - CurrentReturnType = condReturnType; + _currentReturnType = condReturnType; if (isTemporary) - CurrentReturnType = prevReturnType; + _currentReturnType = prevReturnType; proc.Jump(endLabel); proc.AddLabel(elseLabel); @@ -349,7 +349,7 @@ private void ProcessStatementIf(DMASTProcStatementIf statement) { } if (isTemporary) - CurrentReturnType = prevReturnType; + _currentReturnType = prevReturnType; } private void ProcessStatementFor(DMASTProcStatementFor statementFor) { @@ -389,7 +389,7 @@ private void ProcessStatementFor(DMASTProcStatementFor statementFor) { case Local outputLocal: { if (statementFor.DMTypes is not null) outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes; - else if (list.ValType.IsList && list.ValType.ListValueTypes is not null) + else if (list.ValType is { IsList: true, ListValueTypes: not null }) outputLocal.LocalVar.ExplicitValueType = list.ValType.ListValueTypes.NestedListKeyType; if(outputLocal.LocalVar is DMProc.LocalConstVariable) compiler.Emit(WarningCode.WriteToConstant, outputExpr.Location, "Cannot change constant value"); @@ -800,7 +800,7 @@ private void ProcessStatementSwitch(DMASTProcStatementSwitch statementSwitch) { DMASTProcBlockInner? defaultCaseBody = null; _exprBuilder.Emit(statementSwitch.Value, out var expr); - HandleCallableSelfLeft(statementSwitch.Value, ref CurrentReturnType, expr, out var _); + 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) { @@ -859,7 +859,7 @@ Constant CoerceBound(Constant bound) { proc.Pop(); - DMComplexValueType oldReturnType = CurrentReturnType; + DMComplexValueType oldReturnType = _currentReturnType; DMComplexValueType? defaultCaseReturnType = null; if (defaultCaseBody != null) { // if a default case exists, the switch is exhaustive so we go with its returntype @@ -868,25 +868,25 @@ Constant CoerceBound(Constant bound) { ProcessBlockInner(defaultCaseBody); } proc.EndScope(); - defaultCaseReturnType = CurrentReturnType; - CurrentReturnType = oldReturnType; // the default case hasn't taken effect for the rest of the cases + 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; + oldReturnType = _currentReturnType; proc.StartScope(); { ProcessBlockInner(valueCase.CaseBody); } proc.EndScope(); - CurrentReturnType = oldReturnType; + _currentReturnType = oldReturnType; proc.Jump(endLabel); } - CurrentReturnType = defaultCaseReturnType ?? oldReturnType; + _currentReturnType = defaultCaseReturnType ?? oldReturnType; proc.AddLabel(endLabel); } diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index 80f05fac72..ef852fea28 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -98,7 +98,7 @@ internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) { 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) + if (Type.HasFlag(DMValueType.Color) && type is { IsList: true, ListValueTypes: { NestedListKeyType.Type: DMValueType.Num, NestedListValType: null } }) return true; if (type.IsInstance && MatchesType(type.TypePath!.Value.GetAtomType(compiler))) { return true; diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index 982435a395..0a655deedd 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -220,10 +220,12 @@ public override DMComplexValueType ValType { 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; } } diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs index 0fd5c08577..af199703a8 100644 --- a/DMCompiler/DM/Expressions/Dereference.cs +++ b/DMCompiler/DM/Expressions/Dereference.cs @@ -64,7 +64,7 @@ public sealed class CallOperation : NamedOperation { public override DMComplexValueType ValType { get { - if (_valType is null) _valType = DetermineValType(_objectTree); + _valType ??= DetermineValType(_objectTree); return _valType.Value; } } @@ -367,7 +367,7 @@ internal sealed class ScopeReference(DMCompiler compiler, Location location, DME public override DMComplexValueType ValType { get { TryAsConstant(compiler, out var constant); - return constant is not null ? constant.ValType : dmVar.ValType; + return constant?.ValType ?? dmVar.ValType; } } From 4b9deac473b1ffbe5bc35a017b5dca35b64d3030 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 07:54:56 -0500 Subject: [PATCH 08/12] fix `var/icon/i` vars not matching `as icon` --- DMCompiler/DM/DMValueType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index ef852fea28..caa4b1df06 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -70,6 +70,7 @@ public DMComplexValueType(DMValueType type, DreamPath? typePath, DMListValueType public bool MatchesType(DMValueType type) { if (IsAnything || (Type & type) != 0) return true; + if((type & DMValueType.Icon) != 0 && TypePath == DreamPath.Icon) return true; // var/icon/foo is not explicitly "as icon" if ((type & (DMValueType.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; From baaa908790c3c1d906f28a5c1f111a03adf3b800 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 08:01:15 -0500 Subject: [PATCH 09/12] fix sound handling --- DMCompiler/DM/DMValueType.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index caa4b1df06..d188226009 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -71,6 +71,7 @@ public DMComplexValueType(DMValueType type, DreamPath? typePath, DMListValueType public bool MatchesType(DMValueType type) { if (IsAnything || (Type & type) != 0) return true; if((type & DMValueType.Icon) != 0 && TypePath == DreamPath.Icon) return true; // var/icon/foo is not explicitly "as icon" + if((type & DMValueType.Sound) != 0 && TypePath == DreamPath.Sound) return true; // var/sound/foo is not explicitly "as sound" if ((type & (DMValueType.Text | DMValueType.Message)) != 0 && (Type & (DMValueType.Text | DMValueType.Message)) != 0) return true; if ((type & (DMValueType.Text | DMValueType.Color)) != 0 && (Type & (DMValueType.Text | DMValueType.Color)) != 0) return true; return false; From 72f18c5893ce04aab0d9e84a13af38c8e8275360 Mon Sep 17 00:00:00 2001 From: ike709 Date: Mon, 16 Jun 2025 08:39:18 -0500 Subject: [PATCH 10/12] fix explicit anything on local vars --- DMCompiler/DM/Expressions/LValue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs index 34e1188d95..3209ff3e2e 100644 --- a/DMCompiler/DM/Expressions/LValue.cs +++ b/DMCompiler/DM/Expressions/LValue.cs @@ -107,7 +107,7 @@ internal sealed class Local(Location location, DMProc.LocalVariable localVar, DM public override DMComplexValueType ValType { get { 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; + return LocalVar.ExplicitValueType ?? (LocalVar.Type is not null ? new DMComplexValueType(DMValueType.Instance | DMValueType.Path | DMValueType.Null, LocalVar.Type) : DMValueType.Anything); } } From a02b780a18b2281c04749387200c390741a1e582 Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Sat, 21 Jun 2025 22:23:58 -0400 Subject: [PATCH 11/12] Missing comma --- DMCompiler/DM/DMValueType.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index 01c70b9b54..ce80bc5711 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -29,8 +29,8 @@ public enum DMValueType { //Byond here be dragons Unimplemented = 0x8000, // Marks that a method or property is not implemented. Throws a compiler warning if accessed. CompiletimeReadonly = 0x10000, // Marks that a property can only ever be read from, never written to. This is a const-ier version of const, for certain standard values like list.type - NoConstFold = 0x20000 // Marks that a const var cannot be const-folded during compile - Unsupported = 0x40000, // Marks that a method or property will not be not implemented. Throws a compiler warning if accessed. + NoConstFold = 0x20000, // Marks that a const var cannot be const-folded during compile + Unsupported = 0x40000 // Marks that a method or property will not be not implemented. Throws a compiler warning if accessed. } /// From e05cddf62e48e39bf31275a061e6303d32d17302 Mon Sep 17 00:00:00 2001 From: ike709 Date: Sun, 6 Jul 2025 20:42:05 -0500 Subject: [PATCH 12/12] nuke this --- DMCompiler/DMStandard/_Standard.dm | 2 -- 1 file changed, 2 deletions(-) diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 00894e29d9..ecff06649e 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -1,5 +1,3 @@ -/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