 ast/stmt.go        |    7 +-
 env/env.go         |    7 +
 env/envValues.go   |   18 +
 parser/parser.go   | 1757 ++++++++++++++++++++++++++--------------------------
 parser/parser.go.y |   10 +
 vm/vm.go           |    5 +
 vm/vmLetExpr.go    |   11 +-
 vm/vmStmt.go       |  106 ++++
 8 files changed, 1048 insertions(+), 873 deletions(-)
diff --git a/ast/stmt.go b/ast/stmt.go
index e7a15a8..b1ff37b 100644
--- a/ast/stmt.go
+++ b/ast/stmt.go
@@ -109,10 +109,13 @@ type SwitchCaseStmt struct {
 }
 
 // VarStmt provide statement to let variables in current scope.
+// TypeData is set for typed declarations like "var x: int64 = 1" and is nil
+// for untyped declarations like "var x = 1".
 type VarStmt struct {
 	StmtImpl
-	Names []string
-	Exprs []Expr
+	Names    []string
+	TypeData *TypeStruct
+	Exprs    []Expr
 }
 
 // LetsStmt provide multiple statement of let.
diff --git a/env/env.go b/env/env.go
index d8faa70..e5fac41 100644
--- a/env/env.go
+++ b/env/env.go
@@ -21,6 +21,7 @@ type (
 		parent         *Env
 		values         map[string]reflect.Value
 		types          map[string]reflect.Type
+		typedBindings  map[string]reflect.Type
 		externalLookup ExternalLookup
 	}
 )
@@ -172,6 +173,12 @@ func (e *Env) Copy() *Env {
 			copy.types[name] = t
 		}
 	}
+	if e.typedBindings != nil {
+		copy.typedBindings = make(map[string]reflect.Type, len(e.typedBindings))
+		for name, t := range e.typedBindings {
+			copy.typedBindings[name] = t
+		}
+	}
 	e.rwMutex.RUnlock()
 	return &copy
 }
diff --git a/env/envValues.go b/env/envValues.go
index b8bf73e..5cbdbda 100644
--- a/env/envValues.go
+++ b/env/envValues.go
@@ -17,12 +17,17 @@ func (e *Env) Define(symbol string, value interface{}) error {
 }
 
 // DefineValue defines/sets reflect value to symbol in current scope.
+// The new binding is dynamically typed: any type constraint previously
+// recorded for symbol in the current scope is dropped.
 func (e *Env) DefineValue(symbol string, value reflect.Value) error {
 	if strings.Contains(symbol, ".") {
 		return ErrSymbolContainsDot
 	}
 	e.rwMutex.Lock()
 	e.values[symbol] = value
+	if e.typedBindings != nil {
+		delete(e.typedBindings, symbol)
+	}
 	e.rwMutex.Unlock()
 
 	return nil
@@ -55,11 +60,21 @@ func (e *Env) Set(symbol string, value interface{}) error {
 }
 
 // SetValue reflect value to the scope where symbol is frist found.
+// When a type constraint was recorded for symbol in that scope, the value is
+// checked against the constraint and a *TypeError is returned on mismatch.
 func (e *Env) SetValue(symbol string, value reflect.Value) error {
 	e.rwMutex.RLock()
 	_, ok := e.values[symbol]
+	typ, hasBinding := e.typedBindings[symbol]
 	e.rwMutex.RUnlock()
 	if ok {
+		if hasBinding {
+			var err error
+			value, err = CheckTypedAssignment(symbol, value, typ)
+			if err != nil {
+				return err
+			}
+		}
 		e.rwMutex.Lock()
 		e.values[symbol] = value
 		e.rwMutex.Unlock()
@@ -121,6 +136,9 @@ func (e *Env) GetValueSymbols() []string {
 func (e *Env) Delete(symbol string) {
 	e.rwMutex.Lock()
 	delete(e.values, symbol)
+	if e.typedBindings != nil {
+		delete(e.typedBindings, symbol)
+	}
 	e.rwMutex.Unlock()
 }
 
diff --git a/parser/parser.go.y b/parser/parser.go.y
index 2998fed..29243ba 100644
--- a/parser/parser.go.y
+++ b/parser/parser.go.y
@@ -261,6 +261,16 @@ stmt_var :
 		$$ = &ast.VarStmt{Names: $2, Exprs: $4}
 		$$.SetPosition($1.Position())
 	}
+	| VAR expr_idents ':' type_data '=' exprs
+	{
+		$$ = &ast.VarStmt{Names: $2, TypeData: $4, Exprs: $6}
+		$$.SetPosition($1.Position())
+	}
+	| VAR expr_idents ':' type_data
+	{
+		$$ = &ast.VarStmt{Names: $2, TypeData: $4}
+		$$.SetPosition($1.Position())
+	}
 
 stmt_lets :
 	expr '=' expr
diff --git a/vm/vm.go b/vm/vm.go
index 949a53b..0e61b44 100644
--- a/vm/vm.go
+++ b/vm/vm.go
@@ -13,6 +13,11 @@ import (
 // Options provides options to run VM with
 type Options struct {
 	Debug bool // run in Debug mode
+	// TypedBindings enforces the type constraints of typed variable
+	// declarations, like "var x: int64 = 1", on assignment.
+	// When disabled, typed declarations still parse and execute but
+	// assignments behave dynamically.
+	TypedBindings bool
 }
 
 type (
diff --git a/vm/vmLetExpr.go b/vm/vmLetExpr.go
index 0f2743e..3f7a9e3 100644
--- a/vm/vmLetExpr.go
+++ b/vm/vmLetExpr.go
@@ -1,6 +1,7 @@
 package vm
 
 import (
+	"errors"
 	"reflect"
 
 	"github.com/mattn/anko/ast"
@@ -12,7 +13,15 @@ func (runInfo *runInfoStruct) invokeLetExpr() {
 
 	// IdentExpr
 	case *ast.IdentExpr:
-		if runInfo.env.SetValue(expr.Lit, runInfo.rv) != nil {
+		err := runInfo.env.SetValue(expr.Lit, runInfo.rv)
+		if err != nil {
+			var typeError *env.TypeError
+			if errors.As(err, &typeError) {
+				// assignment does not satisfy the type constraint of the variable
+				runInfo.err = newError(expr, err)
+				runInfo.rv = nilValue
+				return
+			}
 			runInfo.err = nil
 			runInfo.env.DefineValue(expr.Lit, runInfo.rv)
 		}
diff --git a/vm/vmStmt.go b/vm/vmStmt.go
index 742eb27..e2aff26 100644
--- a/vm/vmStmt.go
+++ b/vm/vmStmt.go
@@ -97,6 +97,12 @@ func (runInfo *runInfoStruct) runSingleStmt() {
 
 	// VarStmt
 	case *ast.VarStmt:
+		if stmt.TypeData != nil {
+			// typed variable declaration, like "var x: int64 = 1"
+			runInfo.runVarStmtTyped(stmt)
+			return
+		}
+
 		// get right side expression values
 		rvs := make([]reflect.Value, len(stmt.Exprs))
 		var i int
@@ -801,3 +807,103 @@ func (runInfo *runInfoStruct) runSingleStmt() {
 	}
 
 }
+
+// runVarStmtTyped executes a var statement with a declared type, like
+// "var x: int64 = 1", "var x: int64", or "var a, b: int64 = 1, 2".
+// When the TypedBindings option is enabled, the initial values are checked
+// against the declared type and a type constraint is recorded for each new
+// binding. When the option is disabled, values are defined dynamically
+// without any constraint.
+func (runInfo *runInfoStruct) runVarStmtTyped(stmt *ast.VarStmt) {
+	typ := makeType(runInfo, stmt.TypeData)
+	if runInfo.err != nil {
+		runInfo.err = newError(stmt, runInfo.err)
+		runInfo.rv = nilValue
+		return
+	}
+	if typ == nil {
+		runInfo.err = newStringError(stmt, "cannot make type nil")
+		runInfo.rv = nilValue
+		return
+	}
+
+	if len(stmt.Exprs) == 0 {
+		// no initial values, initialize to the Go zero value of the declared type
+		for _, name := range stmt.Names {
+			if !runInfo.defineTypedVar(stmt, name, reflect.Zero(typ), typ) {
+				return
+			}
+		}
+		runInfo.rv = reflect.Zero(typ)
+		return
+	}
+
+	// get right side expression values
+	rvs := make([]reflect.Value, len(stmt.Exprs))
+	var i int
+	for i, runInfo.expr = range stmt.Exprs {
+		runInfo.invokeExpr()
+		if runInfo.err != nil {
+			return
+		}
+		if e, ok := runInfo.rv.Interface().(*env.Env); ok {
+			rvs[i] = reflect.ValueOf(e.DeepCopy())
+		} else {
+			rvs[i] = runInfo.rv
+		}
+	}
+
+	if len(rvs) == 1 && len(stmt.Names) > 1 {
+		// only one right side value but many left side names
+		value := rvs[0]
+		if value.Kind() == reflect.Interface && !value.IsNil() {
+			value = value.Elem()
+		}
+		if (value.Kind() == reflect.Slice || value.Kind() == reflect.Array) && value.Len() > 0 {
+			// value is slice/array, add each value to left side names
+			for i := 0; i < value.Len() && i < len(stmt.Names); i++ {
+				if !runInfo.defineTypedVar(stmt, stmt.Names[i], value.Index(i), typ) {
+					return
+				}
+			}
+			// return last value of slice/array
+			runInfo.rv = value.Index(value.Len() - 1)
+			return
+		}
+	}
+
+	// define all names with right side values
+	for i = 0; i < len(rvs) && i < len(stmt.Names); i++ {
+		if !runInfo.defineTypedVar(stmt, stmt.Names[i], rvs[i], typ) {
+			return
+		}
+	}
+
+	// return last right side value
+	runInfo.rv = rvs[len(rvs)-1]
+}
+
+// defineTypedVar defines name with value in the current scope for a typed
+// variable declaration. It returns false when a type error occurred.
+// Each var declaration creates a new binding that does not inherit any
+// existing constraint. The blank identifier is exempt from constraint
+// checking.
+func (runInfo *runInfoStruct) defineTypedVar(stmt *ast.VarStmt, name string, value reflect.Value, typ reflect.Type) bool {
+	if value.Kind() == reflect.Interface && !value.IsNil() {
+		value = value.Elem()
+	}
+
+	if !runInfo.options.TypedBindings || name == "_" {
+		runInfo.env.DefineValue(name, value)
+		return true
+	}
+
+	value, err := env.CheckTypedAssignment(name, value, typ)
+	if err != nil {
+		runInfo.err = newError(stmt, err)
+		runInfo.rv = nilValue
+		return false
+	}
+	runInfo.env.DefineTypedValue(name, value, typ)
+	return true
+}
