diff --git a/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_error.dm b/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_error.dm new file mode 100644 index 0000000000..1bc81fbc1e --- /dev/null +++ b/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_error.dm @@ -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 diff --git a/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_noerror.dm b/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_noerror.dm new file mode 100644 index 0000000000..046e1e6316 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Procs/ambiguous_procpath_noerror.dm @@ -0,0 +1,11 @@ +#pragma AmbiguousProcPath error + +/datum/proc/foo() + return + +/datum/foo() + return + +/proc/RunTest() + var/meep = /datum/foo + return diff --git a/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element.dm b/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element.dm new file mode 100644 index 0000000000..4ca3eb1465 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element.dm @@ -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 + +/proc/RunTest() + return diff --git a/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element2.dm b/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element2.dm new file mode 100644 index 0000000000..7837726ddd --- /dev/null +++ b/Content.Tests/DMProject/Tests/Procs/procpath_without_proc_element2.dm @@ -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 diff --git a/DMCompiler/Compiler/CompilerError.cs b/DMCompiler/Compiler/CompilerError.cs index ed7f43bd81..d15de5d723 100644 --- a/DMCompiler/Compiler/CompilerError.cs +++ b/DMCompiler/Compiler/CompilerError.cs @@ -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 diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index 9b08ac0ad1..c0d5b087df 100644 --- a/DMCompiler/DM/DMObjectTree.cs +++ b/DMCompiler/DM/DMObjectTree.cs @@ -8,16 +8,22 @@ namespace DMCompiler.DM; internal static class DMObjectTree { public static readonly List AllObjects = new(); + + /// + /// A list of all procs. Can be indexed by a procID int to get its DMProc + /// public static readonly List AllProcs = new(); //TODO: These don't belong in the object tree public static readonly List Globals = new(); public static readonly Dictionary GlobalProcs = new(); + /// /// 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. /// public static readonly HashSet SeenGlobalProcDefinition = new(); + public static readonly List StringTable = new(); public static DMProc GlobalInitProc = default!; // Initialized by Reset() (called in the static initializer) public static readonly HashSet Resources = new(); diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 6b4cb135d9..8cbaf4aba1 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -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, @@ -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; } } } diff --git a/DMCompiler/DMStandard/DefaultPragmaConfig.dm b/DMCompiler/DMStandard/DefaultPragmaConfig.dm index c91ebb324d..48820fd487 100644 --- a/DMCompiler/DMStandard/DefaultPragmaConfig.dm +++ b/DMCompiler/DMStandard/DefaultPragmaConfig.dm @@ -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