Repository: openconfig/goyang (go)

Issue:
allow default substatments for leaf-list
Currently its not allowed, as it is not present in YANG 1.0 RFC,
YANG 1.1 adds `default` substatement to leaf-lists

Pull request description:
added support for leaf-list default substatement #136
Hi @robshakir,
I've approached the issue described in #136 
Let me plz know if that PR satisfies the requirements of the feature and contribution guidelines.

closes #136 

-----------------
Cumulative summary after edits by @wenovus:
- `LeafList` now has a `Default []*Value` field.
- `Entry.Default` changed to []string .
- `Entry.DefaultValue() string` -> `Entry.SingleDefaultValue() (string, bool)` & `Entry.DefaultValues() []string`
  - Note that `Entry.SingleDefaultValue() (string, bool)` only returns true when there is one and only one default value. This includes the case when a leaf-list has a single default value.
- new field `YangType.HasDefault` to determine whether the empty string is a default value.

**NOTE**: This is a backwards-incompatible change (see summary)

Patch:
diff --git a/pkg/yang/entry.go b/pkg/yang/entry.go
index 22e54f4..371466f 100644
--- a/pkg/yang/entry.go
+++ b/pkg/yang/entry.go
@@ -79,17 +79,20 @@ type deviationPresence struct {
 // Errors is not nil then it means semantic errors existed while converting the
 // AST, in which case the only other valid field other than Errors is Node.
 type Entry struct {
-	Parent      *Entry    `json:"-"`
-	Node        Node      `json:"-"` // the base node this Entry was derived from.
-	Name        string    // our name, same as the key in our parent Dirs
-	Description string    `json:",omitempty"` // description from node, if any
-	Default     string    `json:",omitempty"` // default from node, if any
-	Units       string    `json:",omitempty"` // units associated with the type, if any
-	Errors      []error   `json:"-"`          // list of errors encountered on this node
-	Kind        EntryKind // kind of Entry
-	Config      TriState  // config state of this entry, if known
-	Prefix      *Value    `json:",omitempty"` // prefix to use from this point down
-	Mandatory   TriState  `json:",omitempty"` // whether this entry is mandatory in the tree
+	Parent      *Entry `json:"-"`
+	Node        Node   `json:"-"` // the base node this Entry was derived from.
+	Name        string // our name, same as the key in our parent Dirs
+	Description string `json:",omitempty"` // description from node, if any
+	// Default value for the node, if any. Note that only leaf-lists may
+	// have more than one value. For all other types, use the
+	// SingleDefaultValue() method to access the default value.
+	Default   []string  `json:",omitempty"`
+	Units     string    `json:",omitempty"` // units associated with the type, if any
+	Errors    []error   `json:"-"`          // list of errors encountered on this node
+	Kind      EntryKind // kind of Entry
+	Config    TriState  // config state of this entry, if known
+	Prefix    *Value    `json:",omitempty"` // prefix to use from this point down
+	Mandatory TriState  `json:",omitempty"` // whether this entry is mandatory in the tree
 
 	// Fields associated with directory nodes
 	Dir map[string]*Entry `json:",omitempty"`
@@ -576,7 +579,7 @@ func ToEntry(n Node) (e *Entry) {
 			e.Description = s.Description.Name
 		}
 		if s.Default != nil {
-			e.Default = s.Default.Name
+			e.Default = []string{s.Default.Name}
 		}
 		e.Type = s.Type.YangType
 		e.Config, err = tristateValue(s.Config)
@@ -615,6 +618,11 @@ func ToEntry(n Node) (e *Entry) {
 		if e.ListAttr.MinElements, err = semCheckMinElements(s.MinElements); err != nil {
 			e.addError(err)
 		}
+		if len(s.Default) != 0 {
+			for _, def := range s.Default {
+				e.Default = append(e.Default, def.Name)
+			}
+		}
 		e.Prefix = getRootPrefix(e)
 		return e
 	case *Uses:
@@ -650,7 +658,7 @@ func ToEntry(n Node) (e *Entry) {
 	case *Choice:
 		e.Kind = ChoiceEntry
 		if s.Default != nil {
-			e.Default = s.Default.Name
+			e.Default = []string{s.Default.Name}
 		}
 	case *Case:
 		e.Kind = CaseEntry
@@ -866,18 +874,31 @@ func ToEntry(n Node) (e *Entry) {
 		// Keywords that do not need to be handled as an Entry as they are added
 		// to other dictionaries.
 		case "default":
-			d, ok := fv.Interface().(*Value)
-			if !ok {
-				e.addError(fmt.Errorf("%s: unexpected default type in %s:%s", Source(n), n.Kind(), n.NName()))
+			switch e.Kind {
+			case LeafEntry, ChoiceEntry:
+				// default is handled separately for leaf, leaf-list and choice
+			case DeviateEntry:
+				// handle deviate statements.
+				// TODO(wenovus): support refine statement's default substatement.
+				d, ok := fv.Interface().(*Value)
+				if !ok {
+					e.addError(fmt.Errorf("%s: unexpected default type in %s:%s", Source(n), n.Kind(), n.NName()))
+				}
+				// TODO(wenovus): deviate statement and refine statement should
+				// allow multiple default substatements for leaf-list types (YANG1.1).
+				if d != nil {
+					e.Default = []string{d.asString()}
+				}
 			}
-			e.Default = d.asString()
 		case "typedef":
 			continue
 		case "deviation":
 			if a := fv.Interface().([]*Deviation); a != nil {
 				for _, d := range a {
+					deviatedEntry := ToEntry(d)
+					e.importErrors(deviatedEntry)
 					e.Deviations = append(e.Deviations, &DeviatedEntry{
-						Entry:        ToEntry(d),
+						Entry:        deviatedEntry,
 						DeviatedPath: d.Statement().Argument,
 					})
 
@@ -1095,16 +1116,21 @@ func (e *Entry) ApplyDeviate() []error {
 						deviatedNode.Config = devSpec.Config
 					}
 
-					if devSpec.Default != "" {
+					if len(devSpec.Default) > 0 {
 						switch dt {
 						case DeviationAdd:
-							if deviatedNode.Default != "" {
-								appendErr(fmt.Errorf("tried to deviate add a default statement when one already exists: %q", deviatedNode.Default))
-							} else {
-								deviatedNode.Default = devSpec.Default
+							switch {
+							case deviatedNode.IsLeafList():
+								deviatedNode.Default = append(deviatedNode.Default, devSpec.Default...)
+							case len(devSpec.Default) > 1:
+								appendErr(fmt.Errorf("%s: tried to add more than one default to a non-leaflist entry at deviation", Source(e.Node)))
+							case len(deviatedNode.Default) != 0:
+								appendErr(fmt.Errorf("%s: tried to add a default value to an entry that already has a default value", Source(e.Node)))
+							case len(devSpec.Default) == 1 && len(deviatedNode.Default) == 0:
+								deviatedNode.Default = append([]string{}, devSpec.Default[0])
 							}
 						case DeviationReplace:
-							deviatedNode.Default = devSpec.Default
+							deviatedNode.Default = append([]string{}, devSpec.Default...)
 						}
 					}
 
@@ -1148,11 +1174,19 @@ func (e *Entry) ApplyDeviate() []error {
 						deviatedNode.Config = TSUnset
 					}
 
-					if devSpec.Default != "" {
-						if devSpec.Default == deviatedNode.Default {
-							deviatedNode.Default = ""
-						} else {
-							appendErr(fmt.Errorf("%s: tried to deviate delete a default statement that doesn't exist or with a non-matching keyword", Source(e.Node)))
+					if len(devSpec.Default) > 0 {
+						switch {
+						case deviatedNode.IsLeafList():
+							// It is unclear from RFC7950 on how deviate delete works
+							// when there are duplicate leaf-list values in config-false leafs.
+							// TODO(wenbli): Add support for deleting default values when the leaf-list is a config leaf (duplicates are not allowed).
+							appendErr(fmt.Errorf("%s: deviate delete on default statements unsupported for leaf-lists, please use replace instead", Source(e.Node)))
+						case len(deviatedNode.Default) == 0:
+							appendErr(fmt.Errorf("%s: tried to deviate delete a default statement that doesn't exist", Source(e.Node)))
+						case devSpec.Default[0] != deviatedNode.Default[0]:
+							appendErr(fmt.Errorf("%s: tried to deviate delete a default statement with a non-matching keyword", Source(e.Node)))
+						default:
+							deviatedNode.Default = nil
 						}
 					}
 
@@ -1522,17 +1556,40 @@ func errorSort(errors []error) []error {
 	return errors[:i]
 }
 
-// DefaultValue returns the schema default value for e, if any. If the leaf
-// has no explicit default, its type default (if any) will be used.
-func (e *Entry) DefaultValue() string {
-	if e.Default != "" {
-		return e.Default
-	} else if typ := e.Type; typ != nil {
-		if leaf, ok := e.Node.(*Leaf); ok {
-			if leaf.Mandatory == nil || leaf.Mandatory.Name == "false" {
-				return typ.Default
+// SingleDefaultValue returns the single schema default value for e and a bool
+// indicating whether the entry contains one and only one default value. The
+// empty string is returned when the entry has zero or multiple default values.
+// This function is useful for determining the default values of a
+// non-leaf-list leaf entry.  If the leaf has no explicit default, its type
+// default (if any) will be used.
+//
+// For a leaf-list entry, use DefaultValues() instead.
+func (e *Entry) SingleDefaultValue() (string, bool) {
+	if dvals := e.DefaultValues(); len(dvals) == 1 {
+		return dvals[0], true
+	}
+	return "", false
+}
+
+// DefaultValues returns all default values for the leaf entry. This is useful
+// for determining the default values for a leaf-list, which may have more than
+// one default value. If the entry has no explicit default, its type default
+// (if any) will be used. nil is returned when no default value exists.
+//
+// For a leaf entry, use SingleDefaultValue() instead.
+func (e *Entry) DefaultValues() []string {
+	if len(e.Default) > 0 {
+		return append([]string{}, e.Default...)
+	}
+
+	if typ := e.Type; typ != nil && typ.HasDefault {
+		switch leaf := e.Node.(type) {
+		case *Leaf:
+			switch {
+			case e.IsLeaf() && (leaf.Mandatory == nil || leaf.Mandatory.Name == "false"), e.IsLeafList() && e.ListAttr.MinElements == 0:
+				return []string{typ.Default}
 			}
 		}
 	}
-	return ""
+	return nil
 }
diff --git a/pkg/yang/types.go b/pkg/yang/types.go
index a377e9a..e201027 100644
--- a/pkg/yang/types.go
+++ b/pkg/yang/types.go
@@ -145,6 +145,7 @@ func (t *Typedef) resolve(d *typeDictionary) []error {
 		y.Units = t.Units.Name
 	}
 	if t.Default != nil {
+		y.HasDefault = true
 		y.Default = t.Default.Name
 	}
 
diff --git a/pkg/yang/yang.go b/pkg/yang/yang.go
index 57df09b..4f62801 100644
--- a/pkg/yang/yang.go
+++ b/pkg/yang/yang.go
@@ -404,7 +404,9 @@ func (s *Leaf) NName() string         { return s.Name }
 func (s *Leaf) Statement() *Statement { return s.Source }
 func (s *Leaf) Exts() []*Statement    { return s.Extensions }
 
-// A LeafList is defined in: http://tools.ietf.org/html/rfc6020#section-7.7
+// A LeafList is defined in:
+// YANG 1:   http://tools.ietf.org/html/rfc6020#section-7.7
+// YANG 1.1: https://tools.ietf.org/html/rfc7950#section-7.7
 // It this is supposed to be an array of nodes..
 type LeafList struct {
 	Name       string       `yang:"Name,nomerge"`
@@ -413,6 +415,7 @@ type LeafList struct {
 	Extensions []*Statement `yang:"Ext"`
 
 	Config      *Value   `yang:"config"`
+	Default     []*Value `yang:"default"`
 	Description *Value   `yang:"description"`
 	IfFeature   []*Value `yang:"if-feature"`
 	MaxElements *Value   `yang:"max-elements"`
diff --git a/pkg/yang/yangtype.go b/pkg/yang/yangtype.go
index 62b3214..6795adb 100644
--- a/pkg/yang/yangtype.go
+++ b/pkg/yang/yangtype.go
@@ -241,6 +241,7 @@ type YangType struct {
 	Enum             *EnumType   `json:",omitempty"` // enum name to value, "status" is lost
 	Units            string      `json:",omitempty"` // units to be used for this type
 	Default          string      `json:",omitempty"` // default value, if any
+	HasDefault       bool        `json:",omitempty"` // whether the type has a default.
 	FractionDigits   int         `json:",omitempty"` // decimal64 fixed point precision
 	Length           YangRange   `json:",omitempty"` // this should be processed by section 12
 	OptionalInstance bool        `json:",omitempty"` // !require-instances which defaults to true
@@ -263,6 +264,7 @@ func (y *YangType) Equal(t *YangType) bool {
 		y.Kind != t.Kind,
 		y.Units != t.Units,
 		y.Default != t.Default,
+		y.HasDefault != t.HasDefault,
 		y.FractionDigits != t.FractionDigits,
 		y.IdentityBase != t.IdentityBase,
 		len(y.Length) != len(t.Length),


Test patch:
diff --git a/pkg/yang/entry_test.go b/pkg/yang/entry_test.go
index cd457fb..b1c1cc6 100644
--- a/pkg/yang/entry_test.go
+++ b/pkg/yang/entry_test.go
@@ -1058,6 +1058,11 @@ module defaults {
     default "typedef default value";
   }
 
+  typedef string-emptydefault {
+    type string;
+    default "";
+  }
+
   grouping common {
     container common-nodefault {
       leaf string {
@@ -1070,11 +1075,22 @@ module defaults {
         default "default value";
       }
     }
+    container common-withemptydefault {
+      leaf string {
+        type string;
+        default "";
+      }
+    }
     container common-typedef-withdefault {
       leaf string {
         type string-default;
       }
     }
+    container common-typedef-withemptydefault {
+      leaf string {
+        type string-emptydefault;
+      }
+    }
   }
 
   container defaults {
@@ -1109,6 +1125,51 @@ module defaults {
     }
   }
 
+  grouping leaflist-common {
+    container common-nodefault {
+      leaf string {
+        type string;
+      }
+    }
+    container common-withdefault {
+      leaf-list string {
+        type string;
+        default "default value";
+      }
+    }
+    container common-typedef-withdefault {
+      leaf string {
+        type string-default;
+      }
+    }
+  }
+
+  container leaflist-defaults {
+    leaf-list uint32-withdefault {
+      type uint32;
+      default "13";
+      default 14;
+    }
+    leaf-list stringlist-withdefault {
+      type string-default;
+    }
+    leaf-list stringlist-withemptydefault {
+      type string-emptydefault;
+    }
+    leaf-list stringlist-withdefault-withminelem {
+      type string-default;
+      min-elements 1;
+    }
+    leaf-list emptydefault {
+      type string;
+      default "";
+    }
+    leaf-list nodefault {
+      type string;
+    }
+    uses leaflist-common;
+  }
+
 }
 `
 
@@ -1118,40 +1179,117 @@ module defaults {
 	}
 
 	for i, tc := range []struct {
-		want string
-		path []string
+		wantSingle   string
+		wantSingleOk bool
+		wantDefaults []string
+		path         []string
 	}{
 		{
-			path: []string{"defaults", "string-withdefault"},
-			want: "typedef default value",
+			path:         []string{"defaults", "string-withdefault"},
+			wantSingle:   "typedef default value",
+			wantDefaults: []string{"typedef default value"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "uint32-withdefault"},
+			wantSingle:   "13",
+			wantDefaults: []string{"13"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "nodefault"},
+			wantSingle:   "",
+			wantDefaults: nil,
+		},
+		{
+			path:         []string{"defaults", "common-withdefault", "string"},
+			wantSingle:   "default value",
+			wantDefaults: []string{"default value"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "common-withemptydefault", "string"},
+			wantSingle:   "",
+			wantDefaults: []string{""},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "common-typedef-withdefault", "string"},
+			wantSingle:   "typedef default value",
+			wantDefaults: []string{"typedef default value"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "common-typedef-withemptydefault", "string"},
+			wantSingle:   "",
+			wantDefaults: []string{""},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"defaults", "common-nodefault", "string"},
+			wantSingle:   "",
+			wantDefaults: nil,
+		},
+		{
+			path:         []string{"defaults", "mandatory-default"},
+			wantSingle:   "",
+			wantDefaults: nil,
+		},
+		{
+			path:         []string{"defaults", "choice-default"},
+			wantSingle:   "zeta",
+			wantDefaults: []string{"zeta"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"leaflist-defaults", "uint32-withdefault"},
+			wantSingle:   "",
+			wantDefaults: []string{"13", "14"},
 		},
 		{
-			path: []string{"defaults", "uint32-withdefault"},
-			want: "13",
+			path:         []string{"leaflist-defaults", "stringlist-withdefault"},
+			wantSingle:   "typedef default value",
+			wantDefaults: []string{"typedef default value"},
+			wantSingleOk: true,
 		},
 		{
-			path: []string{"defaults", "nodefault"},
-			want: "",
+			path:         []string{"leaflist-defaults", "stringlist-withemptydefault"},
+			wantSingle:   "",
+			wantDefaults: []string{""},
+			wantSingleOk: true,
 		},
 		{
-			path: []string{"defaults", "common-withdefault", "string"},
-			want: "default value",
+			path:         []string{"leaflist-defaults", "stringlist-withdefault-withminelem"},
+			wantSingle:   "",
+			wantDefaults: nil,
 		},
 		{
-			path: []string{"defaults", "common-typedef-withdefault", "string"},
-			want: "typedef default value",
+			path:         []string{"leaflist-defaults", "emptydefault"},
+			wantSingle:   "",
+			wantDefaults: []string{""},
+			wantSingleOk: true,
 		},
 		{
-			path: []string{"defaults", "common-nodefault", "string"},
-			want: "",
+			path:         []string{"leaflist-defaults", "nodefault"},
+			wantSingle:   "",
+			wantDefaults: nil,
 		},
 		{
-			path: []string{"defaults", "mandatory-default"},
-			want: "",
+			path:         []string{"leaflist-defaults", "common-nodefault", "string"},
+			wantSingle:   "",
+			wantDefaults: nil,
 		},
 		{
-			path: []string{"defaults", "choice-default"},
-			want: "zeta",
+			path:         []string{"leaflist-defaults", "common-withdefault", "string"},
+			wantSingle:   "default value",
+			wantDefaults: []string{"default value"},
+			wantSingleOk: true,
+		},
+		{
+			path:         []string{"leaflist-defaults", "common-typedef-withdefault", "string"},
+			wantSingle:   "typedef default value",
+			wantDefaults: []string{"typedef default value"},
+			wantSingleOk: true,
 		},
 	} {
 		tname := strings.Join(tc.path, "/")
@@ -1165,8 +1303,11 @@ module defaults {
 		if err != nil {
 			t.Fatalf("[%d_%s] could not retrieve path: %v", i, tname, err)
 		}
-		if got := dir.DefaultValue(); tc.want != got {
-			t.Errorf("[%d_%s] want DefaultValue %q, got %q", i, tname, tc.want, got)
+		if got, gotOk := dir.SingleDefaultValue(); got != tc.wantSingle || gotOk != tc.wantSingleOk {
+			t.Errorf("[%d_%s] got SingleDefaultValue (%q, %v), want (%q, %v)", i, tname, got, gotOk, tc.wantSingle, tc.wantSingleOk)
+		}
+		if diff := cmp.Diff(dir.DefaultValues(), tc.wantDefaults); diff != "" {
+			t.Errorf("[%d_%s] DefaultValues (-got, +want):\n%s", i, tname, diff)
 		}
 	}
 }
@@ -2835,7 +2976,22 @@ func TestDeviation(t *testing.T) {
 			}, {
 				path: "/target/add/default",
 				entry: &Entry{
-					Default: "a default value",
+					Default: []string{"a default value"},
+				},
+			}, {
+				path: "/target/add/default-typedef",
+				entry: &Entry{
+					Default: nil,
+				},
+			}, {
+				path: "/target/add/default-list",
+				entry: &Entry{
+					Default: []string{"foo", "bar", "foo"},
+				},
+			}, {
+				path: "/target/add/default-list-typedef-default",
+				entry: &Entry{
+					Default: nil,
 				},
 			}, {
 				path: "/target/add/mandatory",
@@ -2901,7 +3057,7 @@ func TestDeviation(t *testing.T) {
 					}
 				}`,
 		},
-		wantProcessErrSubstring: "already exists",
+		wantProcessErrSubstring: "already has a default value",
 	}, {
 		desc: "error case - deviate type not recognized",
 		inFiles: map[string]string{
@@ -3067,7 +3223,12 @@ func TestDeviation(t *testing.T) {
 			}, {
 				path: "/target/replace/default",
 				entry: &Entry{
-					Default: "a default value",
+					Default: []string{"a default value"},
+				},
+			}, {
+				path: "/target/replace/default-list",
+				entry: &Entry{
+					Default: []string{"nematodes"},
 				},
 			}, {
 				path: "/target/replace/mandatory",
@@ -3177,6 +3338,29 @@ func TestDeviation(t *testing.T) {
 				},
 			}},
 		},
+	}, {
+		// TODO(wenovus): Support deviate delete for leaf-lists for config-false leafs once its semantics are clear.
+		// https://github.com/mbj4668/pyang/issues/756
+		desc: "error case - deviation delete on a leaf-list",
+		inFiles: map[string]string{
+			"deviate": `
+				module deviate {
+					prefix "d";
+					namespace "urn:d";
+
+					leaf-list a {
+						type string;
+						default "fish";
+					}
+
+					deviation /a {
+						deviate delete {
+							default "fishsticks";
+						}
+					}
+				}`,
+		},
+		wantProcessErrSubstring: "deviate delete on default statements unsupported for leaf-lists",
 	}, {
 		desc: "error case - deviation delete of default has different keyword value",
 		inFiles: map[string]string{
@@ -3198,6 +3382,26 @@ func TestDeviation(t *testing.T) {
 				}`,
 		},
 		wantProcessErrSubstring: "non-matching keyword",
+	}, {
+		desc: "error case - deviation delete where the default didn't exist",
+		inFiles: map[string]string{
+			"deviate": `
+				module deviate {
+					prefix "d";
+					namespace "urn:d";
+
+					leaf a {
+						type string;
+					}
+
+					deviation /a {
+						deviate delete {
+							default "fishsticks";
+						}
+					}
+				}`,
+		},
+		wantProcessErrSubstring: "default statement that doesn't exist",
 	}, {
 		desc: "error case - deviation delete of min-elements has different keyword value",
 		inFiles: map[string]string{
@@ -3336,18 +3540,25 @@ func TestDeviation(t *testing.T) {
 				}
 			}
 
-			if errs := ms.Process(); len(errs) > 0 {
-				var match bool
-				for _, err := range errs {
-					if diff := errdiff.Substring(err, tt.wantProcessErrSubstring); diff == "" {
-						match = true
-						break
-					}
-				}
-				if !match {
-					t.Fatalf("got errs: %v, want: %v", errs, tt.wantProcessErrSubstring)
+			errs := ms.Process()
+			if len(errs) == 0 {
+				// Add a nil error to compare against the wanted error string.
+				errs = append(errs, nil)
+			}
+			var match bool
+			for _, err := range errs {
+				if diff := errdiff.Substring(err, tt.wantProcessErrSubstring); diff == "" {
+					match = true
+					break
 				}
 			}
+			if !match {
+				t.Fatalf("got errs: %v, want: %v", errs, tt.wantProcessErrSubstring)
+			}
+
+			if tt.wantProcessErrSubstring == "" && len(tt.wants) == 0 {
+				t.Fatalf("test case expects no error and no entry. Please change your test case to contain one of them.")
+			}
 
 			for mod, tcs := range tt.wants {
 				m, errs := ms.GetModule(mod)
@@ -3373,8 +3584,8 @@ func TestDeviation(t *testing.T) {
 						t.Errorf("%d (%s): did not get expected config statement, got: %v, want: %v", idx, want.path, got.Config, want.entry.Config)
 					}
 
-					if got.Default != want.entry.Default {
-						t.Errorf("%d (%s): did not get expected default statement, got: %v, want: %v", idx, want.path, got.Default, want.entry.Default)
+					if diff := cmp.Diff(got.Default, want.entry.Default, cmpopts.EquateEmpty()); diff != "" {
+						t.Errorf("%d (%s): did not get expected default statement, (-got, +want): %s", idx, want.path, diff)
 					}
 
 					if got.Mandatory != want.entry.Mandatory {
diff --git a/pkg/yang/marshal_test.go b/pkg/yang/marshal_test.go
index 4a33430..f77582e 100644
--- a/pkg/yang/marshal_test.go
+++ b/pkg/yang/marshal_test.go
@@ -37,7 +37,7 @@ func TestMarshalJSON(t *testing.T) {
 				Name: "leaf",
 			},
 			Description: "This is a fake leaf.",
-			Default:     "default-leaf-value",
+			Default:     []string{"default-leaf-value"},
 			Errors:      []error{fmt.Errorf("error one")},
 			Kind:        LeafEntry,
 			Config:      TSTrue,
@@ -61,7 +61,9 @@ func TestMarshalJSON(t *testing.T) {
 		want: `{
   "Name": "leaf",
   "Description": "This is a fake leaf.",
-  "Default": "default-leaf-value",
+  "Default": [
+    "default-leaf-value"
+  ],
   "Kind": 0,
   "Config": 1,
   "Prefix": {
diff --git a/pkg/yang/testdata/deviate-replace.yang b/pkg/yang/testdata/deviate-replace.yang
index 93a93ee..9f1166e 100644
--- a/pkg/yang/testdata/deviate-replace.yang
+++ b/pkg/yang/testdata/deviate-replace.yang
@@ -11,6 +11,11 @@ module deviate {
             type string;
             default "fish";
         }
+        leaf-list default-list {
+            type string;
+            default "fish";
+            default "sticks";
+        }
         leaf mandatory {
             type string;
             mandatory false;
@@ -56,6 +61,12 @@ module deviate {
         }
     }
 
+    deviation /target/replace/default-list {
+        deviate replace {
+            default "nematodes";
+        }
+    }
+
     deviation /target/replace/mandatory {
         deviate replace {
             mandatory true;
@@ -92,4 +103,4 @@ module deviate {
             units "fish per second";
         }
     }
-}
\ No newline at end of file
+}
diff --git a/pkg/yang/testdata/deviate.yang b/pkg/yang/testdata/deviate.yang
index f82234c..d8fbaa6 100644
--- a/pkg/yang/testdata/deviate.yang
+++ b/pkg/yang/testdata/deviate.yang
@@ -2,9 +2,17 @@ module deviate {
     prefix "d";
     namespace "urn:d";
 
+    typedef derived-string {
+      type string;
+      default "barnacles";
+    }
+
     grouping substmts {
         leaf config { type string; }
         leaf default { type string; }
+        leaf default-typedef { type derived-string; }
+        leaf-list default-list { type string; default "foo"; default "bar"; }
+        leaf-list default-list-typedef-default { type derived-string; }
         leaf mandatory { type string; }
         leaf-list max-elements { type string; }
         leaf-list min-elements { type string; }
@@ -32,6 +40,14 @@ module deviate {
         }
     }
 
+    deviation /target/add/default-list {
+        deviate add {
+            default "foo";
+            // TODO(wenovus): support multiple default statements for deviate.
+            //default "baz";
+        }
+    }
+
     deviation /target/add/mandatory {
         deviate add {
             mandatory true;
@@ -62,4 +78,4 @@ module deviate {
             units "fish per second";
         }
     }
-}
\ No newline at end of file
+}
diff --git a/pkg/yang/types_test.go b/pkg/yang/types_test.go
index de0e629..569c58a 100644
--- a/pkg/yang/types_test.go
+++ b/pkg/yang/types_test.go
@@ -394,9 +394,10 @@ func TestTypedefResolve(t *testing.T) {
 			Base: &Type{
 				Name: "int64",
 			},
-			Units:   "nanoseconds",
-			Default: "42",
-			Range:   Int64Range,
+			Units:      "nanoseconds",
+			Default:    "42",
+			HasDefault: true,
+			Range:      Int64Range,
 		},
 	}, {
 		desc: "uint32 with more specific range",
diff --git a/pkg/yang/yangtype_test.go b/pkg/yang/yangtype_test.go
index 6d93530..f02e66e 100644
--- a/pkg/yang/yangtype_test.go
+++ b/pkg/yang/yangtype_test.go
@@ -76,6 +76,50 @@ func TestYangTypeEqual(t *testing.T) {
 			Kind: Yint64,
 		},
 		wantEqual: false,
+	}, {
+		name: "defaults-equal",
+		inLeft: &YangType{
+			Name:       "foo",
+			Kind:       Ystring,
+			Default:    "bar",
+			HasDefault: true,
+		},
+		inRight: &YangType{
+			Name:       "foo",
+			Kind:       Ystring,
+			Default:    "bar",
+			HasDefault: true,
+		},
+		wantEqual: true,
+	}, {
+		name: "defaults-unequal",
+		inLeft: &YangType{
+			Name:       "foo",
+			Kind:       Ystring,
+			Default:    "bar",
+			HasDefault: true,
+		},
+		inRight: &YangType{
+			Name:       "foo",
+			Kind:       Ystring,
+			Default:    "baz",
+			HasDefault: true,
+		},
+		wantEqual: false,
+	}, {
+		name: "has-default-unequal",
+		inLeft: &YangType{
+			Name:    "foo",
+			Kind:    Ystring,
+			Default: "",
+		},
+		inRight: &YangType{
+			Name:       "foo",
+			Kind:       Ystring,
+			Default:    "",
+			HasDefault: true,
+		},
+		wantEqual: false,
 	}}
 
 	for _, tt := range tests {