diff --git a/DMCompiler/Compiler/DM/DMAST.cs b/DMCompiler/Compiler/DM/DMAST.cs
index 59f0f5c246..791af423d7 100644
--- a/DMCompiler/Compiler/DM/DMAST.cs
+++ b/DMCompiler/Compiler/DM/DMAST.cs
@@ -618,6 +618,12 @@ public sealed class DMASTProcBlockInner : DMASTNode {
///
public readonly DMASTProcStatement[] SetStatements;
+ ///
+ /// This is used to determine if we should flag for WarningCode.EmptyBlock,
+ /// which we don't do if there was a manually entered semicolon within.
+ ///
+ public bool NotCompletelyEmpty;
+
/// Initializes an empty block.
public DMASTProcBlockInner(Location location) : base(location) {
Statements = Array.Empty();
diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs
index 2bad848edb..c3838db4a5 100644
--- a/DMCompiler/Compiler/DM/DMParser.cs
+++ b/DMCompiler/Compiler/DM/DMParser.cs
@@ -523,6 +523,7 @@ public DMASTFile File() {
var loc = Current().Location;
if (Check(TokenType.DM_LeftCurlyBracket)) {
DMASTProcBlockInner? block;
+ bool hadSemicolon = false;
Whitespace();
Newline();
@@ -535,9 +536,13 @@ public DMASTFile File() {
List setStatements = new(); // set statements are weird and must be held separately.
do {
- (List? stmts, List? setStmts) = ProcBlockInner(); // Hope you understand tuples
- if (stmts is not null) statements.AddRange(stmts);
- if (setStmts is not null) setStatements.AddRange(setStmts);
+ (List? stmts, List? setStmts, bool innerHadSemicolon) = ProcBlockInner(); // Hope you understand tuples
+ if (stmts is not null)
+ statements.AddRange(stmts);
+ if (setStmts is not null)
+ setStatements.AddRange(setStmts);
+ if (innerHadSemicolon)
+ hadSemicolon = innerHadSemicolon;
if (!Check(TokenType.DM_RightCurlyBracket)) {
Error(WarningCode.BadToken, "Expected end of braced block");
@@ -550,7 +555,10 @@ public DMASTFile File() {
}
} while (true);
- block = new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray());
+ block = new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray())
+ {
+ NotCompletelyEmpty = hadSemicolon
+ };
}
return block;
@@ -564,13 +572,16 @@ public DMASTFile File() {
if (Check(TokenType.DM_Indent)) {
List statements = new();
List setStatements = new(); // set statements are weird and must be held separately.
+ bool hadSemicolon = false;
do {
- (List? statements, List? setStatements) blockInner = ProcBlockInner();
+ (List? statements, List? setStatements, bool hadSemicolon) blockInner = ProcBlockInner();
if (blockInner.statements is not null)
statements.AddRange(blockInner.statements);
if (blockInner.setStatements is not null)
setStatements.AddRange(blockInner.setStatements);
+ if (blockInner.hadSemicolon)
+ hadSemicolon = blockInner.hadSemicolon;
if (!Check(TokenType.DM_Dedent)) {
Error("Expected end of proc statement", throwException: false);
@@ -581,17 +592,26 @@ public DMASTFile File() {
}
} while (true);
- return new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray());
+ return new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray())
+ {
+ NotCompletelyEmpty = hadSemicolon
+ };
}
return null;
}
- public (List?, List?) ProcBlockInner() {
+ ///
+ /// Parses the inner of a proc block
+ ///
+ /// We return if there was a semicolon within for WarningCode.EmptyBlock
+ /// (list of proc statements, list of set statements, had a semicolon within)
+ public (List?, List?, bool) ProcBlockInner() {
List procStatements = new();
List setStatements = new(); // We have to store them separately because they're evaluated first
DMASTProcStatement? statement = null;
+ bool hadSemicolon = false;
do {
Whitespace();
@@ -611,11 +631,14 @@ public DMASTFile File() {
DMASTProcBlockInner? blockInner = ProcBlock();
if (blockInner != null) procStatements.AddRange(blockInner.Statements);
}
- } while (Delimiter() || statement is DMASTProcStatementLabel);
+ } while (DelimiterHasSemicolon(ref hadSemicolon) || statement is DMASTProcStatementLabel);
+
Whitespace();
- if (procStatements.Count == 0) return (null,null);
- return (procStatements, setStatements);
+ if (procStatements.Count == 0)
+ return (null, null, hadSemicolon);
+
+ return (procStatements, setStatements, hadSemicolon);
}
public DMASTProcStatement? ProcStatement() {
@@ -2617,5 +2640,19 @@ private bool Delimiter() {
return hasDelimiter;
}
+
+ private bool DelimiterHasSemicolon(ref bool hasSemicolon) {
+ bool hasDelimiter = false;
+ while (
+ // Once it is set to true, keep it that way
+ (!hasSemicolon && (hasSemicolon = Current().Type == TokenType.DM_Semicolon))
+ || Check(TokenType.DM_Semicolon)
+ || Newline()
+ ) {
+ hasDelimiter = true;
+ }
+
+ return hasDelimiter;
+ }
}
}
diff --git a/DMCompiler/DM/Visitors/DMProcBuilder.cs b/DMCompiler/DM/Visitors/DMProcBuilder.cs
index 14e540c08f..136f574510 100644
--- a/DMCompiler/DM/Visitors/DMProcBuilder.cs
+++ b/DMCompiler/DM/Visitors/DMProcBuilder.cs
@@ -85,7 +85,7 @@ private void ProcessBlockInner(DMASTProcBlockInner block, bool silenceEmptyBlock
DMCompiler.Emit(e.Error);
}
}
- if(!silenceEmptyBlockWarning && block.Statements.Length == 0) { // If this block has no real statements
+ if(!silenceEmptyBlockWarning && block.Statements.Length == 0 && !block.NotCompletelyEmpty) { // If this block has no real statements
// Not an error in BYOND, but we do have an emission for this!
if(block.SetStatements.Length != 0) { // Give a more articulate message about this, since it's kinda weird
DMCompiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected - set statements are executed outside of, before, and unconditional to, this block");