cuelang.org/go@v0.13.0/internal/core/adt/typocheck_test.go (about)

     1  // Copyright 2025 CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package adt
    16  
    17  import (
    18  	"slices"
    19  	"testing"
    20  )
    21  
    22  func TestReplaceIDs(t *testing.T) {
    23  	tests := []struct {
    24  		name     string
    25  		reqSets  reqSets
    26  		replace  []replaceID
    27  		expected reqSets
    28  	}{{
    29  		name: "replace single set",
    30  		reqSets: reqSets{
    31  			{id: 1, size: 1},
    32  		},
    33  		replace: []replaceID{
    34  			{from: 1, to: 2},
    35  		},
    36  		// The group was already added as a requirement, so the original group
    37  		// should be deleted.
    38  		expected: reqSets{},
    39  	}, {
    40  		name: "empty result",
    41  		reqSets: reqSets{
    42  			{id: 1, size: 1},
    43  		},
    44  		replace: []replaceID{
    45  			{from: 1, to: deleteID, add: true},
    46  		},
    47  	}, {
    48  		name: "replace first set",
    49  		reqSets: reqSets{
    50  			{id: 1, size: 2},
    51  			{id: 2},
    52  			{id: 3, size: 2},
    53  			{id: 4},
    54  		},
    55  		replace: []replaceID{
    56  			{from: 1, to: 5},
    57  		},
    58  		expected: reqSets{
    59  			{id: 3, size: 2},
    60  			{id: 4},
    61  		},
    62  	}, {
    63  		name: "replace last set",
    64  		reqSets: reqSets{
    65  			{id: 1, size: 2},
    66  			{id: 2},
    67  			{id: 3, size: 2},
    68  			{id: 4},
    69  		},
    70  		replace: []replaceID{
    71  			{from: 3, to: 5},
    72  		},
    73  		expected: reqSets{
    74  			{id: 1, size: 2},
    75  			{id: 2},
    76  		},
    77  	}, {
    78  		name: "replace multiple ids",
    79  		reqSets: reqSets{
    80  			{id: 1, size: 1},
    81  			{id: 2, size: 1},
    82  		},
    83  		replace: []replaceID{
    84  			{from: 1, to: 3},
    85  			{from: 2, to: 4},
    86  		},
    87  		expected: reqSets{},
    88  	}, {
    89  		name: "replace with zero id",
    90  		reqSets: reqSets{
    91  			{id: 1, size: 1},
    92  		},
    93  		replace: []replaceID{
    94  			{from: 1, to: deleteID},
    95  		},
    96  		expected: reqSets{},
    97  	}, {
    98  		name: "replace equivalent",
    99  		reqSets: reqSets{
   100  			{id: 1, size: 2},
   101  			{id: 2}, // e.g. from embedding
   102  		},
   103  		replace: []replaceID{
   104  			{from: 2, to: 3}, // replacing an embedding is additive.
   105  		},
   106  		expected: reqSets{
   107  			{id: 1, size: 3},
   108  			{id: 2},
   109  			{id: 3},
   110  		},
   111  	}, {
   112  		name: "no replacement",
   113  		reqSets: reqSets{
   114  			{id: 1, size: 1},
   115  		},
   116  		replace: []replaceID{},
   117  		expected: reqSets{
   118  			{id: 1, size: 1},
   119  		},
   120  	}, {
   121  		name: "remove multiple from equivalence set",
   122  		reqSets: reqSets{
   123  			{id: 1, size: 4},
   124  			{id: 2},
   125  			{id: 3},
   126  			{id: 4},
   127  			{id: 5, size: 1},
   128  		},
   129  		replace: []replaceID{
   130  			{from: 4, to: deleteID},
   131  			{from: 2, to: deleteID},
   132  		},
   133  		expected: reqSets{
   134  			{id: 1, size: 2},
   135  			{id: 3},
   136  			{id: 5, size: 1},
   137  		},
   138  	}, {
   139  		name: "add new id to existing set",
   140  		reqSets: reqSets{
   141  			{id: 1, size: 1},
   142  		},
   143  		replace: []replaceID{
   144  			{from: 1, to: 2, add: true},
   145  		},
   146  		expected: reqSets{
   147  			{id: 1, size: 2},
   148  			{id: 2},
   149  		},
   150  	}, {
   151  		name: "add new id to multiple sets",
   152  		reqSets: reqSets{
   153  			{id: 1, size: 1},
   154  			{id: 3, size: 1},
   155  		},
   156  		replace: []replaceID{
   157  			{from: 3, to: 4, add: true},
   158  			{from: 1, to: 2, add: true},
   159  		},
   160  		expected: reqSets{
   161  			{id: 1, size: 2},
   162  			{id: 2},
   163  			{id: 3, size: 2},
   164  			{id: 4},
   165  		},
   166  	}, {
   167  		name:    "add new id to empty set",
   168  		reqSets: reqSets{},
   169  		replace: []replaceID{
   170  			{from: 0, to: 1, add: true},
   171  		},
   172  		expected: reqSets{},
   173  	}, {
   174  		name: "add new id to non-existent set",
   175  		reqSets: reqSets{
   176  			{id: 1, size: 1},
   177  		},
   178  		replace: []replaceID{
   179  			{from: 2, to: 3, add: true},
   180  		},
   181  		expected: reqSets{
   182  			{id: 1, size: 1},
   183  		},
   184  	}, {
   185  		name: "add then delete",
   186  		reqSets: reqSets{
   187  			{id: 1, size: 1}, // delete this
   188  			{id: 3, size: 1},
   189  		},
   190  		replace: []replaceID{
   191  			{from: 1, to: 2, add: true},
   192  			{from: 1, to: deleteID},
   193  		},
   194  		expected: reqSets{
   195  			{id: 3, size: 1},
   196  		},
   197  	}, {
   198  		name: "delete then add",
   199  		reqSets: reqSets{
   200  			{id: 1, size: 1}, // delete this
   201  			{id: 3, size: 1},
   202  		},
   203  		replace: []replaceID{
   204  			{from: 1, to: deleteID},
   205  			{from: 1, to: 2, add: true},
   206  		},
   207  		expected: reqSets{
   208  			{id: 3, size: 1},
   209  		},
   210  	}, {
   211  		name: "fixed point",
   212  		reqSets: reqSets{
   213  			{id: 1, size: 1},
   214  			{id: 4, size: 2},
   215  			{id: 1},
   216  		},
   217  		replace: []replaceID{
   218  			{from: 1, to: 2, add: true},
   219  			{from: 2, to: 3, add: true},
   220  			{from: 3, to: 4, add: true},
   221  		},
   222  		expected: reqSets{
   223  			{id: 1, size: 4},
   224  			{id: 2},
   225  			{id: 3},
   226  			{id: 4},
   227  			{id: 4, size: 4},
   228  			{id: 1},
   229  			{id: 2},
   230  			{id: 3},
   231  		},
   232  	}, {
   233  		name: "fixed point with jumps",
   234  		reqSets: reqSets{
   235  			{id: 4, size: 1},
   236  			{id: 1, size: 1},
   237  		},
   238  		replace: []replaceID{
   239  			{from: 1, to: 3, add: true},
   240  			{from: 2, to: 1, add: true},
   241  			{from: 3, to: 2, add: true},
   242  		},
   243  		expected: reqSets{
   244  			{id: 4, size: 1},
   245  			{id: 1, size: 3},
   246  			{id: 3}, // TODO: maybe order?
   247  			{id: 2},
   248  		},
   249  	}, {
   250  		name: "fixed idempotent",
   251  		reqSets: reqSets{
   252  			{id: 1, size: 3},
   253  			{id: 3},
   254  			{id: 2},
   255  			{id: 4, size: 2},
   256  			{id: 1},
   257  		},
   258  		replace: []replaceID{
   259  			{from: 1, to: 3, add: true},
   260  			{from: 2, to: 1, add: true},
   261  			{from: 3, to: 2, add: true},
   262  		},
   263  		expected: reqSets{
   264  			{id: 1, size: 3},
   265  			{id: 3},
   266  			{id: 2},
   267  			{id: 4, size: 4},
   268  			{id: 1},
   269  			{id: 3},
   270  			{id: 2},
   271  		},
   272  	}, {
   273  		name: "add and drop",
   274  		reqSets: reqSets{
   275  			// A main group needs to be fully deleted in case of a replacement.
   276  			// This corresponds to that #B can be dropped as a requirement
   277  			// for `c` in `#B: c: #A` when replacing it with #A.
   278  			{id: 1, size: 1}, // add to this set.
   279  			{id: 2, size: 2}, // drop this set.
   280  			// A replacement of an equivalent id should just add the new id.
   281  			// This corresponds to embeddings being additive.
   282  			{id: 1},
   283  		},
   284  		replace: []replaceID{
   285  			// A main group needs to be fully deleted in case of a replacement.
   286  			// This corresponds to that #B can be dropped as a requirement
   287  			// for `c` in `#B: c: #A` when replacing it with #A.
   288  			{from: 1, to: 3, add: true},
   289  			// A replacement of an equivalent id should just add the new id.
   290  			// This corresponds to embeddings being additive.
   291  			{from: 2, to: 3},
   292  		},
   293  		expected: reqSets{
   294  			{id: 1, size: 2},
   295  			{id: 3},
   296  		},
   297  	}, {
   298  		name: "drop and add",
   299  		reqSets: reqSets{
   300  			{id: 1, size: 1},
   301  			{id: 2, size: 2},
   302  			{id: 1},
   303  		},
   304  		replace: []replaceID{
   305  			{from: 1, to: 3},
   306  			{from: 1, to: 3, add: true},
   307  		},
   308  		expected: reqSets{
   309  			{id: 2, size: 3},
   310  			{id: 1},
   311  			{id: 3},
   312  		},
   313  	}, {
   314  		name: "cycle",
   315  		reqSets: []reqSet{
   316  			{id: 1, size: 1},
   317  			{id: 2, size: 1},
   318  			{id: 3, size: 1, del: 2},
   319  			{id: 4, size: 1, del: 2},
   320  		},
   321  		replace: []replaceID{
   322  			{from: 1, to: 2, add: true}, // , headOnly: true},
   323  			{from: 2, to: 3, add: true},
   324  			{from: 2, to: 4, add: true},
   325  			{from: 3, to: 1, add: true},
   326  			{from: 4, to: 1, add: true},
   327  		},
   328  		expected: reqSets{
   329  			{id: 1, size: 4},
   330  			{id: 2},
   331  			{id: 3},
   332  			{id: 4},
   333  			{id: 2, size: 4},
   334  			{id: 3},
   335  			{id: 1},
   336  			{id: 4},
   337  			{id: 3, size: 2, del: 2},
   338  			{id: 1},
   339  			{id: 4, size: 2, del: 2},
   340  			{id: 1},
   341  		},
   342  	}, {
   343  		name: "exclude 1",
   344  		reqSets: []reqSet{
   345  			{id: 3, size: 1, del: 2},
   346  		},
   347  		replace: []replaceID{
   348  			{from: 1, to: 2, add: true},
   349  			{from: 2, to: 3, add: true},
   350  			{from: 3, to: 1, add: true},
   351  		},
   352  		expected: reqSets{
   353  			{id: 3, size: 2, del: 2},
   354  			{id: 1},
   355  		},
   356  	}, {
   357  		name: "exclude 2",
   358  		reqSets: []reqSet{
   359  			{id: 5, size: 1},
   360  			{id: 6, size: 1},
   361  			{id: 7, size: 1, del: 6},
   362  			{id: 8, size: 1, del: 6},
   363  		},
   364  		replace: []replaceID{
   365  			{from: 5, to: 6, add: true},
   366  			{from: 6, to: 7, add: true},
   367  			{from: 6, to: 8, add: true},
   368  			{from: 7, to: 0, add: true},
   369  			{from: 8, to: 0, add: true},
   370  		},
   371  		expected: reqSets{
   372  			{id: 5, size: 4},
   373  			{id: 6},
   374  			{id: 7},
   375  			{id: 8},
   376  			{id: 6, size: 3},
   377  			{id: 7},
   378  			{id: 8},
   379  			{id: 7, size: 2, del: 6},
   380  			{id: 0},
   381  			{id: 8, size: 2, del: 6},
   382  			{id: 0},
   383  		},
   384  	}, {
   385  		name: "exclude 3",
   386  		reqSets: []reqSet{
   387  			{id: 5, size: 1},
   388  			{id: 8, size: 1, del: 7},
   389  			{id: 9, size: 1, del: 7},
   390  		},
   391  		replace: []replaceID{
   392  			{from: 5, to: 6, add: true},
   393  			{from: 6, to: 7, add: true},
   394  			{from: 7, to: 8, add: true},
   395  			{from: 7, to: 9, add: true},
   396  			{from: 8, to: 6, add: true},
   397  			{from: 9, to: 6, add: true},
   398  		},
   399  		expected: reqSets{
   400  			{id: 5, size: 5},
   401  			{id: 6},
   402  			{id: 7},
   403  			{id: 8},
   404  			{id: 9},
   405  			{id: 8, size: 2, del: 7},
   406  			{id: 6},
   407  			{id: 9, size: 2, del: 7},
   408  			{id: 6},
   409  		},
   410  	}, {
   411  		name: "exclude 4",
   412  		// represents
   413  		// #a: [>="k"]: int // 11
   414  		// #b: [<="m"]: int // 12
   415  		// #c: [>="w"]: int // 13
   416  		// #d: [<="y"]: int // 14
   417  		// X: { // 8
   418  		// 		#a & #b // 9
   419  		// 		#c & #d // 10
   420  		// }
   421  		// ignored groups (9, 10), are omitted.
   422  		reqSets: []reqSet{
   423  			{id: 8, size: 1},
   424  			{id: 11, size: 1, del: 9},
   425  			{id: 12, size: 1, del: 9},
   426  			{id: 13, size: 1, del: 10},
   427  			{id: 14, size: 1, del: 10},
   428  		},
   429  		replace: []replaceID{
   430  			{from: 8, to: 9, add: true},
   431  			{from: 8, to: 10, add: true},
   432  			{from: 9, to: 11, add: true},
   433  			{from: 11, to: 8, add: true},
   434  			{from: 9, to: 12, add: true},
   435  			{from: 12, to: 8, add: true},
   436  			{from: 10, to: 13, add: true},
   437  			{from: 13, to: 8, add: true},
   438  			{from: 10, to: 14, add: true},
   439  			{from: 14, to: 8, add: true},
   440  		},
   441  		expected: reqSets{
   442  			{id: 8, size: 7},
   443  			{id: 9},
   444  			{id: 11},
   445  			{id: 12},
   446  			{id: 10},
   447  			{id: 13},
   448  			{id: 14},
   449  			{id: 11, size: 5, del: 9},
   450  			{id: 8},
   451  			{id: 10},
   452  			{id: 13},
   453  			{id: 14},
   454  			{id: 12, size: 5, del: 9},
   455  			{id: 8},
   456  			{id: 10},
   457  			{id: 13},
   458  			{id: 14},
   459  			{id: 13, size: 5, del: 10},
   460  			{id: 8},
   461  			{id: 9},
   462  			{id: 11},
   463  			{id: 12},
   464  			{id: 14, size: 5, del: 10},
   465  			{id: 8},
   466  			{id: 9},
   467  			{id: 11},
   468  			{id: 12},
   469  		},
   470  	}}
   471  
   472  	for _, tt := range tests {
   473  		t.Run(tt.name, func(t *testing.T) {
   474  			if tt.name != "exclude1" {
   475  				// return
   476  			}
   477  			tt.reqSets.assert()
   478  			tt.expected.assert()
   479  
   480  			tt.reqSets.replaceIDs(&OpContext{}, tt.replace...)
   481  			if !slices.Equal(tt.reqSets, tt.expected) {
   482  				t.Errorf("got: \n%v, want:\n%v", tt.reqSets, tt.expected)
   483  			}
   484  		})
   485  	}
   486  }
   487  
   488  func TestHasEvidence(t *testing.T) {
   489  	tests := []struct {
   490  		name      string
   491  		reqSets   reqSets
   492  		conjuncts []conjunctInfo
   493  		want      bool
   494  	}{{
   495  		name: "single match",
   496  		reqSets: reqSets{
   497  			{id: 1, size: 1},
   498  		},
   499  		conjuncts: []conjunctInfo{
   500  			{id: 1},
   501  		},
   502  		want: true,
   503  	}, {
   504  		name: "no match",
   505  		reqSets: reqSets{
   506  			{id: 1, size: 1},
   507  		},
   508  		conjuncts: []conjunctInfo{
   509  			{id: 2},
   510  		},
   511  		want: false,
   512  	}, {
   513  		name: "no conjuncts",
   514  		reqSets: reqSets{
   515  			{id: 1, size: 1},
   516  		},
   517  		conjuncts: []conjunctInfo{},
   518  		want:      false,
   519  	}, {
   520  		name:    "no requirements",
   521  		reqSets: reqSets{},
   522  		conjuncts: []conjunctInfo{
   523  			{id: 2},
   524  		},
   525  		want: true,
   526  	}, {
   527  		name:      "no requirements, no conjuncts",
   528  		reqSets:   reqSets{},
   529  		conjuncts: []conjunctInfo{},
   530  		want:      true,
   531  	}, {
   532  		name: "multiple, all match",
   533  		reqSets: reqSets{
   534  			{id: 1, size: 1},
   535  			{id: 2, size: 1},
   536  		},
   537  		conjuncts: []conjunctInfo{
   538  			{id: 1},
   539  			{id: 2},
   540  		},
   541  		want: true,
   542  	}, {
   543  		name: "multiple, one does not match",
   544  		reqSets: reqSets{
   545  			{id: 1, size: 1},
   546  			{id: 2, size: 1},
   547  		},
   548  		conjuncts: []conjunctInfo{
   549  			{id: 2},
   550  		},
   551  		want: false,
   552  	}, {
   553  		name: "multiset match",
   554  		reqSets: reqSets{
   555  			{id: 1, size: 2},
   556  			{id: 2},
   557  		},
   558  		conjuncts: []conjunctInfo{
   559  			{id: 2},
   560  		},
   561  		want: true,
   562  	}, {
   563  		name: "multiset no match",
   564  		reqSets: reqSets{
   565  			{id: 1, size: 2},
   566  			{id: 2},
   567  		},
   568  		conjuncts: []conjunctInfo{
   569  			{id: 3},
   570  		},
   571  		want: false,
   572  	}}
   573  
   574  	n := &nodeContext{}
   575  	n.ctx = &OpContext{}
   576  	for _, tt := range tests {
   577  		t.Run(tt.name, func(t *testing.T) {
   578  			tt.reqSets.assert()
   579  
   580  			if got := n.hasEvidenceForAll(tt.reqSets, tt.conjuncts); got != tt.want {
   581  				t.Errorf("got %v, want %v", got, tt.want)
   582  			}
   583  		})
   584  	}
   585  }
   586  func TestMergeCloseInfo(t *testing.T) {
   587  	tests := []struct {
   588  		name     string
   589  		nv       *nodeContext
   590  		nw       *nodeContext
   591  		expected *nodeContext
   592  	}{{
   593  		name: "merge with no conflicts",
   594  		nv: &nodeContext{
   595  			node: &Vertex{
   596  				Arcs: []*Vertex{
   597  					{Label: 1, state: &nodeContext{}},
   598  				},
   599  			},
   600  			conjunctInfo: []conjunctInfo{
   601  				{id: 1},
   602  			},
   603  			replaceIDs: []replaceID{
   604  				{from: 1, to: 2},
   605  			},
   606  		},
   607  		nw: &nodeContext{
   608  			node: &Vertex{
   609  				Arcs: []*Vertex{
   610  					{Label: 1, state: &nodeContext{}},
   611  				},
   612  			},
   613  			conjunctInfo: []conjunctInfo{
   614  				{id: 2},
   615  			},
   616  			replaceIDs: []replaceID{
   617  				{from: 2, to: 3},
   618  			},
   619  		},
   620  		expected: &nodeContext{
   621  			conjunctInfo: []conjunctInfo{
   622  				{id: 1},
   623  				{id: 2},
   624  			},
   625  			replaceIDs: []replaceID{
   626  				{from: 1, to: 2},
   627  				{from: 2, to: 3},
   628  			},
   629  		},
   630  	}, {
   631  		name: "merge with conflicts",
   632  		nv: &nodeContext{
   633  			node: &Vertex{
   634  				Arcs: []*Vertex{
   635  					{Label: 1, state: &nodeContext{}},
   636  				},
   637  			},
   638  			conjunctInfo: []conjunctInfo{
   639  				{id: 1},
   640  			},
   641  			replaceIDs: []replaceID{
   642  				{from: 1, to: 2},
   643  			},
   644  		},
   645  		nw: &nodeContext{
   646  			node: &Vertex{
   647  				Arcs: []*Vertex{
   648  					{Label: 2, state: &nodeContext{}},
   649  				},
   650  			},
   651  			conjunctInfo: []conjunctInfo{
   652  				{id: 1},
   653  			},
   654  			replaceIDs: []replaceID{
   655  				{from: 1, to: 3},
   656  			},
   657  		},
   658  		expected: &nodeContext{
   659  			conjunctInfo: []conjunctInfo{
   660  				{id: 1},
   661  			},
   662  			replaceIDs: []replaceID{
   663  				{from: 1, to: 2},
   664  				{from: 1, to: 3},
   665  			},
   666  		},
   667  	},
   668  	}
   669  
   670  	for _, tt := range tests {
   671  		t.Run(tt.name, func(t *testing.T) {
   672  			mergeCloseInfo(tt.nv, tt.nw)
   673  			if !slices.Equal(tt.nv.conjunctInfo, tt.expected.conjunctInfo) {
   674  				t.Errorf("conjunctInfo got %v, want %v", tt.nv.conjunctInfo, tt.expected.conjunctInfo)
   675  			}
   676  			if !slices.Equal(tt.nv.replaceIDs, tt.expected.replaceIDs) {
   677  				t.Errorf("replaceIDs got %v, want %v", tt.nv.replaceIDs, tt.expected.replaceIDs)
   678  			}
   679  		})
   680  	}
   681  }