diff --git a/lang/core/struct_lookup_optional.go b/lang/core/struct_lookup_optional.go index 4576f8da..813143d4 100644 --- a/lang/core/struct_lookup_optional.go +++ b/lang/core/struct_lookup_optional.go @@ -131,7 +131,7 @@ func (obj *StructLookupOptionalFunc) FuncInfer(partialType *types.Type, partialV return nil, nil, fmt.Errorf("function must not have an empty field name") } // This can happen at runtime too, but we save it here for Build()! - //obj.field = s // don't store for this optional lookup version! + obj.field = s // Figure out more about the sig if any information is known statically. if len(partialType.Ord) > 0 && partialType.Map[partialType.Ord[0]] != nil { @@ -200,6 +200,19 @@ func (obj *StructLookupOptionalFunc) Build(typ *types.Type) (*types.Type, error) return nil, fmt.Errorf("first arg must be of kind struct, got: %s", tStruct.Kind) } + if obj.field == "" { + // programming error + return nil, fmt.Errorf("got an empty field name") + } + + // If the field exists, then it MUST match typ.Out of course! + tFoundField, exists := tStruct.Map[obj.field] + if exists { + if err := typ.Out.Cmp(tFoundField); err != nil { + return nil, errwrap.Wrapf(err, "non-optional arg must match return type") + } + } + obj.Type = tStruct // struct type obj.Out = typ.Out // type of return value obj.built = true @@ -207,6 +220,20 @@ func (obj *StructLookupOptionalFunc) Build(typ *types.Type) (*types.Type, error) return obj.sig(), nil } +// Copy is implemented so that the obj.field value is not lost if we copy this +// function. That value is learned during FuncInfer, and previously would have +// been lost by the time we used it in Build. +func (obj *StructLookupOptionalFunc) Copy() interfaces.Func { + return &StructLookupOptionalFunc{ + Type: obj.Type, // don't copy because we use this after unification + Out: obj.Out, + + built: obj.built, + init: obj.init, // likely gets overwritten anyways + field: obj.field, // this we really need! + } +} + // Validate tells us if the input struct takes a valid form. func (obj *StructLookupOptionalFunc) Validate() error { if !obj.built { diff --git a/lang/interpret_test/TestAstFunc2/structlookup4.txtar b/lang/interpret_test/TestAstFunc2/structlookup4.txtar new file mode 100644 index 00000000..a532426e --- /dev/null +++ b/lang/interpret_test/TestAstFunc2/structlookup4.txtar @@ -0,0 +1,24 @@ +-- main.mcl -- +include foo(struct{ + str0 => ["A", "B",], +}) + +class foo($st1) { + $str1 = $st1->str0 + + include bar(struct{ + str2 => $str1, + }) +} + +class bar($st2) { + # Tricky because LHS and optional arg are both strings! Make sure to + # check that this type matches what's in the incoming struct in case + # that field in the struct exists. + #$str3 = _struct_lookup_optional($st2, "str2", "C") + $str3 = $st2->str2 || "C" # should not unify + + test ["str: ${str3}",] {} +} +-- OUTPUT -- +# err: errUnify: error setting type: func() { }, error: non-optional arg must match return type: base kind does not match (str != list)