From 971a8c8481abbb255acb9136255f6ffca05cff0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Tue, 22 Jul 2025 16:49:27 +0200 Subject: [PATCH 01/10] chore: improve errors in iterators.R --- R/iterators.R | 50 ++++++++++++------------------ tests/testthat/_snaps/iterators.md | 18 ++++++++++- tests/testthat/test-iterators.R | 16 ++++++++++ 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index 31e87dbe307..625a2e99f3e 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1079,7 +1079,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { } if (is.logical(ii) && (length(ii) != length(x) && length(ii) != 1)) { cli::cli_abort( - "Error: Logical index length does not match the number of edges. Recycling is not allowed." + "Logical index length does not match the number of edges. Recycling is not allowed." ) } @@ -1144,14 +1144,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @name igraph-vs-attributes #' @export `[[<-.igraph.vs` <- function(x, i, value) { - if ( - !"name" %in% names(attributes(value)) || - !"value" %in% names(attributes(value)) - ) { - cli::cli_abort("Invalid indexing.") + if (!rlang::has_name(attributes(value), "name")) { + cli::cli_abort("Can't find {.val name} for attribute.") } if (is.null(get_vs_graph(x))) { - stop("Graph is unknown.") + cli::cli_abort("Can't find graph.") } value } @@ -1166,14 +1163,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @name igraph-es-attributes #' @export `[[<-.igraph.es` <- function(x, i, value) { - if ( - !"name" %in% names(attributes(value)) || - !"value" %in% names(attributes(value)) - ) { - stop("Invalid indexing.") + if (!rlang::has_name(attributes(value), "name")) { + cli::cli_abort("Can't find {.val name} for attribute.") } if (is.null(get_es_graph(x))) { - stop("Graph is unknown.") + cli::cli_abort("Can't find graph.") } value } @@ -1239,7 +1233,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { `$.igraph.vs` <- function(x, name) { graph <- get_vs_graph(x) if (is.null(graph)) { - cli::cli_abort("Graph is unknown") + cli::cli_abort("Can't find graph.") } res <- vertex_attr(graph, name, x) if (is_single_index(x)) { @@ -1292,7 +1286,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { `$.igraph.es` <- function(x, name) { graph <- get_es_graph(x) if (is.null(graph)) { - cli::cli_abort("Graph is unknown") + cli::cli_abort("Can't find graph.") } res <- edge_attr(graph, name, x) if (is_single_index(x)) { @@ -1310,7 +1304,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `$<-.igraph.vs` <- function(x, name, value) { if (is.null(get_vs_graph(x))) { - cli::cli_abort("Graph is unknown") + cli::cli_abort("Can't find graph.") } attr(x, "name") <- name attr(x, "value") <- value @@ -1325,7 +1319,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @family vertex and edge sequences `$<-.igraph.es` <- function(x, name, value) { if (is.null(get_es_graph(x))) { - cli::cli_abort("Graph is unknown") + cli::cli_abort("Can't find graph.") } attr(x, "name") <- name attr(x, "value") <- value @@ -1336,11 +1330,8 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `V<-` <- function(x, value) { ensure_igraph(x) - if ( - !"name" %in% names(attributes(value)) || - !"value" %in% names(attributes(value)) - ) { - cli::cli_abort("invalid indexing") + if (!rlang::has_name(attributes(value), "name")) { + cli::cli_abort("Can't find {.val name} for vertex attribute.") } i_set_vertex_attr( x, @@ -1360,11 +1351,8 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `E<-` <- function(x, path = NULL, P = NULL, directed = NULL, value) { ensure_igraph(x) - if ( - !"name" %in% names(attributes(value)) || - !"value" %in% names(attributes(value)) - ) { - cli::cli_abort("invalid indexing") + if (!rlang::has_name(attributes(value), "name")) { + cli::cli_abort("Can't find {.val name} for edge attribute.") } i_set_edge_attr( x, @@ -1563,7 +1551,7 @@ as_igraph_vs <- function(graph, v, na.ok = FALSE) { if (is.character(v) && "name" %in% vertex_attr_names(graph)) { v <- as.numeric(match(v, V(graph)$name)) if (!na.ok && any(is.na(v))) { - cli::cli_abort("Invalid vertex names") + cli::cli_abort("Invalid vertex names {.arg v}.") } v } else { @@ -1575,7 +1563,7 @@ as_igraph_vs <- function(graph, v, na.ok = FALSE) { res <- as.numeric(v) } if (!na.ok && any(is.na(res))) { - cli::cli_abort("Invalid vertex name(s)") + cli::cli_abort("Invalid vertex name(s) {.arg v}.") } res } @@ -1618,7 +1606,7 @@ as_igraph_es <- function(graph, e) { res <- as.numeric(e) } if (any(is.na(res))) { - cli::cli_abort("Invalid edge names") + cli::cli_abort("Invalid edge names {.arg e}.") } res } @@ -1638,7 +1626,7 @@ parse_op_args <- function(..., what, is_fun, as_fun, check_graph = TRUE) { args <- list(...) if (any(!sapply(args, is_fun))) { - cli::cli_abort("Not {what} sequence") + cli::cli_abort("Not {what} sequence.") } ## get the ids of all graphs diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index d01392169aa..e0d56cc988c 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -106,5 +106,21 @@ E(g)[c(TRUE, FALSE)] Condition Error in `FUN()`: - ! Error: Logical index length does not match the number of edges. Recycling is not allowed. + ! Logical index length does not match the number of edges. Recycling is not allowed. + +# `[[<-.igraph.es` and `V<-` error well + + Code + V(g) <- "blue" + Condition + Error in `V<-`: + ! Can't find "name" for vertex attribute. + +--- + + Code + E(g)[1] <- "blue" + Condition + Error in `[<-`: + ! Can't find "name" for attribute. diff --git a/tests/testthat/test-iterators.R b/tests/testthat/test-iterators.R index 50790e1623a..4e885bb01be 100644 --- a/tests/testthat/test-iterators.R +++ b/tests/testthat/test-iterators.R @@ -447,3 +447,19 @@ test_that("logical indices are not recycled", { expect_snapshot(V(g)[c(TRUE, FALSE)], error = TRUE) expect_snapshot(E(g)[c(TRUE, FALSE)], error = TRUE) }) + +test_that("`[[<-.igraph.es` and `V<-` error well", { + g <- make_( + ring(10), + with_vertex_( + name = LETTERS[1:10], + color = sample(1:2, 10, replace = TRUE) + ) + ) + expect_snapshot(error = TRUE, { + V(g) <- "blue" + }) + expect_snapshot(error = TRUE, { + E(g)[1] <- "blue" + }) +}) From 2be846a1326ce77879e845115ea2c049b705ec00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 12 Aug 2026 16:11:23 +0200 Subject: [PATCH 02/10] tests thx Claude --- R/iterators.R | 20 ++++++ tests/testthat/_snaps/iterators.md | 100 ++++++++++++++++++++++++++++- tests/testthat/test-iterators.R | 97 +++++++++++++++++++++++++++- 3 files changed, 215 insertions(+), 2 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index c443860a302..5b9983b5f68 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1200,6 +1200,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for attribute.") } + if (!rlang::has_name(attributes(value), "value")) { + cli::cli_abort( + "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." + ) + } if (is.null(get_vs_graph(x))) { cli::cli_abort("Graph is unknown.", .internal = TRUE) } @@ -1219,6 +1224,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for attribute.") } + if (!rlang::has_name(attributes(value), "value")) { + cli::cli_abort( + "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_edge_attr} instead." + ) + } if (is.null(get_es_graph(x))) { cli::cli_abort("Graph is unknown.", .internal = TRUE) } @@ -1386,6 +1396,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for vertex attribute.") } + if (!rlang::has_name(attributes(value), "value")) { + cli::cli_abort( + "Can't find {.val value} for vertex attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." + ) + } i_set_vertex_attr( x, attr(value, "name"), @@ -1407,6 +1422,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for edge attribute.") } + if (!rlang::has_name(attributes(value), "value")) { + cli::cli_abort( + "Can't find {.val value} for edge attribute. To remove an attribute, use {.fn delete_edge_attr} instead." + ) + } i_set_edge_attr( x, attr(value, "name"), diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index e0d56cc988c..608b22d090f 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -108,7 +108,31 @@ Error in `FUN()`: ! Logical index length does not match the number of edges. Recycling is not allowed. -# `[[<-.igraph.es` and `V<-` error well +# assigning `NULL` errors clearly instead of silently doing nothing + + Code + V(g)$color <- NULL + Condition + Error in `V<-`: + ! Can't find "value" for vertex attribute. To remove an attribute, use `delete_vertex_attr()` instead. + +--- + + Code + V(g)[1:3]$color <- NULL + Condition + Error in `[<-`: + ! Can't find "value" for attribute. To remove an attribute, use `delete_vertex_attr()` instead. + +--- + + Code + E(g)$weight <- NULL + Condition + Error in `E<-`: + ! Can't find "value" for edge attribute. To remove an attribute, use `delete_edge_attr()` instead. + +# direct misuse of `V<-`/`E<-`/`[<-`/`[[<-` errors well Code V(g) <- "blue" @@ -116,6 +140,22 @@ Error in `V<-`: ! Can't find "name" for vertex attribute. +--- + + Code + E(g) <- "blue" + Condition + Error in `E<-`: + ! Can't find "name" for edge attribute. + +--- + + Code + V(g)[1] <- "blue" + Condition + Error in `[<-`: + ! Can't find "name" for attribute. + --- Code @@ -124,3 +164,61 @@ Error in `[<-`: ! Can't find "name" for attribute. +--- + + Code + V(g)[[1]] <- "blue" + Condition + Error in `[[<-`: + ! Can't find "name" for attribute. + +--- + + Code + E(g)[[1]] <- "blue" + Condition + Error in `[[<-`: + ! Can't find "name" for attribute. + +# querying or setting attributes errors when the graph is unknown + + Code + vs$color + Condition + Error in `vs$color`: + ! Can't find graph. + +--- + + Code + vs$color <- "blue" + Condition + Error in `$<-`: + ! Can't find graph. + +--- + + Code + es$weight + Condition + Error in `es$weight`: + ! Can't find graph. + +--- + + Code + es$weight <- 0 + Condition + Error in `$<-`: + ! Can't find graph. + +# `[<-.igraph.vs` reports an internal error when the graph is unknown + + Code + `[<-.igraph.vs`(vs, 1, value = payload) + Condition + Error in `[<-.igraph.vs`: + ! Graph is unknown. + i This is an internal error that was detected in the igraph package. + Please report it at with a reprex () and the full backtrace. + diff --git a/tests/testthat/test-iterators.R b/tests/testthat/test-iterators.R index 16727ef466f..849b7d1bd6c 100644 --- a/tests/testthat/test-iterators.R +++ b/tests/testthat/test-iterators.R @@ -477,7 +477,56 @@ test_that("identical_graphs() tail arguments and legacy positional recovery", { expect_identical(res, identical_graphs(g1, g2, attrs = FALSE)) }) -test_that("`[[<-.igraph.es` and `V<-` error well", { +test_that("`V(g)[idx]$attr <-` and `E(g)[idx]$attr <-` set attributes as intended", { + g <- make_ring(5) + V(g)$color <- "red" + E(g)$weight <- 1:5 + + V(g)[1:3]$color <- "blue" + expect_equal(V(g)$color, c("blue", "blue", "blue", "red", "red")) + + E(g)[1:3]$weight <- 0 + expect_equal(E(g)$weight, c(0, 0, 0, 4, 5)) + + V(g)$color <- "green" + expect_equal(V(g)$color, rep("green", 5)) + + E(g)$weight <- 9 + expect_equal(E(g)$weight, rep(9, 5)) +}) + +test_that("assigning `NA` blanks attribute values without removing the attribute", { + g <- make_ring(5) + V(g)$color <- "red" + + V(g)$color <- NA + expect_true(all(is.na(V(g)$color))) + expect_true("color" %in% vertex_attr_names(g)) +}) + +test_that("assigning `NULL` errors clearly instead of silently doing nothing", { + # `attr(x, "value") <- NULL` removes the "value" attribute that + # `$<-.igraph.vs`/`$<-.igraph.es` attach, rather than attaching a NULL + # value. That made `V(g)$attr <- NULL` (the base R idiom for removing a + # list/data.frame element) a silent no-op once the "value" attribute check + # was dropped. `delete_vertex_attr()`/`delete_edge_attr()` are the actual + # way to remove an attribute. + g <- make_ring(5) + V(g)$color <- "red" + E(g)$weight <- 1:5 + + expect_snapshot(error = TRUE, { + V(g)$color <- NULL + }) + expect_snapshot(error = TRUE, { + V(g)[1:3]$color <- NULL + }) + expect_snapshot(error = TRUE, { + E(g)$weight <- NULL + }) +}) + +test_that("direct misuse of `V<-`/`E<-`/`[<-`/`[[<-` errors well", { g <- make_( ring(10), with_vertex_( @@ -488,7 +537,53 @@ test_that("`[[<-.igraph.es` and `V<-` error well", { expect_snapshot(error = TRUE, { V(g) <- "blue" }) + expect_snapshot(error = TRUE, { + E(g) <- "blue" + }) + expect_snapshot(error = TRUE, { + V(g)[1] <- "blue" + }) expect_snapshot(error = TRUE, { E(g)[1] <- "blue" }) + expect_snapshot(error = TRUE, { + V(g)[[1]] <- "blue" + }) + expect_snapshot(error = TRUE, { + E(g)[[1]] <- "blue" + }) +}) + +test_that("querying or setting attributes errors when the graph is unknown", { + g <- make_ring(5) + V(g)$color <- "red" + E(g)$weight <- 1:5 + + vs <- V(g) + attr(vs, "env") <- NULL + es <- E(g) + attr(es, "env") <- NULL + + expect_snapshot(error = TRUE, vs$color) + expect_snapshot(error = TRUE, { + vs$color <- "blue" + }) + expect_snapshot(error = TRUE, es$weight) + expect_snapshot(error = TRUE, { + es$weight <- 0 + }) +}) + +test_that("`[<-.igraph.vs` reports an internal error when the graph is unknown", { + # Defensive branch flagged in PR #2006 review as possibly disappearing + # once attribute handling is reworked -- kept here as cheap insurance, + # not exhaustive coverage. + g <- make_ring(5) + vs <- V(g) + attr(vs, "env") <- NULL + payload <- structure(1, name = "color", value = "blue") + + expect_snapshot(error = TRUE, { + `[<-.igraph.vs`(vs, 1, value = payload) + }) }) From 59eec13109805aa486ead63266a37b9d13757d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 15:27:06 +0200 Subject: [PATCH 03/10] expand attribute removal --- R/iterators.R | 16 ++++++++++++-- tests/testthat/_snaps/iterators.md | 24 +++++++++++++------- tests/testthat/test-iterators.R | 35 ++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index 5b9983b5f68..ad877624899 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1200,7 +1200,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for attribute.") } - if (!rlang::has_name(attributes(value), "value")) { + if ( + !rlang::has_name(attributes(value), "value") && + !is_complete_iterator(value) + ) { cli::cli_abort( "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." ) @@ -1224,7 +1227,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (!rlang::has_name(attributes(value), "name")) { cli::cli_abort("Can't find {.val name} for attribute.") } - if (!rlang::has_name(attributes(value), "value")) { + if ( + !rlang::has_name(attributes(value), "value") && + !is_complete_iterator(value) + ) { cli::cli_abort( "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_edge_attr} instead." ) @@ -1397,6 +1403,9 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort("Can't find {.val name} for vertex attribute.") } if (!rlang::has_name(attributes(value), "value")) { + if (is_complete_iterator(value)) { + return(delete_vertex_attr(x, attr(value, "name"))) + } cli::cli_abort( "Can't find {.val value} for vertex attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." ) @@ -1423,6 +1432,9 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort("Can't find {.val name} for edge attribute.") } if (!rlang::has_name(attributes(value), "value")) { + if (is_complete_iterator(value)) { + return(delete_edge_attr(x, attr(value, "name"))) + } cli::cli_abort( "Can't find {.val value} for edge attribute. To remove an attribute, use {.fn delete_edge_attr} instead." ) diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index 608b22d090f..d716af5cdbf 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -108,29 +108,37 @@ Error in `FUN()`: ! Logical index length does not match the number of edges. Recycling is not allowed. -# assigning `NULL` errors clearly instead of silently doing nothing +# assigning `NULL` to a subset of vertices/edges errors instead of silently doing nothing Code - V(g)$color <- NULL + V(g)[1:3]$color <- NULL Condition - Error in `V<-`: - ! Can't find "value" for vertex attribute. To remove an attribute, use `delete_vertex_attr()` instead. + Error in `[<-`: + ! Can't find "value" for attribute. To remove an attribute, use `delete_vertex_attr()` instead. --- Code - V(g)[1:3]$color <- NULL + E(g)[1:3]$weight <- NULL Condition Error in `[<-`: - ! Can't find "value" for attribute. To remove an attribute, use `delete_vertex_attr()` instead. + ! Can't find "value" for attribute. To remove an attribute, use `delete_edge_attr()` instead. + +# assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()` + + Code + V(g)$color <- NULL + Condition + Error in `delete_vertex_attr()`: + ! No vertex attribute `color` found. --- Code E(g)$weight <- NULL Condition - Error in `E<-`: - ! Can't find "value" for edge attribute. To remove an attribute, use `delete_edge_attr()` instead. + Error in `delete_edge_attr()`: + ! No edge attribute `weight` found. # direct misuse of `V<-`/`E<-`/`[<-`/`[[<-` errors well diff --git a/tests/testthat/test-iterators.R b/tests/testthat/test-iterators.R index 849b7d1bd6c..15bdbd6b961 100644 --- a/tests/testthat/test-iterators.R +++ b/tests/testthat/test-iterators.R @@ -504,22 +504,43 @@ test_that("assigning `NA` blanks attribute values without removing the attribute expect_true("color" %in% vertex_attr_names(g)) }) -test_that("assigning `NULL` errors clearly instead of silently doing nothing", { +test_that("assigning `NULL` to the full sequence removes the attribute", { + g <- make_ring(5) + V(g)$color <- "red" + E(g)$weight <- 1:5 + + V(g)$color <- NULL + expect_false("color" %in% vertex_attr_names(g)) + + E(g)$weight <- NULL + expect_false("weight" %in% edge_attr_names(g)) +}) + +test_that("assigning `NULL` to a subset of vertices/edges errors instead of silently doing nothing", { # `attr(x, "value") <- NULL` removes the "value" attribute that # `$<-.igraph.vs`/`$<-.igraph.es` attach, rather than attaching a NULL - # value. That made `V(g)$attr <- NULL` (the base R idiom for removing a - # list/data.frame element) a silent no-op once the "value" attribute check - # was dropped. `delete_vertex_attr()`/`delete_edge_attr()` are the actual - # way to remove an attribute. + # value, so a sentinel is stored instead to mark removal intent + # (see `.igraph_attr_removal_sentinel`). Removing an attribute only makes + # sense for the full vertex/edge sequence; indexed assignment can't + # partially remove an attribute, so it errors clearly instead of doing + # nothing. g <- make_ring(5) V(g)$color <- "red" E(g)$weight <- 1:5 expect_snapshot(error = TRUE, { - V(g)$color <- NULL + V(g)[1:3]$color <- NULL }) expect_snapshot(error = TRUE, { - V(g)[1:3]$color <- NULL + E(g)[1:3]$weight <- NULL + }) +}) + +test_that("assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()`", { + g <- make_ring(5) + + expect_snapshot(error = TRUE, { + V(g)$color <- NULL }) expect_snapshot(error = TRUE, { E(g)$weight <- NULL From 588cec27e042fa4099c35cd6ccb1064f2780d4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 15:51:16 +0200 Subject: [PATCH 04/10] better --- R/iterators.R | 36 ++++++++++++++++----------------- tests/testthat/test-iterators.R | 9 +-------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index ad877624899..389cfc2c7d2 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1197,11 +1197,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @name igraph-vs-attributes #' @export `[[<-.igraph.vs` <- function(x, i, value) { - if (!rlang::has_name(attributes(value), "name")) { + if (!rlang::has_name(attributes(value), "attr_name")) { cli::cli_abort("Can't find {.val name} for attribute.") } if ( - !rlang::has_name(attributes(value), "value") && + !rlang::has_name(attributes(value), "attr_value") && !is_complete_iterator(value) ) { cli::cli_abort( @@ -1224,11 +1224,11 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @name igraph-es-attributes #' @export `[[<-.igraph.es` <- function(x, i, value) { - if (!rlang::has_name(attributes(value), "name")) { + if (!rlang::has_name(attributes(value), "attr_name")) { cli::cli_abort("Can't find {.val name} for attribute.") } if ( - !rlang::has_name(attributes(value), "value") && + !rlang::has_name(attributes(value), "attr_value") && !is_complete_iterator(value) ) { cli::cli_abort( @@ -1375,8 +1375,8 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (is.null(get_vs_graph(x))) { cli::cli_abort("Can't find graph.") } - attr(x, "name") <- name - attr(x, "value") <- value + attr(x, "attr_name") <- name + attr(x, "attr_value") <- value x } @@ -1390,8 +1390,8 @@ simple_es_index <- function(x, i, na_ok = FALSE) { if (is.null(get_es_graph(x))) { cli::cli_abort("Can't find graph.") } - attr(x, "name") <- name - attr(x, "value") <- value + attr(x, "attr_name") <- name + attr(x, "attr_value") <- value x } @@ -1399,12 +1399,12 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `V<-` <- function(x, value) { ensure_igraph(x) - if (!rlang::has_name(attributes(value), "name")) { + if (!rlang::has_name(attributes(value), "attr_name")) { cli::cli_abort("Can't find {.val name} for vertex attribute.") } - if (!rlang::has_name(attributes(value), "value")) { + if (!rlang::has_name(attributes(value), "attr_value")) { if (is_complete_iterator(value)) { - return(delete_vertex_attr(x, attr(value, "name"))) + return(delete_vertex_attr(x, attr(value, "attr_name"))) } cli::cli_abort( "Can't find {.val value} for vertex attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." @@ -1412,9 +1412,9 @@ simple_es_index <- function(x, i, na_ok = FALSE) { } i_set_vertex_attr( x, - attr(value, "name"), + attr(value, "attr_name"), index = value, - value = attr(value, "value"), + value = attr(value, "attr_value"), check = FALSE ) } @@ -1428,12 +1428,12 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `E<-` <- function(x, path = NULL, P = NULL, directed = NULL, value) { ensure_igraph(x) - if (!rlang::has_name(attributes(value), "name")) { + if (!rlang::has_name(attributes(value), "attr_name")) { cli::cli_abort("Can't find {.val name} for edge attribute.") } - if (!rlang::has_name(attributes(value), "value")) { + if (!rlang::has_name(attributes(value), "attr_value")) { if (is_complete_iterator(value)) { - return(delete_edge_attr(x, attr(value, "name"))) + return(delete_edge_attr(x, attr(value, "attr_name"))) } cli::cli_abort( "Can't find {.val value} for edge attribute. To remove an attribute, use {.fn delete_edge_attr} instead." @@ -1441,9 +1441,9 @@ simple_es_index <- function(x, i, na_ok = FALSE) { } i_set_edge_attr( x, - attr(value, "name"), + attr(value, "attr_name"), index = value, - value = attr(value, "value"), + value = attr(value, "attr_value"), check = FALSE ) } diff --git a/tests/testthat/test-iterators.R b/tests/testthat/test-iterators.R index 15bdbd6b961..b11d384a4e6 100644 --- a/tests/testthat/test-iterators.R +++ b/tests/testthat/test-iterators.R @@ -517,13 +517,6 @@ test_that("assigning `NULL` to the full sequence removes the attribute", { }) test_that("assigning `NULL` to a subset of vertices/edges errors instead of silently doing nothing", { - # `attr(x, "value") <- NULL` removes the "value" attribute that - # `$<-.igraph.vs`/`$<-.igraph.es` attach, rather than attaching a NULL - # value, so a sentinel is stored instead to mark removal intent - # (see `.igraph_attr_removal_sentinel`). Removing an attribute only makes - # sense for the full vertex/edge sequence; indexed assignment can't - # partially remove an attribute, so it errors clearly instead of doing - # nothing. g <- make_ring(5) V(g)$color <- "red" E(g)$weight <- 1:5 @@ -602,7 +595,7 @@ test_that("`[<-.igraph.vs` reports an internal error when the graph is unknown", g <- make_ring(5) vs <- V(g) attr(vs, "env") <- NULL - payload <- structure(1, name = "color", value = "blue") + payload <- structure(1, attr_name = "color", attr_value = "blue") expect_snapshot(error = TRUE, { `[<-.igraph.vs`(vs, 1, value = payload) From a4e0bc8e526d32515ec9330d35e85c6372d22368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 15:55:07 +0200 Subject: [PATCH 05/10] name name yay --- R/iterators.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index 389cfc2c7d2..cd24f34d4f1 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1412,7 +1412,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { } i_set_vertex_attr( x, - attr(value, "attr_name"), + name = attr(value, "attr_name"), index = value, value = attr(value, "attr_value"), check = FALSE @@ -1441,7 +1441,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { } i_set_edge_attr( x, - attr(value, "attr_name"), + name = attr(value, "attr_name"), index = value, value = attr(value, "attr_value"), check = FALSE From b36728852bc5f26b6f663317ae1db36e6fa77d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 16:00:14 +0200 Subject: [PATCH 06/10] better errors --- R/iterators.R | 20 ++++++++++++++++---- tests/testthat/_snaps/iterators.md | 6 ++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index cd24f34d4f1..85d3f67a7f0 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1205,7 +1205,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { !is_complete_iterator(value) ) { cli::cli_abort( - "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." + c( + "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_vertex_attr}." + ) ) } if (is.null(get_vs_graph(x))) { @@ -1232,7 +1235,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { !is_complete_iterator(value) ) { cli::cli_abort( - "Can't find {.val value} for attribute. To remove an attribute, use {.fn delete_edge_attr} instead." + c( + "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_edge_attr}." + ) ) } if (is.null(get_es_graph(x))) { @@ -1407,7 +1413,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { return(delete_vertex_attr(x, attr(value, "attr_name"))) } cli::cli_abort( - "Can't find {.val value} for vertex attribute. To remove an attribute, use {.fn delete_vertex_attr} instead." + c( + "Can't find {.val value} for vertex attribute {.val {attr(value, 'attr_name')}}.", + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_vertex_attr}." + ) ) } i_set_vertex_attr( @@ -1436,7 +1445,10 @@ simple_es_index <- function(x, i, na_ok = FALSE) { return(delete_edge_attr(x, attr(value, "attr_name"))) } cli::cli_abort( - "Can't find {.val value} for edge attribute. To remove an attribute, use {.fn delete_edge_attr} instead." + c( + "Can't find {.val value} for edge attribute {.val {attr(value, 'attr_name')}}.", + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_edge_attr}." + ) ) } i_set_edge_attr( diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index d716af5cdbf..69e114d58a8 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -114,7 +114,8 @@ V(g)[1:3]$color <- NULL Condition Error in `[<-`: - ! Can't find "value" for attribute. To remove an attribute, use `delete_vertex_attr()` instead. + ! Can't find "value" for attribute "color". + i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset. To remove it directly, use `delete_vertex_attr()`. --- @@ -122,7 +123,8 @@ E(g)[1:3]$weight <- NULL Condition Error in `[<-`: - ! Can't find "value" for attribute. To remove an attribute, use `delete_edge_attr()` instead. + ! Can't find "value" for attribute "weight". + i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset. To remove it directly, use `delete_edge_attr()`. # assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()` From a988499b5252e88796817102ae666d6dd7ff64de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 16:06:54 +0200 Subject: [PATCH 07/10] better wording --- R/iterators.R | 8 ++++---- tests/testthat/_snaps/iterators.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index 85d3f67a7f0..c383389c8d0 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1207,7 +1207,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_vertex_attr}." + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_vertex_attr}." ) ) } @@ -1237,7 +1237,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_edge_attr}." + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_edge_attr}." ) ) } @@ -1415,7 +1415,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for vertex attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_vertex_attr}." + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_vertex_attr}." ) ) } @@ -1447,7 +1447,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for edge attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. To remove it directly, use {.fn delete_edge_attr}." + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_edge_attr}." ) ) } diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index 69e114d58a8..067d5448c8b 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -115,7 +115,7 @@ Condition Error in `[<-`: ! Can't find "value" for attribute "color". - i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset. To remove it directly, use `delete_vertex_attr()`. + i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset, or use `delete_vertex_attr()`. --- @@ -124,7 +124,7 @@ Condition Error in `[<-`: ! Can't find "value" for attribute "weight". - i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset. To remove it directly, use `delete_edge_attr()`. + i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset, or use `delete_edge_attr()`. # assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()` From 85883bc93ee33cc77b200ee77e8f938d7bfdb78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 16:10:17 +0200 Subject: [PATCH 08/10] ok --- R/iterators.R | 8 ++++---- tests/testthat/_snaps/iterators.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index c383389c8d0..f86157656ab 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1207,7 +1207,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_vertex_attr}." + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_vertex_attr}." ) ) } @@ -1237,7 +1237,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_edge_attr}." + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_edge_attr}." ) ) } @@ -1415,7 +1415,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for vertex attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_vertex_attr}." + i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_vertex_attr}." ) ) } @@ -1447,7 +1447,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { cli::cli_abort( c( "Can't find {.val value} for edge attribute {.val {attr(value, 'attr_name')}}.", - i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset, or use {.fn delete_edge_attr}." + i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_edge_attr}." ) ) } diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index 067d5448c8b..01cba8e7203 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -115,7 +115,7 @@ Condition Error in `[<-`: ! Can't find "value" for attribute "color". - i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset, or use `delete_vertex_attr()`. + i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset. Use `delete_vertex_attr()`. --- @@ -124,7 +124,7 @@ Condition Error in `[<-`: ! Can't find "value" for attribute "weight". - i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset, or use `delete_edge_attr()`. + i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset. Use `delete_edge_attr()`. # assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()` From f46e70f9f79b64cd24aa3a9c6806774ceeadaa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Wed, 26 Aug 2026 16:14:45 +0200 Subject: [PATCH 09/10] more specific --- R/iterators.R | 8 ++++---- tests/testthat/_snaps/iterators.md | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/R/iterators.R b/R/iterators.R index f86157656ab..729a78c46ff 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -1198,7 +1198,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `[[<-.igraph.vs` <- function(x, i, value) { if (!rlang::has_name(attributes(value), "attr_name")) { - cli::cli_abort("Can't find {.val name} for attribute.") + cli::cli_abort("Can't find {.val name} for vertex attribute.") } if ( !rlang::has_name(attributes(value), "attr_value") && @@ -1206,7 +1206,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { ) { cli::cli_abort( c( - "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", + "Can't find {.val value} for vertex attribute {.val {attr(value, 'attr_name')}}.", i = "Removing an attribute is only supported for the whole vertex sequence, e.g. {.code V(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_vertex_attr}." ) ) @@ -1228,7 +1228,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { #' @export `[[<-.igraph.es` <- function(x, i, value) { if (!rlang::has_name(attributes(value), "attr_name")) { - cli::cli_abort("Can't find {.val name} for attribute.") + cli::cli_abort("Can't find {.val name} for edge attribute.") } if ( !rlang::has_name(attributes(value), "attr_value") && @@ -1236,7 +1236,7 @@ simple_es_index <- function(x, i, na_ok = FALSE) { ) { cli::cli_abort( c( - "Can't find {.val value} for attribute {.val {attr(value, 'attr_name')}}.", + "Can't find {.val value} for edge attribute {.val {attr(value, 'attr_name')}}.", i = "Removing an attribute is only supported for the whole edge sequence, e.g. {.code E(g)${attr(value, 'attr_name')} <- NULL}, not a subset. Use {.fn delete_edge_attr}." ) ) diff --git a/tests/testthat/_snaps/iterators.md b/tests/testthat/_snaps/iterators.md index 01cba8e7203..14bfdb75dfe 100644 --- a/tests/testthat/_snaps/iterators.md +++ b/tests/testthat/_snaps/iterators.md @@ -114,7 +114,7 @@ V(g)[1:3]$color <- NULL Condition Error in `[<-`: - ! Can't find "value" for attribute "color". + ! Can't find "value" for vertex attribute "color". i Removing an attribute is only supported for the whole vertex sequence, e.g. `V(g)$color <- NULL`, not a subset. Use `delete_vertex_attr()`. --- @@ -123,7 +123,7 @@ E(g)[1:3]$weight <- NULL Condition Error in `[<-`: - ! Can't find "value" for attribute "weight". + ! Can't find "value" for edge attribute "weight". i Removing an attribute is only supported for the whole edge sequence, e.g. `E(g)$weight <- NULL`, not a subset. Use `delete_edge_attr()`. # assigning `NULL` for a non-existent attribute errors like `delete_vertex_attr()`/`delete_edge_attr()` @@ -164,7 +164,7 @@ V(g)[1] <- "blue" Condition Error in `[<-`: - ! Can't find "name" for attribute. + ! Can't find "name" for vertex attribute. --- @@ -172,7 +172,7 @@ E(g)[1] <- "blue" Condition Error in `[<-`: - ! Can't find "name" for attribute. + ! Can't find "name" for edge attribute. --- @@ -180,7 +180,7 @@ V(g)[[1]] <- "blue" Condition Error in `[[<-`: - ! Can't find "name" for attribute. + ! Can't find "name" for vertex attribute. --- @@ -188,7 +188,7 @@ E(g)[[1]] <- "blue" Condition Error in `[[<-`: - ! Can't find "name" for attribute. + ! Can't find "name" for edge attribute. # querying or setting attributes errors when the graph is unknown From 35ad09fa6b7b044855800edb71a7e1f1eb286c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABlle=20Salmon?= Date: Tue, 8 Sep 2026 09:59:17 +0200 Subject: [PATCH 10/10] change to `isTRUE()` --- R/iterators.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/iterators.R b/R/iterators.R index 2ecad58faed..0b94db77a83 100644 --- a/R/iterators.R +++ b/R/iterators.R @@ -182,7 +182,7 @@ graph_id.igraph.es <- function(x, ...) { } is_complete_iterator <- function(x) { - identical(attr(x, "is_all"), TRUE) + isTRUE(attr(x, "is_all")) } set_complete_iterator <- function(x, value = TRUE) {