diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp
index 5e1316d71..ae26bc37d 100644
--- a/src/dobjtype.cpp
+++ b/src/dobjtype.cpp
@@ -74,6 +74,7 @@ TArray<PClass *> PClass::AllClasses;
 bool PClass::bShutdown;
 
 PErrorType *TypeError;
+PErrorType *TypeAuto;
 PVoidType *TypeVoid;
 PInt *TypeSInt8,  *TypeUInt8;
 PInt *TypeSInt16, *TypeUInt16;
@@ -570,6 +571,7 @@ void PType::StaticInit()
 
 	// Create types and add them type the type table.
 	TypeTable.AddType(TypeError = new PErrorType);
+	TypeTable.AddType(TypeAuto = new PErrorType(2));
 	TypeTable.AddType(TypeVoid = new PVoidType);
 	TypeTable.AddType(TypeSInt8 = new PInt(1, false));
 	TypeTable.AddType(TypeUInt8 = new PInt(1, true));
diff --git a/src/dobjtype.h b/src/dobjtype.h
index ad626ee8b..c310f9863 100644
--- a/src/dobjtype.h
+++ b/src/dobjtype.h
@@ -370,7 +370,7 @@ class PErrorType : public PType
 {
 	DECLARE_CLASS(PErrorType, PType);
 public:
-	PErrorType() : PType(0, 1) {}
+	PErrorType(int which = 1) : PType(0, which) {}
 };
 
 class PVoidType : public PType
@@ -930,6 +930,7 @@ PPrototype *NewPrototype(const TArray<PType *> &rettypes, const TArray<PType *>
 // Built-in types -----------------------------------------------------------
 
 extern PErrorType *TypeError;
+extern PErrorType *TypeAuto;
 extern PVoidType *TypeVoid;
 extern PInt *TypeSInt8,  *TypeUInt8;
 extern PInt *TypeSInt16, *TypeUInt16;
diff --git a/src/namedef.h b/src/namedef.h
index c3866a0be..62d513437 100644
--- a/src/namedef.h
+++ b/src/namedef.h
@@ -721,6 +721,7 @@ xx(State)
 xx(Fixed)
 xx(Vector2)
 xx(Vector3)
+xx(let)
 
 xx(Min)
 xx(Max)
diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re
index a57cc34ad..8e4b42f69 100644
--- a/src/sc_man_scanner.re
+++ b/src/sc_man_scanner.re
@@ -183,6 +183,7 @@ std2:
 		'deprecated'				{ RET(TK_Deprecated); }
 		'action'					{ RET(TK_Action); }
 		'readonly'					{ RET(TK_ReadOnly); }
+		'let'						{ RET(TK_Let); }
 
 		/* Actor state options */
 		'bright'					{ RET(StateOptions ? TK_Bright : TK_Identifier); }
diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h
index c1ba10044..efa479cf3 100644
--- a/src/sc_man_tokens.h
+++ b/src/sc_man_tokens.h
@@ -142,4 +142,5 @@ xx(TK_NoDelay,				"'nodelay'")
 xx(TK_Offset,				"'offset'")
 xx(TK_Slow,					"'slow'")
 xx(TK_Bright,				"'bright'")
+xx(TK_Let,					"'let'")
 #undef xx
diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp
index a0491f69d..0402ed9ab 100644
--- a/src/scripting/codegeneration/codegen.cpp
+++ b/src/scripting/codegeneration/codegen.cpp
@@ -9964,17 +9964,43 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx)
 		delete this;
 		return nullptr;
 	}
-	if (ValueType->RegType == REGT_NIL)
+	if (ValueType->RegType == REGT_NIL && ValueType != TypeAuto)
 	{
 		auto sfunc = static_cast<VMScriptFunction *>(ctx.Function->Variants[0].Implementation);
 		StackOffset = sfunc->AllocExtraStack(ValueType);
 		// Todo: Process the compound initializer once implemented.
+		if (Init != nullptr)
+		{
+			ScriptPosition.Message(MSG_ERROR, "Cannot initialize non-scalar variable %s here", Name.GetChars());
+			delete this;
+			return nullptr;
+		}
 	}
-	else
+	else if (ValueType !=TypeAuto)
 	{
 		if (Init) Init = new FxTypeCast(Init, ValueType, false);
 		SAFE_RESOLVE_OPT(Init, ctx);
 	}
+	else
+	{
+		if (Init == nullptr)
+		{
+			ScriptPosition.Message(MSG_ERROR, "Automatic type deduction requires an initializer for variable %s", Name.GetChars());
+			delete this;
+			return nullptr;
+		}
+		SAFE_RESOLVE_OPT(Init, ctx);
+		if (Init->ValueType->RegType == REGT_NIL)
+		{
+			ScriptPosition.Message(MSG_ERROR, "Cannot initialize non-scalar variable %s here", Name.GetChars());
+			delete this;
+			return nullptr;
+		}
+		ValueType = Init->ValueType;
+		// check for undersized ints and floats. These are not allowed as local variables.
+		if (IsInteger() && ValueType->Align < sizeof(int)) ValueType = TypeSInt32;
+		else if (IsFloat() && ValueType->Align < sizeof(double)) ValueType = TypeFloat64;
+	}
 	if (Name != NAME_None)
 	{
 		for (auto l : ctx.Block->LocalVars)
diff --git a/src/scripting/zscript/zcc-parse.lemon b/src/scripting/zscript/zcc-parse.lemon
index 5ba5a204e..9305b9f71 100644
--- a/src/scripting/zscript/zcc-parse.lemon
+++ b/src/scripting/zscript/zcc-parse.lemon
@@ -704,6 +704,7 @@ type_name1(X) ::= NAME(T).					{ X.Int = ZCC_Name; X.SourceLoc = T.SourceLoc; }
 type_name1(X) ::= SOUND(T).					{ X.Int = ZCC_Sound; X.SourceLoc = T.SourceLoc; }
 type_name1(X) ::= STATE(T).					{ X.Int = ZCC_State; X.SourceLoc = T.SourceLoc; }
 type_name1(X) ::= COLOR(T).					{ X.Int = ZCC_Color; X.SourceLoc = T.SourceLoc; }
+type_name1(X) ::= LET(T).					{ X.Int = ZCC_Let; X.SourceLoc = T.SourceLoc; }
 
 type_name(X) ::= type_name1(A).
 {
diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp
index 98a1a5ff3..856802a97 100644
--- a/src/scripting/zscript/zcc_compile.cpp
+++ b/src/scripting/zscript/zcc_compile.cpp
@@ -1449,6 +1449,10 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n
 			retval = TypeSound;
 			break;
 
+		case ZCC_Let:
+			retval = TypeAuto;
+			break;
+
 		case ZCC_UserType:
 			// statelabel et.al. are not tokens - there really is no need to, it works just as well as an identifier. Maybe the same should be done for some other types, too?
 			switch (btype->UserType->Id)
diff --git a/src/scripting/zscript/zcc_parser.cpp b/src/scripting/zscript/zcc_parser.cpp
index 8a1618127..44f72da28 100644
--- a/src/scripting/zscript/zcc_parser.cpp
+++ b/src/scripting/zscript/zcc_parser.cpp
@@ -201,6 +201,7 @@ static void InitTokenMap()
 	TOKENDEF2(TK_State,			ZCC_STATE,		NAME_State);
 	TOKENDEF2(TK_Color,			ZCC_COLOR,		NAME_Color);
 	TOKENDEF2(TK_Sound,			ZCC_SOUND,		NAME_Sound);
+	TOKENDEF2(TK_Let,			ZCC_LET,		NAME_let);
 
 	TOKENDEF (TK_Identifier,	ZCC_IDENTIFIER);
 	TOKENDEF (TK_StringConst,	ZCC_STRCONST);
diff --git a/src/scripting/zscript/zcc_parser.h b/src/scripting/zscript/zcc_parser.h
index 7a763b2c1..d02b1a12f 100644
--- a/src/scripting/zscript/zcc_parser.h
+++ b/src/scripting/zscript/zcc_parser.h
@@ -131,6 +131,7 @@ enum EZCCBuiltinType
 	ZCC_Sound,
 
 	ZCC_UserType,
+	ZCC_Let,
 
 	ZCC_NUM_BUILT_IN_TYPES
 };