Skip to content

This file was deleted.

This file was deleted.

This file was deleted.

11 changes: 11 additions & 0 deletions Content.Tests/DMProject/Tests/Expression/ModifiedType/basic.dm
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 14 additions & 0 deletions Content.Tests/DMProject/Tests/Expression/ModifiedType/multi.dm
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

//# issue 655

/datum
var/a = 5

/proc/RunTest()
var/datum/D = new /datum {a=7}
ASSERT(D.a == 7)
6 changes: 6 additions & 0 deletions DMCompiler/Compiler/DM/AST/DMAST.Expression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions DMCompiler/Compiler/DM/AST/DMAST.ExpressionConstant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, DMASTExpression>? varOverrides) : DMASTExpressionConstant(location) {
public readonly DMASTPath Value = value;
public readonly Dictionary<string, DMASTExpression>? VarOverrides = varOverrides;
}

public sealed class DMASTUpwardPathSearch(Location location, DMASTExpressionConstant path, DMASTPath search)
: DMASTExpressionConstant(location) {
public readonly DMASTExpressionConstant Path = path;
Expand Down
21 changes: 11 additions & 10 deletions DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
};
Expand Down Expand Up @@ -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<string, DMASTExpression> 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;
Expand Down
8 changes: 8 additions & 0 deletions DMCompiler/DM/Builders/DMASTFolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
52 changes: 50 additions & 2 deletions DMCompiler/DM/Builders/DMExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object?> 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<string, DMASTExpression> 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),
Expand All @@ -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<string, object?>(),
BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath));
break;
case DMASTPreIncrement preIncrement:
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 18 additions & 1 deletion DMCompiler/DM/Expressions/Builtins.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using DMCompiler.Bytecode;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using DMCompiler.Compiler;
using DMCompiler.Json;

Expand Down Expand Up @@ -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<string, object?>? 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;

Expand All @@ -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:
Expand Down Expand Up @@ -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);
}
Expand Down
21 changes: 20 additions & 1 deletion OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<string, object?>? overrides = null;
if (state.Pop().TryGetValueAsString(out var jsonDict)) {
overrides = JsonSerializer.Deserialize<Dictionary<string, object?>>(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);

Expand Down Expand Up @@ -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<string, object?> 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<string, object?> 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);
Expand Down
Loading