Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composite literal inference #704

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions ttcn3/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@
}
return nil
}
case *syntax.Ident:
// names?
if n, ok := Predefined[n.String()]; ok {
return n
}
if n.String() == "infinity" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check if this condition is ever true?

At least it should never be true, because the infinity token is supposed to a syntax.ValueLiteral (like true, inconc, ...) and not syntax.Ident.

If it es ever true it must be a bug in the parser, which needs fixing.

return Predefined["float"]
}
case *syntax.BinaryExpr:
switch n.Op.Kind() {
case syntax.LT, syntax.GT, syntax.LE, syntax.GE, syntax.EQ, syntax.NE, syntax.AND, syntax.OR, syntax.XOR:
Expand All @@ -448,6 +456,19 @@
return Predefined["integer"]
case syntax.SHL, syntax.SHR, syntax.ROL, syntax.ROR:
return TypeOf(n.X)
case syntax.ASSIGN:
// Investigate: Label on the left, omit on the right, Maps
if n.Y.FirstTok().Kind() == syntax.OMIT {
if t := TypeOf(n.X); t != nil {
return t
} else {
return Predefined["boolean"]
}
}
if X, ok := n.X.(*syntax.IndexExpr); ok && X.X == nil && TypeOf(X.Index) != Predefined["integer"] {
return &MapType{From: TypeOf(X.Index), To: TypeOf(n.Y)}
}
return TypeOf(n.Y)
}
case *syntax.UnaryExpr:
switch n.Op.Kind() {
Expand All @@ -458,6 +479,175 @@
case syntax.INC, syntax.DEC:
return Predefined["integer"]
}
case *syntax.ParenExpr:
// different length?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: syntax.ParenExpr represents either a (template) list, e.g. (1,2,3) or it presents an expression such as 1 * (2+3).
For now its okay to use the length of the list as indicator. But there is some unresolved ambiguity, we need to resolve: (1) could be be a template list or an expression. Which means we properly must infer the proper type from its context.

if len(n.List) == 1 {
return TypeOf(n.List[0])
}
case *syntax.IndexExpr:
if n.X == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please comments explaining the conditions. Short examples are okay.

return TypeOf(n.Index)
} else if X, ok := TypeOf(n.X).(*ListType); ok {
return X.ElementType
} else {
return TypeOf(n.X).(*MapType).From
}
}
case *syntax.CompositeLiteral:

Check failure on line 496 in ttcn3/types/types.go

View workflow job for this annotation

GitHub Actions / build

syntax error: unexpected case, expected }
fields := make([]Field, len(n.List))
typeList := make([]Type, len(n.List))
isRecord := false

for i, v := range n.List {
typeList[i] = TypeOf(v)
fields[i].Type = typeList[i]
if v, ok := v.(*syntax.BinaryExpr); ok && v.Op.Kind() == syntax.ASSIGN && v.Y.FirstTok().Kind() == syntax.OMIT {
fields[i].Optional = true
isRecord = true
} else {
fields[i].Optional = false
}
}
if len(fields) == 0 {
return &ListType{Kind: RecordOf}
}
Comment on lines +511 to +513
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this condition to the top of the case-block to improve the flow when reading.

Also, you can remove one indirection. Write if len(n.List) == 0 instead of if len(fields) = 0.

if t := mostSpecificCommonTypeOfArray(typeList...); isRecord || t == Predefined["any"] {
t = &StructuredType{Kind: Record, Fields: fields}
return t
// Map vs Record of Maps?
} else if _, ok := t.(*MapType); ok {
return t
} else {
return &ListType{Kind: RecordOf, ElementType: t}
}
}
return nil
}

func mostSpecificCommonTypeOfArray(types ...Type) Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write a documentation comment for this function.

if len(types) == 0 {
return Predefined["any"]
}
result := types[0]
if len(types) == 1 || result == Predefined["any"] {
return result
}
for _, v := range types[1:] {
result = mostSpecificCommonTypeOf2(result, v)
if result == Predefined["any"] {
break
}
}
return result
}

func mostSpecificCommonTypeOf2(type1 Type, type2 Type) Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has mostSpecificCommonTypeOf2 a different behaviour than mostSpecificCommonTypeOfArray?

if type1 == type2 {
return type1
}
if t := isSuper(type1, type2); t != nil {
return t
}
// Is there another Type that the two share?
return Predefined["any"]
}

func isSuper(type1 Type, type2 Type) Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging you should rename and document this function. isSuper suggests a boolean, but it returns a Type.

if type1 == Predefined["any"] || type2 == Predefined["any"] {
return Predefined["any"]
}
switch type1 := type1.(type) {
case *PrimitiveType:
if type2, ok := type2.(*PrimitiveType); !ok {
return nil
} else {
if type1.Kind == type2.Kind {
if type1.ValueConstraints == nil {
return type1
}
if type2.ValueConstraints == nil {
return type2
}
// ToDo: Add more ConstraintChecks
}
return nil
}
case *ListType:
if type2, ok := type2.(*ListType); !ok {
return nil
} else {
if type1.Kind == type2.Kind {
if type1.ElementType == type2.ElementType {
return type1
}
if isString(type1.Kind) {
// Check Constraints
return nil
}
// check ElementType (Array RecordOf SetOf)
superElement := isSuper(type1.ElementType, type2.ElementType)
if superElement == type1.ElementType {
return type1
}
if superElement == type2.ElementType {
return type2
}
return nil
}
switch type1.Kind {
case Hexstring, Octetstring, Bitstring, SetOf:
return nil
case Charstring:
if type2.Kind == UniversalCharstring {
// check Constraints
return type2
}
return nil
case UniversalCharstring:
if type2.Kind == Charstring {
// check Constraints
return type1
}
return nil
case RecordOf:
if type2.Kind == Array {
// check ElementType and Constraints
return type1
}
return nil
case Array:
if type2.Kind == RecordOf {
// check ElementType and Constraints
return type2
}
return nil
}
}
case *StructuredType:
if type2, ok := type2.(*StructuredType); !ok {
return nil
} else {
if type1.Kind == type2.Kind && len(type1.Fields) == len(type2.Fields) {
for i, f := range type1.Fields {
if f.Optional == type2.Fields[i].Optional && isSuper(f.Type, type2.Fields[i].Type) == f.Type {
continue
} else {
return nil
}
}
return type1
}
}
case *MapType:
if type2, ok := type2.(*MapType); !ok || type1.From != type2.From {
return nil
} else {
if superTo := isSuper(type1.To, type2.To); superTo == type1.To {
return type1
} else if superTo == type2.To {
return type2
}
}
}
return nil
}
31 changes: 16 additions & 15 deletions ttcn3/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,14 @@ func TestTypeInference(t *testing.T) {
skip bool
}{
// Identifiers
{skip: true, input: `integer`, expect: `integer`},
{skip: true, input: `float`, expect: `float`},
{skip: true, input: `boolean`, expect: `boolean`},
{input: `integer`, expect: `integer`},
{input: `float`, expect: `float`},
{input: `boolean`, expect: `boolean`},

// ValueLiterals
{input: `0`, expect: `integer`},
{input: `0.0`, expect: `float`},
{skip: true, input: `infinity`, expect: `float`},
{input: `infinity`, expect: `float`},
{input: `not_a_number`, expect: `float`},
{input: `true`, expect: `boolean`},
{input: `false`, expect: `boolean`},
Expand All @@ -342,18 +342,19 @@ func TestTypeInference(t *testing.T) {
{input: `not true or false`, expect: `boolean`},
{input: `1+2 <= 3`, expect: `boolean`},

{skip: true, input: `x := ¶{1}`, expect: `record of integer`},
{skip: true, input: `x := ¶{1,2,3}`, expect: `record of integer`},
{skip: true, input: `x := ¶{1+2,2,3}`, expect: `record of integer`},
{skip: true, input: `x := ¶{(1+2),2,(3)}`, expect: `record of integer`},
{input: `x := ¶{1}`, expect: `record of integer`},
{input: `x := ¶{1,2,3}`, expect: `record of integer`},
{input: `x := ¶{1+2,2,3}`, expect: `record of integer`},
{input: `x := ¶{(1+2),2,(3)}`, expect: `record of integer`},
{skip: true, input: `x := ¶{{1},{2},{}}`, expect: `record of record of integer`},
{skip: true, input: `x := ¶{"A",""}`, expect: `record of charstring`},
{skip: true, input: `x := ¶{"A","Ä"}`, expect: `record of universal charstring`},
{skip: true, input: `x := ¶{x := 2, y := true}`, expect: `record { integer, boolean }`},
{skip: true, input: `x := ¶{x := 2, y := omit}`, expect: `record { integer, boolean optional}`},
{skip: true, input: `x := ¶{integer := 2, float := omit}`, expect: `record { integer, float optional}`},
{skip: true, input: `x := ¶{[1] := 1, [3] := 2, [2] := 3}`, expect: `record of integer`},
{skip: true, input: `x := ¶{[1.0] := 1, [3.0] := 2, [2.0] := 3}`, expect: `map from float to integer`},
{input: `x := ¶{"A",""}`, expect: `record of charstring`},
{input: `x := ¶{"A","Ä"}`, expect: `record of universal charstring`},
{input: `x := ¶{x := 2, y := true}`, expect: `record {integer, boolean}`},
{input: `x := ¶{x := 2, y := omit}`, expect: `record {integer, boolean optional}`},
{input: `x := ¶{integer := 2, float := omit}`, expect: `record {integer, float optional}`},
{input: `x := ¶{[1] := 1, [3] := 2, [2] := 3}`, expect: `record of integer`},
{input: `x := ¶{[1.0] := 1, [3.0] := 2, [2.0] := 3}`, expect: `map from float to integer`},
{input: `x := ¶{["ÄB"] := 1.0, ["CÖ"] := 2.0, ["EÜ"] := 3.0}`, expect: `map from universal charstring to float`},

//{skip: true, input: `x := 2`, expect: `void`},
//{skip: true, input: `mtc == null`, expect: `boolean`},
Expand Down
Loading