diff --git a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/basic.dm b/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/basic.dm deleted file mode 100644 index 730d6da6a9..0000000000 --- a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/basic.dm +++ /dev/null @@ -1,11 +0,0 @@ - -//# issue 473 - -/obj - var/a = 5 - -/proc/RunTest() - var/obj/o1 = new /obj - var/obj/o2 = new /obj{a=6} - ASSERT(o1.a == 5) - ASSERT(o2.a == 6) \ No newline at end of file diff --git a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/multi.dm b/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/multi.dm deleted file mode 100644 index 3e82194030..0000000000 --- a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/multi.dm +++ /dev/null @@ -1,14 +0,0 @@ - -//# issue 473 - -/obj - var/a = 5 - var/b = 7 - -/proc/RunTest() - var/obj/o1 = new /obj - var/obj/o2 = new /obj{a=6;b=8} - ASSERT(o1.a == 5) - ASSERT(o1.b == 7) - ASSERT(o2.a == 6) - ASSERT(o2.b == 8) \ No newline at end of file diff --git a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/space_before_brace.dm b/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/space_before_brace.dm deleted file mode 100644 index 596ff4dc25..0000000000 --- a/Content.Tests/DMProject/BrokenTests/Expression/ModifiedType/space_before_brace.dm +++ /dev/null @@ -1,9 +0,0 @@ - -//# issue 655 - -/obj - var/a = 5 - -/proc/RunTest() - var/obj/o = new /obj {a=7} - ASSERT(o.a == 7) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Expression/ModifiedType/basic.dm b/Content.Tests/DMProject/Tests/Expression/ModifiedType/basic.dm new file mode 100644 index 0000000000..62522ab35e --- /dev/null +++ b/Content.Tests/DMProject/Tests/Expression/ModifiedType/basic.dm @@ -0,0 +1,11 @@ + +//# issue 473 + +/datum + var/a = 5 + +/proc/RunTest() + var/datum/D1 = new /datum + var/datum/D2 = new /datum{a=6} + ASSERT(D1.a == 5) + ASSERT(D2.a == 6) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Expression/ModifiedType/multi.dm b/Content.Tests/DMProject/Tests/Expression/ModifiedType/multi.dm new file mode 100644 index 0000000000..2f25ee425b --- /dev/null +++ b/Content.Tests/DMProject/Tests/Expression/ModifiedType/multi.dm @@ -0,0 +1,14 @@ + +//# issue 473 + +/datum + var/a = 5 + var/b = 7 + +/proc/RunTest() + var/datum/D1 = new /datum + var/datum/D2 = new /datum{a=6;b=8} + ASSERT(D1.a == 5) + ASSERT(D1.b == 7) + ASSERT(D2.a == 6) + ASSERT(D2.b == 8) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/Expression/ModifiedType/space_before_brace.dm b/Content.Tests/DMProject/Tests/Expression/ModifiedType/space_before_brace.dm new file mode 100644 index 0000000000..3d00025825 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Expression/ModifiedType/space_before_brace.dm @@ -0,0 +1,9 @@ + +//# issue 655 + +/datum + var/a = 5 + +/proc/RunTest() + var/datum/D = new /datum {a=7} + ASSERT(D.a == 7) \ No newline at end of file diff --git a/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs b/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs index 44f6e60fe3..4e37bc5b7f 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs @@ -146,6 +146,12 @@ public sealed class DMASTNewPath(Location location, DMASTConstantPath path, DMAS public readonly DMASTCallParameter[]? Parameters = parameters; } +public sealed class DMASTNewModifiedType(Location location, DMASTModifiedType type, DMASTCallParameter[]? parameters) + : DMASTExpression(location) { + public readonly DMASTModifiedType Type = type; + public readonly DMASTCallParameter[]? Parameters = parameters; +} + public sealed class DMASTNewExpr(Location location, DMASTExpression expression, DMASTCallParameter[]? parameters) : DMASTExpression(location) { public readonly DMASTExpression Expression = expression; diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionConstant.cs b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionConstant.cs index 81a79fbd98..28c9edd0da 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ExpressionConstant.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ExpressionConstant.cs @@ -24,6 +24,11 @@ public sealed class DMASTConstantPath(Location location, DMASTPath value) : DMAS public readonly DMASTPath Value = value; } +public sealed class DMASTModifiedType(Location location, DMASTPath value, Dictionary? varOverrides) : DMASTExpressionConstant(location) { + public readonly DMASTPath Value = value; + public readonly Dictionary? VarOverrides = varOverrides; +} + public sealed class DMASTUpwardPathSearch(Location location, DMASTExpressionConstant path, DMASTPath search) : DMASTExpressionConstant(location) { public readonly DMASTExpressionConstant Path = path; diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index 0996f41bf0..f22e6f08f2 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -2231,6 +2231,7 @@ private void ExpressionTo(out DMASTExpression endRange, out DMASTExpression? ste DMASTExpression? newExpression = type switch { DMASTConstantPath path => new DMASTNewPath(loc, path, parameters), + DMASTModifiedType modifiedType => new DMASTNewModifiedType(loc, modifiedType, parameters), not null => new DMASTNewExpr(loc, type, parameters), null => new DMASTNewInferred(loc, parameters), }; @@ -2279,34 +2280,34 @@ private void ExpressionTo(out DMASTExpression endRange, out DMASTExpression? ste Whitespace(); // whitespace between path and modified type - //TODO actual modified type support if (Check(TokenType.DM_LeftCurlyBracket)) { - Compiler.UnimplementedWarning(path.Location, "Modified types are currently not supported and modified values will be ignored."); - BracketWhitespace(); - Check(TokenType.DM_Indent); // The body could be indented. We ignore that. TODO: Better braced block parsing + Whitespace(true); + Dictionary overrides = new(); DMASTIdentifier? overriding = Identifier(); - while (overriding != null) { BracketWhitespace(); Consume(TokenType.DM_Equals, "Expected '='"); BracketWhitespace(); - - Expression(); // TODO: Use this (one day...) - + DMASTExpression? value = Expression(); + RequireExpression(ref value); + overrides[overriding.Identifier] = value; if (Check(TokenType.DM_Semicolon)) { BracketWhitespace(); + Whitespace(true); overriding = Identifier(); } else { overriding = null; } } - Check(TokenType.DM_Dedent); // We ignore indents/dedents in the body + pathConstant = new DMASTModifiedType(loc, path, overrides); + Check(TokenType.DM_Dedent); BracketWhitespace(); + Whitespace(true); Consume(TokenType.DM_RightCurlyBracket, "Expected '}'"); - //The lexer tosses in a newline after '}', but we avoid Newline() because we only want to remove the extra newline, not all of them Check(TokenType.Newline); + //The lexer tosses in a newline after '}', but we avoid Newline() because we only want to remove the extra newline, not all of them } return pathConstant; diff --git a/DMCompiler/DM/Builders/DMASTFolder.cs b/DMCompiler/DM/Builders/DMASTFolder.cs index 0bbe08b1ea..6d7909a3a7 100644 --- a/DMCompiler/DM/Builders/DMASTFolder.cs +++ b/DMCompiler/DM/Builders/DMASTFolder.cs @@ -159,6 +159,14 @@ private DMASTExpression FoldExpression(DMASTExpression? expression) { } } + break; + case DMASTNewModifiedType newModifiedType: + if (newModifiedType.Parameters != null) { + foreach (DMASTCallParameter parameter in newModifiedType.Parameters) { + parameter.Value = FoldExpression(parameter.Value); + } + } + break; case DMASTNewExpr newExpr: if (newExpr.Parameters != null) { diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs index 943ccf87cc..7154284c9f 100644 --- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs @@ -284,9 +284,56 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe break; } - result = new NewPath(Compiler, newPath.Location, path, + result = new NewPath(Compiler, newPath.Location, path, null, BuildArgumentList(newPath.Location, newPath.Parameters, inferredPath)); break; + case DMASTNewModifiedType newModifiedType: + if (BuildExpression(newModifiedType.Type, inferredPath) is not IConstantPath typePath) { + result = BadExpression(WarningCode.BadExpression, newModifiedType.Type.Location, + "Expected a path expression"); + break; + } + + Dictionary overrides = new(); + if (newModifiedType.Type.VarOverrides is null) { + result = new NewPath(Compiler, newModifiedType.Location, typePath, overrides, + BuildArgumentList(newModifiedType.Location, newModifiedType.Parameters, inferredPath)); + break; + } + + if (!ObjectTree.TryGetDMObject(newModifiedType.Type.Value.Path, out var owner)) { + return UnknownReference(newModifiedType.Type.Location, $"Type {newModifiedType.Type.Value.Path} does not exist"); + } + + var failed = false; + foreach (KeyValuePair varOverride in newModifiedType.Type.VarOverrides) { + if (!owner.HasLocalVariable(varOverride.Key)) { + return UnknownIdentifier(newModifiedType.Type.Location, varOverride.Key); + } + + if (!BuildExpression(varOverride.Value, inferredPath) + .TryAsConstant(Compiler, out var jsonConstant)) { + if (!BuildExpression(varOverride.Value, inferredPath) + .TryAsJsonRepresentation(Compiler, out var jsonValue)) { + failed = true; + break; + } + + overrides[varOverride.Key] = jsonValue; + } else { + jsonConstant.TryAsJsonRepresentation(Compiler, out var jsonValue); + overrides[varOverride.Key] = jsonValue; + } + } + + if (failed) { + result = BadExpression(WarningCode.BadExpression, newModifiedType.Type.Location, "Expected a constant expression"); + break; + } + + result = new NewPath(Compiler, newModifiedType.Location, typePath, overrides, + BuildArgumentList(newModifiedType.Location, newModifiedType.Parameters, inferredPath)); + break; case DMASTNewExpr newExpr: result = new New(Compiler, newExpr.Location, BuildExpression(newExpr.Expression, inferredPath), @@ -305,7 +352,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe break; } - result = new NewPath(Compiler, newInferred.Location, inferredType, + result = new NewPath(Compiler, newInferred.Location, inferredType, new Dictionary(), BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath)); break; case DMASTPreIncrement preIncrement: @@ -456,6 +503,7 @@ private DMExpression BuildConstant(DMASTExpressionConstant constant) { case DMASTConstantString constString: return new String(constant.Location, constString.Value); case DMASTConstantResource constResource: return new Resource(Compiler, constant.Location, constResource.Path); case DMASTConstantPath constPath: return BuildPath(constant.Location, constPath.Value.Path); + case DMASTModifiedType constModifiedPath: return BuildPath(constant.Location, constModifiedPath.Value.Path); case DMASTUpwardPathSearch upwardSearch: BuildExpression(upwardSearch.Path).TryAsConstant(Compiler, out var pathExpr); if (pathExpr is not IConstantPath expr) diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index f8b995968f..474edf0e43 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -1,5 +1,6 @@ using DMCompiler.Bytecode; using System.Diagnostics.CodeAnalysis; +using System.Text.Json; using DMCompiler.Compiler; using DMCompiler.Json; @@ -67,13 +68,15 @@ internal sealed class New(DMCompiler compiler, Location location, DMExpression e public override void EmitPushValue(ExpressionContext ctx) { var argumentInfo = arguments.EmitArguments(ctx, null); + ctx.Proc.PushNull(); expr.EmitPushValue(ctx); ctx.Proc.CreateObject(argumentInfo.Type, argumentInfo.StackSize); } } // new /x/y/z (...) -internal sealed class NewPath(DMCompiler compiler, Location location, IConstantPath create, ArgumentList arguments) : DMExpression(location) { +internal sealed class NewPath(DMCompiler compiler, Location location, IConstantPath create, + Dictionary? variableOverrides, ArgumentList arguments) : DMExpression(location) { public override DreamPath? Path => (create is ConstantTypeReference typeReference) ? typeReference.Path : null; public override DMComplexValueType ValType => Path?.GetAtomType(compiler) ?? DMValueType.Anything; @@ -87,10 +90,23 @@ public override void EmitPushValue(ExpressionContext ctx) { var newProc = ctx.ObjectTree.GetNewProc(typeReference.Value.Id); (argumentsType, stackSize) = arguments.EmitArguments(ctx, newProc); + if (variableOverrides is null || variableOverrides.Count == 0) { + ctx.Proc.PushNull(); + } else { + ctx.Proc.PushString(JsonSerializer.Serialize(variableOverrides)); + } + ctx.Proc.PushType(typeReference.Value.Id); break; case ConstantProcReference procReference: // "new /proc/new_verb(Destination)" is a thing (argumentsType, stackSize) = arguments.EmitArguments(ctx, ctx.ObjectTree.AllProcs[procReference.Value.Id]); + if(variableOverrides is not null && variableOverrides.Count > 0) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Cannot add a Var Override to a proc"); + ctx.Proc.PushNull(); + return; + } + + ctx.Proc.PushNull(); ctx.Proc.PushProc(procReference.Value.Id); break; default: @@ -577,6 +593,7 @@ internal sealed class NewList(Location location, DMExpression[] parameters) : DM public override void EmitPushValue(ExpressionContext ctx) { foreach (DMExpression parameter in parameters) { + ctx.Proc.PushNull(); parameter.EmitPushValue(ctx); ctx.Proc.CreateObject(DMCallArgumentsType.None, 0); } diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 85e652d479..1e2a0be0dc 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; +using System.Text.Json; using System.Threading.Tasks; using DMCompiler; using DMCompiler.Bytecode; @@ -198,12 +199,18 @@ public static ProcStatus CreateRangeEnumerator(DMProcState state) { public static ProcStatus CreateObject(DMProcState state) { var argumentInfo = state.ReadProcArguments(); var val = state.Pop(); + Dictionary? overrides = null; + if (state.Pop().TryGetValueAsString(out var jsonDict)) { + overrides = JsonSerializer.Deserialize>(jsonDict); + } + if (!val.TryGetValueAsType(out var objectType)) { if (val.TryGetValueAsString(out var pathString)) { if (!state.Proc.ObjectTree.TryGetTreeEntry(pathString, out objectType)) { ThrowCannotCreateUnknownObject(val); } - } else if (val.TryGetValueAsProc(out var proc)) { // new /proc/proc_name(Destination,Name,Desc) + } else if (val.TryGetValueAsProc(out var proc)) { + // new /proc/proc_name(Destination,Name,Desc) var arguments = state.PopProcArguments(null, argumentInfo.Type, argumentInfo.StackSize); var destination = arguments.GetArgument(0); @@ -237,12 +244,24 @@ public static ProcStatus CreateObject(DMProcState state) { ThrowInvalidTurfLoc(loc); state.Proc.DreamMapManager.SetTurf(turf, objectDef, newArguments); + if (overrides is not null) { + foreach (KeyValuePair varOverride in overrides) { + turf.SetVariable(varOverride.Key, + state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value)); + } + } state.Push(loc); return ProcStatus.Continue; } var newObject = state.Proc.ObjectTree.CreateObject(objectType); + if (overrides is not null) { + foreach (KeyValuePair varOverride in overrides) { + newObject.SetVariable(varOverride.Key, state.Proc.ObjectTree.GetDreamValueFromJsonElement(varOverride.Value)); + } + } + var s = newObject.InitProc(state.Thread, state.Usr, newArguments); state.Thread.PushProcState(s);