Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve proc override paths in var values #1981

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_error.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// COMPILE ERROR OD2601
#pragma AmbiguousProcPath error

/datum/proc/foo()
return

/datum/foo()
return

/proc/RunTest()
var/meep = /datum/proc/foo
return
11 changes: 11 additions & 0 deletions Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_noerror.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma AmbiguousProcPath error

/datum/proc/foo()
return

/datum/foo()
return

/proc/RunTest()
var/meep = /datum/foo
return
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// COMPILE ERROR OD0404

/atom/movable/proc/foo()
return

// This only works without the "proc" element if an override is declared
/datum/var/bar = /atom/movable/foo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
this errors in DM, it's invalid (tries to find a type rather than a proc)

in 515 and later the proper way to call the latest override (lateral or descended) is via call(nameof(/atom/movable/proc/foo))() (or call(nameof(/atom/movable.proc/foo))(), i don't know why that one is sometimes used? i think it's just a legacy thing)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeating what I said on discord a while back (to keep any PR observers informed), this does work if it's a proc override. My mistake was not erroring when there's no proc override declared.


/proc/RunTest()
return
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// RETURN TRUE

/atom/movable/proc/foo()
return

/atom/movable/foo()
return

// This only works without the "proc" element if an override is declared
/datum/var/bar = /atom/movable/foo

/proc/RunTest()
return TRUE
1 change: 1 addition & 0 deletions DMCompiler/Compiler/CompilerError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum WarningCode {
DanglingVarType = 2401, // For types inferred by a particular var definition and nowhere else, that ends up not existing (not forced-fatal because BYOND doesn't always error)
MissingInterpolatedExpression = 2500, // A text macro is missing a required interpolated expression
AmbiguousResourcePath = 2600,
AmbiguousProcPath = 2601, // A procpath is being referenced with a /proc/ element even though lateral overrides exist and will be ignored
UnsupportedTypeCheck = 2700,
InvalidReturnType = 2701, // Proc static typing
InvalidVarType = 2702, // Var static typing
Expand Down
6 changes: 6 additions & 0 deletions DMCompiler/DM/DMObjectTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ namespace DMCompiler.DM;

internal static class DMObjectTree {
public static readonly List<DMObject> AllObjects = new();

/// <summary>
/// A list of all procs. Can be indexed by a procID int to get its DMProc
/// </summary>
public static readonly List<DMProc> AllProcs = new();

//TODO: These don't belong in the object tree
public static readonly List<DMVariable> Globals = new();
public static readonly Dictionary<string, int> GlobalProcs = new();

/// <summary>
/// Used to keep track of when we see a /proc/foo() or whatever, so that duplicates or missing definitions can be discovered,
/// even as GlobalProcs keeps clobbering old global proc overrides/definitions.
/// </summary>
public static readonly HashSet<string> SeenGlobalProcDefinition = new();

public static readonly List<string> StringTable = new();
public static DMProc GlobalInitProc = default!; // Initialized by Reset() (called in the static initializer)
public static readonly HashSet<string> Resources = new();
Expand Down
58 changes: 44 additions & 14 deletions DMCompiler/DM/Expressions/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,17 +336,9 @@ public bool TryResolvePath([NotNullWhen(true)] out (PathType Type, int Id)? path
if (procIndex != -1) {
DreamPath withoutProcElement = path.RemoveElement(procIndex);
DreamPath ownerPath = withoutProcElement.FromElements(0, -2);
DMObject owner = DMObjectTree.GetDMObject(ownerPath, createIfNonexistent: false);
string procName = path.LastElement;
string procName = path.LastElement!;

int? procId;
if (owner == DMObjectTree.Root && DMObjectTree.TryGetGlobalProc(procName, out var globalProc)) {
procId = globalProc.Id;
} else {
var procs = owner.GetProcs(procName);

procId = procs?[^1];
}
ResolveProc(ownerPath, procName, false, out var procId);

if (procId == null) {
DMCompiler.Emit(WarningCode.ItemDoesntExist, Location,
Expand All @@ -364,11 +356,49 @@ public bool TryResolvePath([NotNullWhen(true)] out (PathType Type, int Id)? path
if (DMObjectTree.TryGetTypeId(Value, out var typeId)) {
pathInfo = (PathType.TypeReference, typeId);
return true;
} else {
DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {Value} does not exist");
}

pathInfo = null;
return false;
// If it's not a type, check again for a proc override without the /proc/ element
if (ResolveProc(path.FromElements(0, -2), path.LastElement!, true, out var proc)) {
pathInfo = (PathType.ProcReference, proc.Value);
return true;
}

DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {Value} does not exist");

pathInfo = null;
return false;

bool ResolveProc(DreamPath ownerPath, string procName, bool isOverride, [NotNullWhen(true)] out int? procId) {
procId = null;

DMObject? owner = DMObjectTree.GetDMObject(ownerPath, createIfNonexistent: false);
if (owner is null) return false;

if (owner == DMObjectTree.Root && DMObjectTree.TryGetGlobalProc(procName, out var globalProc)) {
procId = globalProc.Id;
return true;
}

var procs = owner.GetProcs(procName);
if (procs is null || procs.Count == 0) return false;

if (isOverride) {
procId = procs[^1];
var dmProc = DMObjectTree.AllProcs[procId.Value];
// Trying to resolve a procpath without the "/proc/" element only works if the proc is an override
if ((dmProc.Attributes & ProcAttributes.IsOverride) != ProcAttributes.IsOverride) {
DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"{Value}: undefined type path");
}
} else {
procId = procs[0];
if (procs.Count > 1) {
DMCompiler.Emit(WarningCode.AmbiguousProcPath, Location,
$"Type {ownerPath} has lateral overrides of proc {procName} but \"/proc/\" references the original definition only");
}
}

return true;
}
}
}
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/DMStandard/DefaultPragmaConfig.dm
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#pragma DanglingVarType warning
#pragma MissingInterpolatedExpression warning
#pragma AmbiguousResourcePath warning
#pragma AmbiguousProcPath warning
#pragma SuspiciousSwitchCase warning
#pragma PointlessPositionalArgument warning
// NOTE: The next few pragmas are for OpenDream's experimental type checker
Expand Down
Loading