Skip to content

Commit

Permalink
Add support for final on object vars (#2159)
Browse files Browse the repository at this point in the history
  • Loading branch information
wixoaGit authored Jan 6, 2025
1 parent e73af64 commit 8f975f9
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// COMPILE ERROR OD0407

/datum
var/final/foo = 1

/datum/a
foo = 2 // Can't override a final var
1 change: 1 addition & 0 deletions DMCompiler/Compiler/CompilerError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public enum WarningCode {
ItemDoesntExist = 404,
DanglingOverride = 405,
StaticOverride = 406,
FinalOverride = 407,
// ReSharper disable once InconsistentNaming
IAmATeaPot = 418, // TODO: Implement the HTCPC protocol for OD
HardConstContext = 500,
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public sealed class DMASTObjectVarDefinition(
public bool IsStatic => _varDecl.IsStatic;

public bool IsConst => _varDecl.IsConst;
public bool IsFinal => _varDecl.IsFinal;
public bool IsTmp => _varDecl.IsTmp;

public readonly DMComplexValueType ValType = valType;
Expand Down
243 changes: 127 additions & 116 deletions DMCompiler/Compiler/DM/DMPath.cs
Original file line number Diff line number Diff line change
@@ -1,146 +1,157 @@
namespace DMCompiler.Compiler.DM {
public abstract class VarDeclInfo {
public DreamPath? TypePath;
public string VarName;
namespace DMCompiler.Compiler.DM;

///<summary>Marks whether the variable is /global/ or /static/. (These are seemingly interchangeable keywords in DM and so are under this same boolean)</summary>
public bool IsStatic;
internal abstract class VarDeclInfo {
public DreamPath? TypePath;
public string VarName;

public bool IsConst;
public bool IsList;
}
///<summary>Marks whether the variable is /global/ or /static/. (These are seemingly interchangeable keywords in DM and so are under this same boolean)</summary>
public bool IsStatic;

public sealed class ProcVarDeclInfo : VarDeclInfo
{
public ProcVarDeclInfo(DreamPath path)
{
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
if (elements[readIdx] == "var")
{
readIdx++;
}
while (readIdx < elements.Length - 1)
{
var elem = elements[readIdx];
if (elem == "static" || elem == "global")
{
public bool IsConst;
public bool IsFinal;
public bool IsList;
}

internal sealed class ProcVarDeclInfo : VarDeclInfo {
public ProcVarDeclInfo(DreamPath path) {
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
if (elements[readIdx] == "var") {
readIdx++;
}

while (readIdx < elements.Length - 1) {
var elem = elements[readIdx];
switch (elem) {
case "static":
case "global":
IsStatic = true;
}
else if (elem == "const")
{
break;
case "const":
IsConst = true;
}
else if (elem == "list")
{
break;
case "final":
IsFinal = true;
break;
case "list":
IsList = true;
}
else
{
break;
default:
currentPath.Add(elem);
}
readIdx += 1;
}
if (currentPath.Count > 0)
{
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
}
else
{
TypePath = null;
break;
}
VarName = elements[elements.Length - 1];

readIdx += 1;
}

if (currentPath.Count > 0) {
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
} else {
TypePath = null;
}

VarName = elements[^1];
}
}

public sealed class ObjVarDeclInfo : VarDeclInfo
{
public DreamPath ObjectPath;
public bool IsTmp;

public ObjVarDeclInfo(DreamPath path)
{
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
while (readIdx < elements.Length && elements[readIdx] != "var")
{
currentPath.Add(elements[readIdx]);
readIdx += 1;
}
ObjectPath = new DreamPath(path.Type, currentPath.ToArray());
if (ObjectPath.Elements.Length == 0) // Variables declared in the root scope are inherently static.
{
IsStatic = true;
}
currentPath.Clear();
internal sealed class ObjVarDeclInfo : VarDeclInfo {
public DreamPath ObjectPath;
public readonly bool IsTmp;

public ObjVarDeclInfo(DreamPath path) {
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
while (readIdx < elements.Length && elements[readIdx] != "var") {
currentPath.Add(elements[readIdx]);
readIdx += 1;
while (readIdx < elements.Length - 1)
{
var elem = elements[readIdx];
if (elem == "static" || elem == "global")
{
}

ObjectPath = new DreamPath(path.Type, currentPath.ToArray());
if (ObjectPath.Elements.Length == 0) { // Variables declared in the root scope are inherently static.
IsStatic = true;
}

currentPath.Clear();
readIdx += 1;
while (readIdx < elements.Length - 1) {
var elem = elements[readIdx];
switch (elem) {
case "static":
case "global":
IsStatic = true;
}
else if (elem == "const")
{
break;
case "const":
IsConst = true;
}
else if (elem == "list")
{
break;
case "final":
IsFinal = true;
break;
case "list":
IsList = true;
}
else if (elem == "tmp")
{
break;
case "tmp":
IsTmp = true;
}
else
{
break;
default:
currentPath.Add(elem);
}
readIdx += 1;
}
if (currentPath.Count > 0)
{
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
break;
}
else
{
TypePath = null;
}
VarName = elements[elements.Length - 1];

readIdx += 1;
}

if (currentPath.Count > 0) {
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
} else {
TypePath = null;
}

VarName = elements[^1];
}
}

public sealed class ProcParameterDeclInfo : VarDeclInfo {
public ProcParameterDeclInfo(DreamPath path) {
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
if (elements[readIdx] == "var") {
readIdx++;
}
while (readIdx < elements.Length - 1) {
var elem = elements[readIdx];
if (elem == "static" || elem == "global") {
internal sealed class ProcParameterDeclInfo : VarDeclInfo {
public ProcParameterDeclInfo(DreamPath path) {
string[] elements = path.Elements;
var readIdx = 0;
List<string> currentPath = new();
if (elements[readIdx] == "var") {
readIdx++;
}

while (readIdx < elements.Length - 1) {
var elem = elements[readIdx];
switch (elem) {
case "static":
case "global":
//No effect
} else if (elem == "const") {
break;
case "const":
//TODO: Parameters can be constant
//If they are they can't be assigned to but still cannot be used in const-only contexts (such as switch cases)
} else if (elem == "list") {
break;
case "final":
IsFinal = true;
break;
case "list":
IsList = true;
} else {
break;
default:
currentPath.Add(elem);
}
readIdx += 1;
break;
}
if (currentPath.Count > 0) {
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
} else {
TypePath = null;
}
VarName = elements[elements.Length - 1];

readIdx += 1;
}

if (currentPath.Count > 0) {
TypePath = new DreamPath(DreamPath.PathType.Absolute, currentPath.ToArray());
} else {
TypePath = null;
}

VarName = elements[^1];
}
}
11 changes: 8 additions & 3 deletions DMCompiler/DM/DMCodeTree.Vars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private bool HandleGlobalVar(DMCompiler compiler, DMObject dmObject, int pass) {
return false;

int globalId = compiler.DMObjectTree.CreateGlobal(out DMVariable global, varDef.Type, VarName, varDef.IsConst,
varDef.ValType);
varDef.IsFinal, varDef.ValType);

dmObject.AddGlobalVariable(global, globalId);
_defined = true;
Expand All @@ -152,7 +152,7 @@ private bool HandleInstanceVar(DMCompiler compiler, DMObject dmObject) {
if (!TryBuildValue(new(compiler, dmObject, null), varDef.Value, varDef.Type, ScopeMode.Normal, out var value))
return false;

var variable = new DMVariable(varDef.Type, VarName, false, varDef.IsConst, varDef.IsTmp, varDef.ValType);
var variable = new DMVariable(varDef.Type, VarName, false, varDef.IsConst, varDef.IsFinal, varDef.IsTmp, varDef.ValType);
dmObject.AddVariable(variable);
_defined = true;

Expand Down Expand Up @@ -222,6 +222,11 @@ public override bool TryDefineVar(DMCompiler compiler, int pass) {
$"Var \"{VarName}\" is const and cannot be modified");
_finished = true;
return true;
} else if (variable.IsFinal) {
compiler.Emit(WarningCode.FinalOverride, varOverride.Location,
$"Var \"{VarName}\" is final and cannot be modified");
_finished = true;
return true;
} else if (variable.ValType.IsCompileTimeReadOnly) {
compiler.Emit(WarningCode.WriteToConstant, varOverride.Location,
$"Var \"{VarName}\" is a native read-only value which cannot be modified");
Expand Down Expand Up @@ -267,7 +272,7 @@ public override bool TryDefineVar(DMCompiler compiler, int pass) {
}

int globalId = compiler.DMObjectTree.CreateGlobal(out DMVariable global, varDecl.Type, varDecl.Name, varDecl.IsConst,
varDecl.ValType);
false, varDecl.ValType);

global.Value = new Null(Location.Internal);
proc.AddGlobalVariable(global, globalId);
Expand Down
4 changes: 2 additions & 2 deletions DMCompiler/DM/DMObjectTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ public bool TryGetTypeId(DreamPath path, out int typeId) {
return null;
}

public int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, 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, false, valType);
global = new DMVariable(type, name, true, isConst, isFinal, false, valType);
Globals.Add(global);
return id;
}
Expand Down
18 changes: 11 additions & 7 deletions DMCompiler/DM/DMVariable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ namespace DMCompiler.DM;

internal sealed class DMVariable {
public DreamPath? Type;
public string Name;
public bool IsGlobal;
public readonly string Name;
public readonly bool IsGlobal;
public readonly bool IsTmp;
public readonly bool IsFinal;
public DMExpression? Value;
public DMComplexValueType ValType;

/// <remarks>
/// NOTE: This DMVariable may be forced constant through opendream_compiletimereadonly. This only marks that the variable has the DM quality of /const/ness.
/// </remarks>
public bool IsConst;
public bool IsTmp;
public DMExpression? Value;
public DMComplexValueType ValType;
public readonly bool IsConst;

public bool CanConstFold => (IsConst || ValType.Type.HasFlag(DMValueType.CompiletimeReadonly)) &&
!ValType.Type.HasFlag(DMValueType.NoConstFold);

public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, bool isTmp, DMComplexValueType? valType = null) {
public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, bool isFinal, bool isTmp, DMComplexValueType? valType = null) {
Type = type;
Name = name;
IsGlobal = isGlobal;
IsConst = isConst;
IsFinal = isFinal;
IsTmp = isTmp;
Value = null;
ValType = valType ?? DMValueType.Anything;
Expand All @@ -32,6 +35,7 @@ public DMVariable(DMVariable copyFrom) {
Name = copyFrom.Name;
IsGlobal = copyFrom.IsGlobal;
IsConst = copyFrom.IsConst;
IsFinal = copyFrom.IsFinal;
IsTmp = copyFrom.IsTmp;
Value = copyFrom.Value;
ValType = copyFrom.ValType;
Expand Down

0 comments on commit 8f975f9

Please sign in to comment.