Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

/proc/RunTest()
return
#pragma ImplicitNullType notice
4 changes: 2 additions & 2 deletions DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public sealed class DMASTObjectVarDefinition(
Location location,
DreamPath path,
DMASTExpression value,
DMComplexValueType valType) : DMASTStatement(location) {
DMComplexValueType? valType) : DMASTStatement(location) {
/// <summary>The path of the object that we are a property of.</summary>
public DreamPath ObjectPath => _varDecl.ObjectPath;

Expand All @@ -88,7 +88,7 @@ public sealed class DMASTObjectVarDefinition(
public bool IsFinal => _varDecl.IsFinal;
public bool IsTmp => _varDecl.IsTmp;

public readonly DMComplexValueType ValType = valType;
public readonly DMComplexValueType? ValType = valType;
}

public sealed class DMASTMultipleObjectVarDefinitions(Location location, DMASTObjectVarDefinition[] varDefinitions)
Expand Down
5 changes: 3 additions & 2 deletions DMCompiler/Compiler/DM/AST/DMAST.ProcStatements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ public sealed class DMASTProcStatementExpression(Location location, DMASTExpress
public DMASTExpression Expression = expression;
}

public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType valType)
public sealed class DMASTProcStatementVarDeclaration(Location location, DMASTPath path, DMASTExpression? value, DMComplexValueType? valType)
: DMASTProcStatement(location) {
public DMASTExpression? Value = value;

public DreamPath? Type => _varDecl.IsList ? DreamPath.List : _varDecl.TypePath;
public DMComplexValueType? ExplicitValType => valType;

public DMComplexValueType ValType => valType;
public DMComplexValueType? ValType => ExplicitValType ?? DMValueType.Anything;

public string Name => _varDecl.VarName;
public bool IsGlobal => _varDecl.IsStatic;
Expand Down
92 changes: 73 additions & 19 deletions DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public DMASTFile File() {
value = new DMASTConstantNull(loc);
}

var valType = AsComplexTypes() ?? DMValueType.Anything;
var valType = AsComplexTypes();
var varDef = new DMASTObjectVarDefinition(loc, varPath, value, valType);

if (varDef.IsStatic && varDef.Name is "usr" or "src" or "args" or "world" or "global" or "callee" or "caller")
Expand Down Expand Up @@ -936,7 +936,11 @@ public DMASTFile File() {
RequireExpression(ref value);
}

var valType = AsComplexTypes() ?? DMValueType.Anything;
var valType = AsComplexTypes();
// the != DMValueType.Null check is a hacky workaround for Anything|Null being equal to just Null
if (valType is null && value?.GetUnwrapped() is DMASTInput input && input.Types != DMValueType.Null) {
valType = input.Types;
}

varDeclarations.Add(new DMASTProcStatementVarDeclaration(loc, varPath, value, valType));
if (allowMultiple && Check(TokenType.DM_Comma)) {
Expand Down Expand Up @@ -2782,7 +2786,7 @@ private void BracketWhitespace() {

do {
Whitespace();
type |= SingleAsType(out _);
type |= SingleAsType(out _, out _);
Whitespace();
} while (Check(TokenType.DM_Bar));

Expand All @@ -2802,29 +2806,49 @@ private void BracketWhitespace() {
if (parenthetical && Check(TokenType.DM_RightParenthesis)) // as ()
return DMValueType.Anything; // TODO: BYOND doesn't allow this for proc return types

var outType = UnionComplexTypes();

if (parenthetical) {
ConsumeRightParenthesis();
}

return outType;
}

private DMComplexValueType? UnionComplexTypes() {

DMValueType type = DMValueType.Anything;
DreamPath? path = null;
DMListValueTypes? outListTypes = null;

do {
Whitespace();
type |= SingleAsType(out var pathType, allowPath: true);
type |= SingleAsType(out var pathType, out var listTypes, allowPath: true);
Whitespace();

if (pathType != null) {
if (path == null)
if (pathType is not null) {
if (path is null)
path = pathType;
else
Compiler.Emit(WarningCode.BadToken, CurrentLoc,
$"Only one type path can be used, ignoring {pathType}");
else {
var newPath = path.Value.GetLastCommonAncestor(compiler, pathType.Value);
if (newPath != path) {
Compiler.Emit(WarningCode.LostTypeInfo, CurrentLoc,
$"Only one type path can be used, using last common ancestor {newPath}");
path = newPath;
}
}
}

if (listTypes is not null) {
if (outListTypes is null)
outListTypes = listTypes;
else {
outListTypes = DMListValueTypes.MergeListValueTypes(compiler, outListTypes, listTypes);
}
}
} while (Check(TokenType.DM_Bar));

if (parenthetical) {
ConsumeRightParenthesis();
}

return new(type, path);
return new(type, path, outListTypes);
}

private bool AsTypesStart(out bool parenthetical) {
Expand All @@ -2838,22 +2862,53 @@ private bool AsTypesStart(out bool parenthetical) {
return false;
}

private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
private DMValueType SingleAsType(out DreamPath? path, out DMListValueTypes? listTypes, bool allowPath = false) {
Token typeToken = Current();

if (!Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) {
listTypes = null;

var inPath = false;
if (typeToken.Type is TokenType.DM_Identifier && typeToken.Text == "path") {
Advance();
if(Check(TokenType.DM_LeftParenthesis)) {
inPath = true;
Whitespace();
}
}

if (inPath || !Check(new[] { TokenType.DM_Identifier, TokenType.DM_Null })) {
// Proc return types
path = Path()?.Path;
if (allowPath) {
if (path is null) {
Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type or path");
}

return DMValueType.Path;
if (inPath) {
Whitespace();
ConsumeRightParenthesis();
} else if (path == DreamPath.List) {
// check for list types
if (Check(TokenType.DM_LeftParenthesis)) {
DMComplexValueType? nestedKeyType = UnionComplexTypes();
if (nestedKeyType is null)
Compiler.Emit(WarningCode.BadToken, CurrentLoc, "Expected value type or path");
DMComplexValueType? nestedValType = null;
if (Check(TokenType.DM_Comma)) { // Value
nestedValType = UnionComplexTypes();
}

ConsumeRightParenthesis();
listTypes = new(nestedKeyType!.Value, nestedValType);
return DMValueType.Instance;
}
}

return inPath ? DMValueType.Path : DMValueType.Instance;
}

Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type");
return 0;
return DMValueType.Anything;
}

path = null;
Expand All @@ -2872,7 +2927,6 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) {
case "command_text": return DMValueType.CommandText;
case "sound": return DMValueType.Sound;
case "icon": return DMValueType.Icon;
case "path": return DMValueType.Path;
case "opendream_unimplemented": return DMValueType.Unimplemented;
case "opendream_unsupported": return DMValueType.Unsupported;
case "opendream_compiletimereadonly": return DMValueType.CompiletimeReadonly;
Expand Down
51 changes: 33 additions & 18 deletions DMCompiler/DM/Builders/DMExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public void Emit(DMASTExpression expression, DreamPath? inferredPath = null) {
expr.EmitPushValue(ctx);
}

public void Emit(DMASTExpression expression, out DMExpression returnedExpr, DreamPath? inferredPath = null) {
returnedExpr = Create(expression, inferredPath);
returnedExpr.EmitPushValue(ctx);
}

public bool TryConstant(DMASTExpression expression, out Constant? constant) {
var expr = Create(expression);
return expr.TryAsConstant(Compiler, out constant);
Expand All @@ -64,7 +69,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
case DMASTStringFormat stringFormat: result = BuildStringFormat(stringFormat, inferredPath); break;
case DMASTIdentifier identifier: result = BuildIdentifier(identifier, inferredPath); break;
case DMASTScopeIdentifier globalIdentifier: result = BuildScopeIdentifier(globalIdentifier, inferredPath); break;
case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.ReturnTypes); break;
case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.RawReturnTypes); break;
case DMASTCallableSuper: result = new ProcSuper(expression.Location, ctx.Type.GetProcReturnTypes(ctx.Proc.Name)); break;
case DMASTCallableProcIdentifier procIdentifier: result = BuildCallableProcIdentifier(procIdentifier, ctx.Type); break;
case DMASTProcCall procCall: result = BuildProcCall(procCall, inferredPath); break;
Expand Down Expand Up @@ -272,10 +277,10 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe

if (b.ValType.TypePath != null && c.ValType.TypePath != null && b.ValType.TypePath != c.ValType.TypePath) {
Compiler.Emit(WarningCode.LostTypeInfo, ternary.Location,
$"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using {b.ValType.TypePath}.");
$"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using their common ancestor, {b.ValType.TypePath.Value.GetLastCommonAncestor(Compiler, c.ValType.TypePath.Value)}.");
}

result = new Ternary(ternary.Location, a, b, c);
result = new Ternary(Compiler, ternary.Location, a, b, c);
break;
case DMASTNewPath newPath:
if (BuildExpression(newPath.Path, inferredPath) is not IConstantPath path) {
Expand All @@ -284,7 +289,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
break;
}

result = new NewPath(Compiler, newPath.Location, path,
result = new NewPath(newPath.Location, path,
BuildArgumentList(newPath.Location, newPath.Parameters, inferredPath));
break;
case DMASTNewExpr newExpr:
Expand All @@ -305,7 +310,7 @@ private DMExpression BuildExpression(DMASTExpression expression, DreamPath? infe
break;
}

result = new NewPath(Compiler, newInferred.Location, inferredType,
result = new NewPath(newInferred.Location, inferredType,
BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath));
break;
case DMASTPreIncrement preIncrement:
Expand Down Expand Up @@ -554,7 +559,7 @@ private DMExpression BuildIdentifier(DMASTIdentifier identifier, DreamPath? infe
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
var localVar = ctx.Proc?.GetLocalVariable(name);
if (localVar is not null) {
return new Local(identifier.Location, localVar);
return new Local(identifier.Location, localVar, localVar.ExplicitValueType);
}
}

Expand Down Expand Up @@ -622,7 +627,7 @@ private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier,
return UnknownReference(location, $"No global proc named \"{bIdentifier}\" exists");

var arguments = BuildArgumentList(location, scopeIdentifier.CallArguments, inferredPath);
return new ProcCall(location, new GlobalProc(location, globalProc), arguments, DMValueType.Anything);
return new ProcCall(Compiler, location, new GlobalProc(location, globalProc), arguments, DMValueType.Anything);
}

// ::vars, special case
Expand Down Expand Up @@ -715,7 +720,7 @@ private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier,
if (variable == null)
return UnknownIdentifier(location, bIdentifier);

return new ScopeReference(ObjectTree, location, expression, bIdentifier, variable);
return new ScopeReference(Compiler, location, expression, bIdentifier, variable);
}
}

Expand All @@ -729,7 +734,7 @@ private DMExpression BuildCallableProcIdentifier(DMASTCallableProcIdentifier pro
}

if (dmObject.HasProc(procIdentifier.Identifier)) {
return new Proc(procIdentifier.Location, procIdentifier.Identifier);
return new Proc(procIdentifier.Location, procIdentifier.Identifier, dmObject);
}

if (ObjectTree.TryGetGlobalProc(procIdentifier.Identifier, out var globalProc)) {
Expand Down Expand Up @@ -766,10 +771,10 @@ private DMExpression BuildProcCall(DMASTProcCall procCall, DreamPath? inferredPa
if (target is Proc targetProc) { // GlobalProc handles returnType itself
var returnType = targetProc.GetReturnType(ctx.Type);

return new ProcCall(procCall.Location, target, args, returnType);
return new ProcCall(Compiler, procCall.Location, target, args, returnType);
}

return new ProcCall(procCall.Location, target, args, DMValueType.Anything);
return new ProcCall(Compiler, procCall.Location, target, args, DMValueType.Anything);
}

private ArgumentList BuildArgumentList(Location location, DMASTCallParameter[]? arguments, DreamPath? inferredPath = null) {
Expand Down Expand Up @@ -899,7 +904,7 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters, inferredPath);

var globalProcExpr = new GlobalProc(expr.Location, globalProc);
expr = new ProcCall(expr.Location, globalProcExpr, argumentList, DMValueType.Anything);
expr = new ProcCall(Compiler, expr.Location, globalProcExpr, argumentList, DMValueType.Anything);
break;

case DMASTDereference.FieldOperation:
Expand Down Expand Up @@ -1045,6 +1050,7 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
case DMASTDereference.CallOperation callOperation: {
var field = callOperation.Identifier;
var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters, inferredPath);
DreamPath? nextPath = null;

if (!callOperation.NoSearch && !pathIsFuzzy) {
if (prevPath == null) {
Expand All @@ -1056,6 +1062,14 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
if (!fromObject.HasProc(field))
return UnknownIdentifier(callOperation.Location, field);

var returnTypes = fromObject.GetProcReturnTypes(field, argumentList) ?? DMValueType.Anything;
nextPath = returnTypes.HasPath ? returnTypes.TypePath : returnTypes.AsPath();
if (!returnTypes.HasPath & nextPath.HasValue) {
var thePath = nextPath!.Value;
thePath.Type = DreamPath.PathType.UpwardSearch;
nextPath = thePath;
}

var procId = fromObject.GetProcs(field)![^1];
ObjectTree.AllProcs[procId].EmitUsageWarnings(callOperation.Location);
}
Expand All @@ -1064,10 +1078,11 @@ private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferre
Parameters = argumentList,
Safe = callOperation.Safe,
Identifier = field,
Path = null
Path = prevPath
};
prevPath = null;
pathIsFuzzy = true;
prevPath = nextPath;
if(prevPath is null)
pathIsFuzzy = true;
break;
}

Expand All @@ -1089,7 +1104,7 @@ private DMExpression BuildLocate(DMASTLocate locate, DreamPath? inferredPath) {
if (inferredPath == null)
return BadExpression(WarningCode.BadExpression, locate.Location, "inferred locate requires a type");

return new LocateInferred(locate.Location, inferredPath.Value, container);
return new LocateInferred(Compiler, locate.Location, inferredPath.Value, container);
}

var pathExpr = BuildExpression(locate.Expression, inferredPath);
Expand Down Expand Up @@ -1136,7 +1151,7 @@ private DMExpression BuildList(DMASTList list, DreamPath? inferredPath) {
if (list.IsAList) {
return new AList(list.Location, values!);
} else {
return new List(list.Location, values);
return new List(Compiler, list.Location, values);
}
}

Expand Down Expand Up @@ -1243,7 +1258,7 @@ private DMExpression BuildPick(DMASTPick pick, DreamPath? inferredPath) {
pickValues[i] = new Pick.PickValue(weight, value);
}

return new Pick(pick.Location, pickValues);
return new Pick(Compiler, pick.Location, pickValues);
}

private DMExpression BuildLog(DMASTLog log, DreamPath? inferredPath) {
Expand Down
Loading
Loading